2021/2/9 Implicit animations codelab(コードラボ)の訳

implicit animations(暗黙的なアニメーション)コードラボへようこそ。ここでは、特定のプロパティセットのアニメーションを簡単に作成できるFlutterウィジェットの使用方法を学習します。

このコードラボを最大限に活用するには、次の基本的な知識が必要です。

  • Flutterアプリの作り方。
  • StatefulWidget(ステートフルウィジェット)の使用方法。

このコードラボは、次の資料をカバーしています。

  • AnimatedOpacityを使ったfade-in効果の実装方法
  • AnimatedContainerを使ってサイズ、色、マージンをアニメーション化する方法
  • implicit animationsのまとめと使用法、テクニック

このコードラボを完了するための推定時間:15〜30分。


What are implicit animations?

Flutterのアニメーションライブラリを使えば、UIでウィジェットのモーションを追加し、視覚効果を作成できます。

アニメーションライブラリの一部は、アニメーションを管理するウィジェットの品揃えです。

これらのウィジェットは、まとめてimplicit animationsまたはimplicitly animated widgetsと呼ばれ、実装するImplicitlyAnimatedWidgetクラスから名前が付けられます。

implicit animationsでは、ターゲット値を設定することでウィジェットプロパティをアニメーション化できます。

そのターゲット値が変更されるたびに、ウィジェットはプロパティを古い値から新しい値にアニメーション化します。 このように、暗黙的なアニメーションは、利便性のために制御を交換します。

つまり、implicit animationsがアニメーション効果を管理するため、開発者が管理用コードを記述する必要がありません。


Example: Fade-in text effect

次の例は、AnimatedOpacityと呼ばれる暗黙的にアニメーション化されたウィジェットを使用して既存のUIにフェードイン効果を追加する方法を示しています。 この例は、アニメーションコードなしで始まります。これは、以下を含むMaterialAppのホーム画面で構成されています。

  • フクロウの写真。
  • クリックしても何もしない1つの[詳細を表示]ボタン。
  • 写真のフクロウの説明文。

Fade-in (starter code)

// lib/main.dart

import 'package:flutter/material.dart';

const owl_url = 'https://raw.githubusercontent.com/flutter/website/master/src/images/owl.jpg';

class FadeInDemo extends StatefulWidget {
  _FadeInDemoState createState() => _FadeInDemoState();
}

class _FadeInDemoState extends State<FadeInDemo> {
  @override
  Widget build(BuildContext context) {
    return Column(children: <Widget>[
      Image.network(owl_url),
      TextButton(
          child: Text(
            'Show details',
            style: TextStyle(color: Colors.blueAccent),
          ),
          onPressed: () => null),
      Container(
        child: Column(
          children: <Widget>[
            Text('Type: Owl'),
            Text('Age: 39'),
            Text('Employment: None'),
          ],
        ),
      )
    ]);
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: FadeInDemo(),
        ),
      ),
    );
  }
}

void main() {
  runApp(
    MyApp(),
  );
}

Animate opacity with AnimatedOpacity widget

このセクションには、フェードインスターターコードに暗黙的なアニメーションを追加するために使用できる手順のリストが含まれています。 手順の後で、すでに行った変更を使用してフェードインの完全なコードを実行することもできます。

この手順では、AnimatedOpacityウィジェットを使用して次のアニメーション機能を追加する方法の概要を説明します。

  • フクロウの説明テキストは、ユーザーが[Show details(詳細を表示)]ボタンをクリックするまで非表示のままです。
  • ユーザーが[Show details]ボタンをクリックすると、フクロウの説明テキストがフェードインします。

1. Pick a widget property to animate

フェードイン効果を作成するには、AnimatedOpacityウィジェットを使用してopacityプロパティをアニメーション化します。

ContainerウィジェットをAnimatedOpacityウィジェットに変更します。

@override
Widget build(BuildContext context) {
  return Column(children: <Widget>[
    Image.network(owl_url),
    TextButton(
        child: Text(
          'Show details',
          style: TextStyle(color: Colors.blueAccent),
        ),
        onPressed: () => null),
    AnimatedOpacity( //←ここを修正
      child: Column(
        children: <Widget>[
          Text('Type: Owl'),
          Text('Age: 39'),
          Text('Employment: None'),
        ],
      ),
    )
  ]);
}

