Flutter入门10--动画

Animation

  • 在Flutter中,实现动画的核心类是Animation,Widget可以直接将这些动画合并到自己的build方法中来读取它们当前值或监听它们的状态变化;
  • Animation是一个抽象类,常见的方法如下:
  • addListener方法
    • 每当动画的状态值发生变化时,动画都会通知所有通过 addListener 添加的监听器。
    • 通常,一个正在监听动画的state对象会调用自身的setState方法,将自身传入这些监听器的回调函数来通知 widget 系统需要根据新状态值进行重新构建。
  • addStatusListener方法
    • 当动画的状态发生变化时,会通知所有通过 addStatusListener 添加的监听器。
    • 通常情况下,动画会从 dismissed 状态开始,表示它处于变化区间的开始点。
    • 举例来说,从 0.0 到 1.0 的动画在 dismissed 状态时的值应该是 0.0。
    • 动画进行的下一状态可能是 forward(比如从 0.0 到 1.0)或者 reverse(比如从 1.0 到 0.0)。
    • 最终,如果动画到达其区间的结束点(比如 1.0),则动画会变成 completed状态。

AnimationController

  • AnimationController是Animation的一个子类,通常我们会创建一个 AnimationController实例对象来实现动画效果;
  • AnimationController会生成一系列的值,默认情况下值是0.0到1.0区间的值;
  • AnimationController提供了对动画的控制
    • forward:向前执行动画
    • reverse:方向播放动画
    • stop:停止动画
  • 其构造函数如下:
  AnimationController({
    double value,
    this.duration,
    this.reverseDuration,
    this.debugLabel,
    this.lowerBound = 0.0,
    this.upperBound = 1.0,
    this.animationBehavior = AnimationBehavior.normal,
    @required TickerProvider vsync,
  })
  • value:初始化值;
  • duration:动画执行的时间;
  • reverseDuration:反向动画执行的时间;
  • lowerBound:最小值;
  • upperBound:最大值;
  • vsync:垂直信号,必传参数;

CurvedAnimation

  • CurvedAnimation是Animation的一个子类,它的作用是给Animation增加执行曲线,执行速率;
  • CurvedAnimation可以将AnimationController和Curve结合起来,生成一个新的Animation对象;
  • Curve类型的对象的有一些常量Curves(和Color类型有一些Colors是一样的),可以供我们直接使用,在官网https://api.flutter.dev/flutter/animation/Curves-class.html 中提供了动画效果;

Tween

  • 默认情况下,AnimationController动画生成的值所在区间是0.0到1.0,如果需要使用这个以外的值,或者其他的数据类型,就需要使用Tween;
  • Tween构造函数如下:
Tween({ this.begin, this.end })
  • 传参味初始值与结束值,可确定一段区间;
  • Tween也有一些子类,比如ColorTween、BorderTween,可以针对动画或者边框来设置动画的值;

案例代码:

  • 需求:目标组件心形图标,由小到大,由大到小 无限循环 ,点击可暂停动画,再次点击在原来的状态基础上继续执行动画;
import 'package:flutter/material.dart';

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

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatefulWidget {
  @override
  _SFHomePageState createState() => _SFHomePageState();
}

class _SFHomePageState extends State with SingleTickerProviderStateMixin{
  AnimationController _controller;
  Animation _animation;
  Animation _animationSize;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    //1.创建Animation Controller
    _controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 2),
      lowerBound: 0.0,
      upperBound: 1.0
    );

    //2.设置Curve值 动画的执行速率
    _animation = CurvedAnimation(parent: _controller, curve: Curves.linear);

    //3.Tween
    _animationSize = Tween(begin: 50.0,end: 150.0).animate(_animation);

    //3.监听动画值的改变 刷新界面
    _controller.addListener(() {
      setState(() {

      });
    });

    //4.监听动画状态的改变
    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _controller.reverse();
      }else if (status == AnimationStatus.dismissed) {
        _controller.forward();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text("基础widget")),
        body: Center(
          child: Icon(Icons.favorite,color: Colors.red,size: _animationSize.value),
        ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: () {
          if (_controller.isAnimating) {
            _controller.stop();
          } else if (_controller.status == AnimationStatus.forward) {
            _controller.forward();
          } else if (_controller.status == AnimationStatus.reverse) {
            _controller.reverse();
          } else {
            _controller.forward();
          }
        },
      ),
    );
  }

  @override
  void dispose() {
    //销毁 回收内存
    _controller.dispose();
    super.dispose();
  }
}

AnimatedWidget

  • 上述实现的动画效果,存在一个弊端:
  • 监听动画值的改变,然后调用setState方法,就会导致State类build的方法重新执行,里面包含的所有组件都会重新构建,这样效率十分低下;
  • 可通过AnimatedWidget来解决这个弊端,执行动画时,仅仅让心形组件重建即可,自定义组件SFAnimationIcon继承自AnimatedWidget, 实现如下:
