LoginSignup
19
6

More than 5 years have passed since last update.

Flutterでアニメーション

Last updated at Posted at 2018-12-20

はじめに

ぐるなびでサーバーサイドのエンジニアとして活動している前田です。
https://developers.gnavi.co.jp/archive/category/%E3%80%90%E5%9F%B7%E7%AD%86%E8%80%85%E3%80%91%E5%89%8D%E7%94%B0

Flutterがバージョン1.0としてメジャーリリースされましたね。
前からFlutterに興味があったのと、アニメーションの実装に興味があったので、
Flutterでアニメーションを実装してみました。

使ったライブラリ

flutter_sequence_animationというライブラリを使います。
https://pub.dartlang.org/packages/flutter_sequence_animation

実装

とりあえずなれるために実装した後、
ログインボタン押下→画面遷移のアニメーションを実装してみます。
クロスプラットフォームということなので、AndroidとiOS両方のシュミレータで実行してみます。

とりあえず実装してみた

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

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

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

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  AnimationController controller;
  SequenceAnimation sequenceAnimation;

  @override
  void initState() {
    super.initState();
    controller = new AnimationController(vsync: this);

    sequenceAnimation = SequenceAnimationBuilder()
        .addAnimatable(
            animatable: new EdgeInsetsTween(
              begin: const EdgeInsets.only(bottom: 40.0),
              end: const EdgeInsets.only(bottom: 200.0),
            ),
            from: Duration.zero,
            to: const Duration(milliseconds: 1000),
            curve: Curves.fastOutSlowIn,
            tag: "padding")
        .addAnimatable(
            animatable: new Tween<double>(begin: 0.0, end: 1.0),
            from: Duration.zero,
            to: const Duration(milliseconds: 1000),
            curve: Curves.fastOutSlowIn,
            tag: "opacity")
        .addAnimatable(
            animatable: Tween<double>(begin: 10.0, end: 200.0),
            from: const Duration(milliseconds: 1000),
            to: const Duration(milliseconds: 2000),
            curve: Curves.elasticOut,
            tag: 'width')
        .addAnimatable(
            animatable: Tween<double>(begin: 10.0, end: 200.0),
            from: const Duration(milliseconds: 2000),
            to: const Duration(milliseconds: 3000),
            curve: Curves.elasticOut,
            tag: 'height')
        .addAnimatable(
            animatable: ColorTween(begin: Colors.blue, end: Colors.red),
            from: const Duration(milliseconds: 3100),
            to: const Duration(milliseconds: 5000),
            curve: Curves.bounceOut,
            tag: 'color')
        .addAnimatable(
            animatable: new BorderRadiusTween(
              begin: new BorderRadius.circular(4.0),
              end: new BorderRadius.circular(100.0),
            ),
            from: const Duration(milliseconds: 3100),
            to: const Duration(milliseconds: 5000),
            curve: Curves.bounceOut,
            tag: "borderRadius")
        .animate(controller);
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  Widget _buildAnimation(BuildContext context, Widget child) {
    return new Container(
      padding: sequenceAnimation["padding"].value,
      alignment: Alignment.bottomCenter,
      child: new Opacity(
        opacity: sequenceAnimation["opacity"].value,
        child: new Container(
          width: sequenceAnimation["width"].value,
          height: sequenceAnimation["height"].value,
          decoration: new BoxDecoration(
            borderRadius: sequenceAnimation["borderRadius"].value,
            color: sequenceAnimation["color"].value,
          ),
        ),
      ),
    );
  }

  Future<Null> _playAnimation() async {
    try {
      await controller.forward().orCancel;
      await controller.reverse().orCancel;
    } on TickerCanceled {
      // the animation got canceled, probably because we were disposed
    }
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Staggered Animation"),
      ),
      body: new GestureDetector(
        behavior: HitTestBehavior.opaque,
        onTap: () {
          _playAnimation();
        },
        child: new Center(
          child: new Container(
            child: new AnimatedBuilder(
                animation: controller, builder: _buildAnimation),
          ),
        ),
      ),
    );
  }
}

