😺

Flutterアニメーション(Part 2)

2022/11/21に公開

日本語版:

はじめに

この記事を読む前に、Flutterアニメーション(Part 1)を読んでいることを確認してください。

カーブド・アニメーション

カーブド・アニメーションは、アニメーション・オブジェクトで非線形カーブを適用する必要がある場合に、非常に便利です。したがって、アニメーションの進行を非線形曲線として定義します。

CurvedAnimation(parent: animationController, curve: Curves.bounceOut));  

カーブを追加するには、android studioで前回のアプリを開き、animationControllerの代わりにCurvedAnimationを追加します。

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Animation',
      theme: ThemeData(
        // This is the theme of your application.
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  late Animation<double> animation;
  late AnimationController animationController;
  
  void initState() {
    super.initState();
    animationController = AnimationController(
        vsync: this, duration: Duration(milliseconds: 2500));
    animation = Tween<double>(begin: 0.0, end: 1.0).animate(
        CurvedAnimation(parent: animationController, curve: Curves.bounceOut));
    animation.addListener(() {
      setState(() {
        print(animation.value.toString());
      });
    });
    animation.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        animationController.reverse();
      } else if (status == AnimationStatus.dismissed) {
        animationController.forward();
      }
    });
    animationController.forward();
  }

  
  Widget build(BuildContext context) {
    return Center(
        child: AnimatedLogo(
      animation: animation,
    ));
  }
}

class AnimatedLogo extends AnimatedWidget {
  final Tween<double> _sizeAnimation = Tween<double>(begin: 0.0, end: 50.0);
  AnimatedLogo({Key? key, required Animation animation})
      : super(key: key, listenable: animation);
  
  Widget build(BuildContext context) {
    final Animation<double> animation = listenable as Animation<double>;
    return Transform.scale(
      scale: _sizeAnimation.evaluate(animation),
      child: FlutterLogo(),
    );
  }
}

ヒーローアニメーション

ヒーローアニメーションとは、アプリが次のページに進むときに、ある画面の要素が新しい画面に飛んでいくアニメーションの一種です。例えば、アイコンや画像のような要素を取り込み、それをタップすると次のページに飛ぶようなアニメーションです。次の例では、よりわかりやすく説明しています。

Flutterアプリを開き、main.dartファイル内の以下のコードを置き換えてください。

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Application',
      theme: ThemeData(
        primarySwatch: Colors.orange,
      ),
      home: const HeroAnimation(title: 'Hero Animation'),
    );
  }
}

class HeroAnimation extends StatefulWidget {
  const HeroAnimation({Key? key, required this.title}) : super(key: key);
  final String title;

  
  _HeroAnimationState createState() => _HeroAnimationState();
}

class _HeroAnimationState extends State<HeroAnimation> {
  Widget _greenRectangle() {
    return Container(
      width: 75,
      height: 75,
      color: Colors.green,
    );
  }

  Widget _detailPageRectangle() {
    return Container(
      width: 150,
      height: 150,
      color: Colors.red,
    );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: buildDemoWidget(context),
    );
  }

  Widget buildDemoWidget(BuildContext context) {
    return Center(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          SizedBox(
            height: 30.0,
          ),
          ListTile(
            leading: GestureDetector(
              child: Hero(
                tag: 'hero-rectangle',
                child: _greenRectangle(),
              ),
              onTap: () => _gotoDetailsPage(context),
            ),
            title: Text(
                '緑色のアイコンをタップすると、ヒーローアニメーションの遷移を解析することができます。'),
          ),
        ],
      ),
    );
  }

  void _gotoDetailsPage(BuildContext context) {
    Navigator.of(context).push(MaterialPageRoute(
      builder: (ctx) => Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Hero(
                tag: 'hero-rectangle',
                child: _detailPageRectangle(),
              ),
              Text(
                  '前ページでタップしたアイコンの詳細が表示されます。.'),
            ],
          ),
        ),
      ),
    ));
  }
}

English ver:

Introduction

Make sure you have read Flutter Animation (Part 1) before reading this article.

Curved animation

Curved animation is very useful when nonlinear curves need to be applied on animated objects. Thus, the animation progression is defined as a nonlinear curve.

CurvedAnimation(parent: animationController, curve: Curves.bounceOut));  

To add a curve, open the previous app in android studio and add CurvedAnimation instead of animationController.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Animation',
      theme: ThemeData(
        // This is the theme of your application.
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  late Animation<double> animation;
  late AnimationController animationController;
  
  void initState() {
    super.initState();
    animationController = AnimationController(
        vsync: this, duration: Duration(milliseconds: 2500));
    animation = Tween<double>(begin: 0.0, end: 1.0).animate(
        CurvedAnimation(parent: animationController, curve: Curves.bounceOut));
    animation.addListener(() {
      setState(() {
        print(animation.value.toString());
      });
    });
    animation.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        animationController.reverse();
      } else if (status == AnimationStatus.dismissed) {
        animationController.forward();
      }
    });
    animationController.forward();
  }

  
  Widget build(BuildContext context) {
    return Center(
        child: AnimatedLogo(
      animation: animation,
    ));
  }
}

class AnimatedLogo extends AnimatedWidget {
  final Tween<double> _sizeAnimation = Tween<double>(begin: 0.0, end: 50.0);
  AnimatedLogo({Key? key, required Animation animation})
      : super(key: key, listenable: animation);
  
  Widget build(BuildContext context) {
    final Animation<double> animation = listenable as Animation<double>;
    return Transform.scale(
      scale: _sizeAnimation.evaluate(animation),
      child: FlutterLogo(),
    );
  }
}

Hero Animation

Hero animation is a type of animation in which elements from one screen jump to a new screen when the application advances to the next page. For example, an animation that takes an element such as an icon or image and when you tap it, it jumps to the next page. The following example illustrates this more clearly.

Open the Flutter app and replace the following code in the main.dart file

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

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Application',
      theme: ThemeData(
        primarySwatch: Colors.orange,
      ),
      home: const HeroAnimation(title: 'Hero Animation'),
    );
  }
}

class HeroAnimation extends StatefulWidget {
  const HeroAnimation({Key? key, required this.title}) : super(key: key);
  final String title;

  
  _HeroAnimationState createState() => _HeroAnimationState();
}

class _HeroAnimationState extends State<HeroAnimation> {
  Widget _greenRectangle() {
    return Container(
      width: 75,
      height: 75,
      color: Colors.green,
    );
  }

  Widget _detailPageRectangle() {
    return Container(
      width: 150,
      height: 150,
      color: Colors.red,
    );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: buildDemoWidget(context),
    );
  }

  Widget buildDemoWidget(BuildContext context) {
    return Center(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          SizedBox(
            height: 30.0,
          ),
          ListTile(
            leading: GestureDetector(
              child: Hero(
                tag: 'hero-rectangle',
                child: _greenRectangle(),
              ),
              onTap: () => _gotoDetailsPage(context),
            ),
            title: Text(
                'Tap on the green icon rectangle to analyse hero animation transition.'),
          ),
        ],
      ),
    );
  }

  void _gotoDetailsPage(BuildContext context) {
    Navigator.of(context).push(MaterialPageRoute(
      builder: (ctx) => Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Hero(
                tag: 'hero-rectangle',
                child: _detailPageRectangle(),
              ),
              Text(
                  'This is a place where you can see details about the icon tapped at previous page.'),
            ],
          ),
        ),
      ),
    ));
  }
}

Discussion