import 'package:flutter/material.dart';

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

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatefulWidget {
  @override
  _SFHomePageState createState() => _SFHomePageState();
}

class _SFHomePageState extends State with SingleTickerProviderStateMixin{
  AnimationController _controller;
  Animation _animation;
  Animation _animationSize;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    //1.创建Animation Controller
    _controller = AnimationController(
      vsync: this,
      duration: Duration(seconds: 2),
      lowerBound: 0.0,
      upperBound: 1.0
    );

    //2.设置Curve值 动画的执行速率
    _animation = CurvedAnimation(parent: _controller, curve: Curves.linear);

    //3.Tween
    _animationSize = Tween(begin: 50.0,end: 150.0).animate(_animation);

    //3.监听动画值的改变 刷新界面
    // _controller.addListener(() {
    //   setState(() {
    //
    //   });
    // });

    //4.监听动画状态的改变
    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _controller.reverse();
      }else if (status == AnimationStatus.dismissed) {
        _controller.forward();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    print("_SFHomePageState build");
    return Scaffold(
        appBar: AppBar(title: Text("基础widget")),
        body: Center(
          child: SFAnimationIcon(_animationSize),
        ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: () {
          if (_controller.isAnimating) {
            _controller.stop();
          } else if (_controller.status == AnimationStatus.forward) {
            _controller.forward();
          } else if (_controller.status == AnimationStatus.reverse) {
            _controller.reverse();
          } else {
            _controller.forward();
          }
        },
      ),
    );
  }

  @override
  void dispose() {
    //销毁 回收内存
    _controller.dispose();
    super.dispose();
  }
}

class SFAnimationIcon extends AnimatedWidget {
  final Animation _animationSize;

  SFAnimationIcon(this._animationSize): super(listenable: _animationSize);

  @override
  Widget build(BuildContext context) {
    return Icon(Icons.favorite,color: Colors.red,size: _animationSize.value);
  }
}

AnimatedBuilder

  • AnimatedWidget解决了上述动画的执行效率问题,但其也存在弊端:
    • 我们每次都要新建一个类来继承自AnimatedWidget;
    • 如果我们的动画Widget有子Widget,那么意味着它的子Widget也会重新build;
  • 可通过AnimatedBuilder来解决上面的两个问题,代码如下:
import 'package:flutter/material.dart';

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

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatefulWidget {
  @override
  _SFHomePageState createState() => _SFHomePageState();
}

class _SFHomePageState extends State with SingleTickerProviderStateMixin{
  AnimationController _controller;
  Animation _animation;
  Animation _animationSize;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    //1.创建Animation Controller
    _controller = AnimationController(
        vsync: this,
        duration: Duration(seconds: 2),
        lowerBound: 0.0,
        upperBound: 1.0
    );

    //2.设置Curve值 动画的执行速率
    _animation = CurvedAnimation(parent: _controller, curve: Curves.linear);

    //3.Tween
    _animationSize = Tween(begin: 50.0,end: 150.0).animate(_animation);

    //3.监听动画值的改变 刷新界面
    // _controller.addListener(() {
    //   setState(() {
    //
    //   });
    // });