flutter_animation_1.gif

ボタン押下時から画面遷移するまでのアニメーション

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

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

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

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  AnimationController controller;
  SequenceAnimation sequenceAnimation;

  @override
  void initState() {
    super.initState();
    controller = new AnimationController(vsync: this);

    sequenceAnimation = SequenceAnimationBuilder()
        .addAnimatable(
            animatable: new EdgeInsetsTween(
              begin: const EdgeInsets.only(bottom: 80.0),
              end: const EdgeInsets.only(bottom: 400.0),
            ),
            from: Duration.zero,
            to: const Duration(milliseconds: 3000),
            curve: Curves.elasticInOut,
            tag: "padding")
        .addAnimatable(
            animatable: new BorderRadiusTween(
              begin: new BorderRadius.circular(4.0),
              end: new BorderRadius.circular(100.0),
            ),
            from: Duration.zero,
            to: const Duration(milliseconds: 1000),
            curve: Curves.ease,
            tag: "borderRadius")
        .addAnimatable(
            animatable: Tween<double>(begin: 200.0, end: 40.0),
            from: Duration.zero,
            to: const Duration(milliseconds: 1000),
            curve: Curves.ease,
            tag: 'width')
        .addAnimatable(
            animatable: ColorTween(begin: Colors.pink, end: Colors.blue),
            from: const Duration(milliseconds: 1000),
            to: const Duration(milliseconds: 3000),
            curve: Curves.easeInOut,
            tag: 'color')
        .addAnimatable(
            animatable: new EdgeInsetsTween(
              begin: const EdgeInsets.only(bottom: 400.0),
              end: const EdgeInsets.only(bottom: 0.0),
            ),
            from: const Duration(milliseconds: 3000),
            to: const Duration(milliseconds: 4000),
            curve: Curves.ease,
            tag: "padding")
        .addAnimatable(
            animatable: new Tween<double>(begin: 1.0, end: 0.0),
            from: const Duration(milliseconds: 4000),
            to: const Duration(milliseconds: 5500),
            curve: Curves.ease,
            tag: "opacity")
        .addAnimatable(
            animatable: new BorderRadiusTween(
              begin: new BorderRadius.circular(100.0),
              end: new BorderRadius.circular(0.0),
            ),
            from: const Duration(milliseconds: 4000),
            to: const Duration(milliseconds: 4100),
            curve: Curves.ease,
            tag: "borderRadius")
        .addAnimatable(
            animatable: Tween<double>(begin: 40.0, end: 1000.0),
            from: const Duration(milliseconds: 4000),
            to: const Duration(milliseconds: 5000),
            curve: Curves.ease,
            tag: 'height')
        .addAnimatable(
            animatable: Tween<double>(begin: 40.0, end: 1000.0),
            from: const Duration(milliseconds: 4000),
            to: const Duration(milliseconds: 5000),
            curve: Curves.ease,
            tag: 'width')
        .animate(controller);
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  Widget _buildAnimation(BuildContext context, Widget child) {
    return new Container(
      padding: sequenceAnimation["padding"].value,
      alignment: Alignment.bottomCenter,
      child: new Opacity(
        opacity: sequenceAnimation["opacity"].value,
        child: new Container(
          width: sequenceAnimation["width"].value,
          height: sequenceAnimation["height"].value,
          decoration: new BoxDecoration(
            borderRadius: sequenceAnimation["borderRadius"].value,
            color: sequenceAnimation["color"].value,
          ),
        ),
      ),
    );
  }

  Future<Null> _playAnimation() async {
    try {
      await controller.forward().orCancel;
    } on TickerCanceled {
      // the animation got canceled, probably because we were disposed
    }
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text("Flutter Demo"),
      ),
      body: new GestureDetector(
        behavior: HitTestBehavior.opaque,
        onTap: () {
          _playAnimation();
        },
        child: new Center(
          child: new Container(
            child: new AnimatedBuilder(
                animation: controller, builder: _buildAnimation),
          ),
        ),
      ),
    );
  }
}

flutter_animation_2.gif

19
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
6