2. Initialize a state variable for the animated property

ユーザーが[show details(詳細を表示)]をクリックする前にテキストを非表示にするため、opacityの開始値をゼロに設定します。

class FadeInDemo extends StatefulWidget {
  _FadeInDemoState createState() => _FadeInDemoState();
}

class _FadeInDemoState extends State<FadeInDemo> {
  double opacity = 0.0; //←この状態変数を追加。
  
  @override
  Widget build(BuildContext context) {
    return Column(children: <Widget>[
      Image.network(owl_url),
      TextButton(
          child: Text(
            'Show details',
            style: TextStyle(color: Colors.blueAccent),
          ),
          onPressed: () => null),
      AnimatedOpacity(
        duration:Duration(seconds:3,), //←このパラメータを追加。
        opacity:opacity, //←このパラメータを追加。
        child: Column(
          children: <Widget>[
            Text('Type: Owl'),
            Text('Age: 39'),
            Text('Employment: None'),
          ],
        ),
      )
    ]);
  }
}

3. Set up a trigger for the animation, and choose an end value

@override
Widget build(BuildContext context) {
  return Column(children: <Widget>[
    Image.network(owl_url),
    TextButton(
        child: Text(
          'Show details',
          style: TextStyle(color: Colors.blueAccent),
        ),
        //↓ボタンを押した時のコールバック関数を記述する。
        onPressed: () => setState((){
          opacity = 1.0;
        })),
    AnimatedOpacity(
      duration:Duration(seconds:3,),
      opacity:opacity,
      child: Column(
        children: <Widget>[
          Text('Type: Owl'),
          Text('Age: 39'),
          Text('Employment: None'),
        ],
      ),
    )
  ]);
}

opacityの開始値と終了値のみを設定する必要があることに注意してください。 AnimatedOpacityウィジェットは、その間のすべてを管理します。


4. Set the duration of the animation

opacityパラメーターに加えて、AnimatedOpacityには、アニメーションに使用するdurationパラメータが必要です。 この例では、2秒に設定しています。

AnimatedOpacity(
       //↓2秒に設定。なぜ3秒から2秒に変更したのかは不明。
        duration:Duration(seconds:2,),
        opacity:opacity,
        child: Column(
          children: <Widget>[
            Text('Type: Owl'),
            Text('Age: 39'),
            Text('Employment: None'),
          ],
        ),
      )

Fade-in (complete)

変更が完了した例を次に示します。この例を実行し、[show details(詳細を表示)]ボタンをクリックしてアニメーションをトリガーします。

import 'package:flutter/material.dart';

const owl_url = 'https://raw.githubusercontent.com/flutter/website/master/src/images/owl.jpg';

class FadeInDemo extends StatefulWidget {
  _FadeInDemoState createState() => _FadeInDemoState();
}

class _FadeInDemoState extends State<FadeInDemo> {
  double opacity = 0.0;

  @override
  Widget build(BuildContext context) {
    return Column(children: <Widget>[
      Image.network(owl_url),
      TextButton(
          child: Text(
            'Show details',
            style: TextStyle(color: Colors.blueAccent),
          ),
          //↓ボタンを押した時のコールバック関数を記述する。
          onPressed: () => setState((){
            opacity = 1.0;
          })),
      AnimatedOpacity(
        duration:Duration(seconds:2,),
        opacity:opacity,
        child: Column(
          children: <Widget>[
            Text('Type: Owl'),
            Text('Age: 39'),
            Text('Employment: None'),
          ],
        ),
      )
    ]);
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: FadeInDemo(),
        ),
      ),
    );
  }
}

void main() {
  runApp(
    MyApp(),
  );
}

めちゃ簡単ですね✨implicit animationに感謝ですね。


Putting it all together(ここまでのまとめ)

フェードインテキスト効果の例は、AnimatedOpacityの次の機能を示しています。

  • AnimatedOpacityは、opacityプロパティ(状態変数)の状態変化をリッスンします。
  • opacityが変更されるたびに、AnimatedOpacityはウィジェットのopacityの新しい値への遷移を自動的にアニメーション化します。
  • AnimatedOpacityには、古い不透明度値(opacity)と新しい不透明度値(opacity)の間の遷移をアニメーション化するのにかかる時間を定義するためのdurationパラメーターが必要です。