    //4.监听动画状态的改变
    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _controller.reverse();
      }else if (status == AnimationStatus.dismissed) {
        _controller.forward();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    print("_SFHomePageState build");
    return Scaffold(
      appBar: AppBar(title: Text("基础widget")),
      body: Center(
        child: AnimatedBuilder(
          animation: _controller,
          builder: (ctx,child) {
            return Icon(Icons.favorite,color: Colors.red,size: _animationSize.value);
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: () {
          if (_controller.isAnimating) {
            _controller.stop();
          } else if (_controller.status == AnimationStatus.forward) {
            _controller.forward();
          } else if (_controller.status == AnimationStatus.reverse) {
            _controller.reverse();
          } else {
            _controller.forward();
          }
        },
      ),
    );
  }

  @override
  void dispose() {
    //销毁 回收内存
    _controller.dispose();
    super.dispose();
  }
}

交织动画

  • 动画集合了透明度变化、大小变化、颜色变化、旋转动画等;
  • 通过多个Tween生成了多个Animation对象;
  • 代码如下所示:
import 'dart:math';

import 'package:flutter/material.dart';

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

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatefulWidget {
  @override
  _SFHomePageState createState() => _SFHomePageState();
}

class _SFHomePageState extends State with SingleTickerProviderStateMixin{
  AnimationController _controller;
  Animation _animation;
  Animation _animationSize;
  Animation _animationColor;
  Animation _animationOpacity;
  Animation _animationRotation;

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    //1.创建Animation Controller
    _controller = AnimationController(
        vsync: this,
        duration: Duration(seconds: 2),
        lowerBound: 0.0,
        upperBound: 1.0
    );

    //2.设置Curve值 动画的执行速率
    _animation = CurvedAnimation(parent: _controller, curve: Curves.linear);

    //3.Tween
    _animationSize = Tween(begin: 10.0,end: 200.0).animate(_controller);
    _animationColor = ColorTween(begin: Colors.orange,end: Colors.pink).animate(_controller);
    _animationOpacity = Tween(begin: 0.0,end: 1.0).animate(_controller);
    _animationRotation = Tween(begin: 0.0,end: 2 * pi).animate(_controller);

    //4.监听动画状态的改变
    _controller.addStatusListener((status) {
      if (status == AnimationStatus.completed) {
        _controller.reverse();
      }else if (status == AnimationStatus.dismissed) {
        _controller.forward();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    print("_SFHomePageState build");
    return Scaffold(
      appBar: AppBar(title: Text("基础widget")),
      body: Center(
        child: AnimatedBuilder(
          animation: _controller,
          builder: (ctx,child) {
            return Opacity(
              opacity: _animationOpacity.value,
              child: Transform(
                transform: Matrix4.rotationZ(_animationRotation.value),
                alignment: Alignment.center,
                child: Container(
                  width: _animationSize.value,
                  height: _animationSize.value,
                  color: _animationColor.value,
                ),
              ),
            );
          },
        )
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.play_arrow),
        onPressed: () {
          if (_controller.isAnimating) {
            _controller.stop();
          } else if (_controller.status == AnimationStatus.forward) {
            _controller.forward();
          } else if (_controller.status == AnimationStatus.reverse) {
            _controller.reverse();
          } else {
            _controller.forward();
          }
        },
      ),
    );
  }

  @override
  void dispose() {
    //销毁 回收内存
    _controller.dispose();
    super.dispose();
  }
}

转场动画

  • 界面切换时的动画效果;
  • 新建一个界面ModalPage,代码如下:

import 'package:flutter/material.dart';

class ModalPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.red,
      appBar: AppBar(
        title: Text("Modal Page"),
      ),
      body: Center(
        child: Text("Modal Page"),
      ),
    );
  }
}
  • 首页代码:
import 'package:Fluter01/day01/pages/ModalPage.dart';
import 'package:flutter/material.dart';

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

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(title: Text("基础widget")),
        body: Center(
          child: Text("Hello world!!"),
        ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.pool),
        onPressed: () {
          //iOS Modal界面切换
          // Navigator.of(context).push(MaterialPageRoute(
          //   builder: (ctx) {
          //     return ModalPage();
          //   },
          //   fullscreenDialog: true
          // ));

          Navigator.of(context).push(PageRouteBuilder(
            transitionDuration: Duration(seconds: 3),
            pageBuilder: (ctx,animation1,animation2) {
              return FadeTransition(
                opacity: animation1,
                child: ModalPage()
              );
            }
          ));

        },
      ),
    );
  }
}
  • 通过PageRouteBuilder实现转场动画效果;

Hero动画

  • 需求:图片列表展示图片,当点击一张图片时,切换到图片详情页面,显示点击的图片,切换有动画效果;
  • 图片详情页面,代码如下:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class ImageDetailPage extends StatelessWidget {

  final String imageUrl;

  ImageDetailPage(this.imageUrl);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: Center(
        child: GestureDetector(
          onTap: () {
            Navigator.of(context).pop();
          },
          child: Hero(
            tag: imageUrl,
            child: Image.network(imageUrl)
          )
        ),
      ),
    );
  }
}
  • 首页代码如下:
import 'package:Fluter01/day01/pages/ImageDetailPage.dart';
import 'package:Fluter01/day01/pages/ModalPage.dart';
import 'package:flutter/material.dart';

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

class SFMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: SFHomePage());
  }
}

class SFHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("基础widget")),
      body: GridView(
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          crossAxisSpacing: 8,
          mainAxisSpacing: 8,
          childAspectRatio: 16/9
        ),
        children: List.generate(20, (index) {
          final String imageUrl = "https://picsum/photos/500/500?random=$index";
          return GestureDetector(
            onTap: () {
              Navigator.of(context).push(PageRouteBuilder(
                pageBuilder: (ctx,animation1,animation2) {
                  return FadeTransition(opacity: animation1, child: ImageDetailPage(imageUrl));
                }
              ));
            },
            child: Hero(
              tag: imageUrl,
              child: Image.network(
                "https://picsum/photos/500/500?random=$index",
                fit: BoxFit.cover,
              ),
            ),
          );
        }),
      ),
    );
  }
}

你可能感兴趣的:(Flutter入门10--动画)