implicit animationsは親のStatefulWidgetのプロパティ(状態変数)のみをアニメーション化できるため、この例はStatefulWidgetを拡張するFadeInDemoウィジェットから始まります。

AnimatedOpacityは、不透明度(opacity)という1つのプロパティをアニメーション化することにも注意してください。

次の例に示すように、implicit animations化されたウィジェットの中には、複数のプロパティをアニメーション化できるものがあります。


Example: Shape-shifting effect

次の例は、AnimatedContainerウィジェットを使用して、さまざまなタイプ(doubleとColor)で複数のプロパティ(margin、borderRadius、およびcolor)をアニメーション化する方法を示しています。

この例は、アニメーションコードなしで始まります。これは、以下を含むMaterialAppのホーム画面で始まります。

  • サンプルを実行するたびに、異なるborderRadius、margin、およびcolorプロパティを持つコンテナ。
  • クリックしても何もしない変更ボタン。

Shape-shifting (starter code)

//main.dart
//ホットリスタートを繰り返すと、毎回違う形、違う色の図形が表示される。

import 'dart:math';

import 'package:flutter/material.dart';

double randomBorderRadius() {
  return Random().nextDouble() * 64;
}

double randomMargin() {
  return Random().nextDouble() * 64;
}

Color randomColor() {
  return Color(0xFFFFFFFF & Random().nextInt(0xFFFFFFFF));
}

class AnimatedContainerDemo extends StatefulWidget {
  _AnimatedContainerDemoState createState() => _AnimatedContainerDemoState();
}

class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
  Color color;
  double borderRadius;
  double margin;

  @override
  initState() {
    color = randomColor();
    borderRadius = randomBorderRadius();
    margin = randomMargin();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: <Widget>[
            SizedBox(
              width: 128,
              height: 128,
              child: Container(
                margin: EdgeInsets.all(margin),
                decoration: BoxDecoration(
                  color: color,
                  borderRadius: BorderRadius.circular(borderRadius),
                ),
              ),
            ),
            ElevatedButton(
              child: Text('change'),
              //↓コールバック関数がこれなので、ボタンを押しても何も起こらない。
              onPressed: () => null, 
            ),
          ],
        ),
      ),
    );
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: AnimatedContainerDemo(),
    );
  }
}

void main() {
  runApp(
    MyApp(),
  );
}

Animate color, borderRadius, and margin with AnimatedContainer

スターターコードでは、コンテナウィジェット(AnimatedContainerDemoState)の各プロパティ(状態変数)(color、borderRadius、margin)に、関連する関数(randomColor()、randomBorderRadius()、randomMargin())によって値が割り当てられます。

AnimatedContainerウィジェットを使用すると、このコードをリファクタリングして次のことを実行できます。

  • ユーザーが[change(変更)]ボタンをクリックするたびに、color、borderRadius、およびmarginの新しい値を生成します。
  • color、borderRadius、marginが設定されるたびに、それらの新しい値への遷移をアニメーション化します。

1. Add an implicit animation

ContainerウィジェットをAnimatedContainerウィジェットに変更します。

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: Center(
      child: Column(
        children: <Widget>[
          SizedBox(
            width: 128,
            height: 128,
            child: AnimatedContainer( //←AnimatedContainerに変更。
              margin: EdgeInsets.all(margin),
              decoration: BoxDecoration(

2. Set starting values for animated properties

AnimatedContainerは、プロパティ(状態変数、color,borderRadius,margin)の古い値が新しい値が変更されると、それらの間で自動的にアニメーション化します。

ユーザーが[change(変更)]ボタンをクリックしたときにトリガーされる動作を定義するchange()メソッドを作成します。

void change(){
  setState((){
    color = randomColor();
    borderRadius = randomBorderRadius();
    margin = randomMargin();
  });
}

change()メソッドは、setState()を使用して、状態変数color、borderRadius、およびmarginに新しい値を設定します。


3. Set up a trigger for the animation

ユーザーが[変更]ボタンを押すたびにトリガーするようにアニメーションを設定するには、onPressed()ハンドラーでchange()メソッドを呼び出します。

ElevatedButton(
  child: Text('change'),
  onPressed: () => change(), //←ここに定義したchange()メソッドを設定する。

4. Set duration

import 'dart:math';
import 'package:flutter/material.dart';

const _duration = Duration(milliseconds: 400); //←を宣言する。

//...
//...

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: <Widget>[
            SizedBox(
              width: 128,
              height: 128,
              child: AnimatedContainer(
                margin: EdgeInsets.all(margin),
                decoration: BoxDecoration(
                  color: color,
                  borderRadius: BorderRadius.circular(borderRadius),
                ),
                duration:_duration, //←宣言した_duration定数をここにセットする。
              ),
            ),
            ElevatedButton(

Shape-shifting (complete)

完了した変更の例を次に示します。コードを実行し、[変更]ボタンをクリックしてアニメーションをトリガーします。 [変更]ボタンをクリックするたびに、シェイプがmargin、borderRadius、およびcolorの新しい値にアニメーション化されることに注意してください。

import 'dart:math';

import 'package:flutter/material.dart';

const _duration = Duration(milliseconds: 400);

double randomBorderRadius() {
  return Random().nextDouble() * 64;
}

double randomMargin() {
  return Random().nextDouble() * 64;
}

Color randomColor() {
  return Color(0xFFFFFFFF & Random().nextInt(0xFFFFFFFF));
}

class AnimatedContainerDemo extends StatefulWidget {
  _AnimatedContainerDemoState createState() => _AnimatedContainerDemoState();
}

class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
  Color color;
  double borderRadius;
  double margin;

  @override
  initState() {
    color = randomColor();
    borderRadius = randomBorderRadius();
    margin = randomMargin();
  }

  void change(){
    setState((){
      color = randomColor();
      borderRadius = randomBorderRadius();
      margin = randomMargin();
    });
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: <Widget>[
            SizedBox(
              width: 128,
              height: 128,
              child: AnimatedContainer(
                margin: EdgeInsets.all(margin),
                decoration: BoxDecoration(
                  color: color,
                  borderRadius: BorderRadius.circular(borderRadius),
                ),
                duration:_duration,
              ),
            ),
            ElevatedButton(
              child: Text('change'),
              onPressed: () => change(),
            ),
          ],
        ),
      ),
    );
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: AnimatedContainerDemo(),
    );
  }
}

void main() {
  runApp(
    MyApp(),
  );
}


Using animation curves

上記の例で、implicit animationsを使用して特定のウィジェットプロパティ(状態変数)の値の変更をアニメーション化する方法と、durationパラメーターを使用してアニメーションの完了にかかる時間を設定する方法を示して来ました。

implicit animationsを使用すると、期間(duration)内のアニメーションの速度の変更を制御することもできます。 このレートの変化を定義するために使用するパラメーターはcurveです。

上記の例ではcurveパラメータを指定していないため、暗黙的なアニメーションではデフォルトの linear animation curve(線形アニメーション曲線)が適用されます。

child: AnimatedContainer(
                margin: EdgeInsets.all(margin),
                decoration: BoxDecoration(
                  color: color,
                  borderRadius: BorderRadius.circular(borderRadius),
                ),
                duration:_duration,
                curve:Curves.elasticInOut,
              ),
            ),
            ElevatedButton(
              child: Text('change'),

AnimatedContainerウィジェットのcurveパラメータに、CurveのelasticInOut定数を渡すとアニメーションがどのように変化するかを確認してみましょう。

curveの値としてelasticInOutをAnimatedContainerに渡したので、margin、borderRadius、およびcolorの変化率がelasticInOut曲線で定義された曲線に従うことに注意してください。

elasticInOut定数は、curveパラメーターに渡すことができる多くの定数の1つにすぎません。 Curve定数のリストを調べて、曲線を使用してアニメーションのルックアンドフィールを変更するその他の方法を見つけてください。


Putting it all together

工事中🏗

 


What’s next?

おめでとうございます。コードラボが完成しました。 詳細を知りたい場合は、次に進むべき場所についていくつかの提案があります。

 

参考

https://flutter.dev/docs/codelabs/implicit-animations

コメントを残す

メールアドレスが公開されることはありません。