Flutter记录自学flutter点点滴滴

Flutter 学习之旅(三十五) Flutter 动画(二)

2020-09-15  本文已影响0人  Tsm_2020

上一篇我们写了一个文件渐变动画,但是写法非常繁琐,而且动画和控件结合的太紧密了,如果继续添加别的效果需要修改的地方太多了,先把上一篇最后的代码贴出来,我们一点一点优化

class _TsmAnimationState extends State<TsmAnimationPage>
    with SingleTickerProviderStateMixin {
  Animation<int> animation;
  AnimationController animatedContainer;

  @override
  void initState() {
    animatedContainer = AnimationController(
        duration: Duration(seconds: 2),
        lowerBound: 0,
        upperBound: 1,
        vsync: this);

    ///添加变速模型
    Animation curve =
        CurvedAnimation(parent: animatedContainer, curve: Curves.easeIn,);

    ///改变起始值
    animation = IntTween(begin: 0, end: 255).animate(curve);


   
    animatedContainer.addListener(() {
      setState(() {
        printString("value:${animation.value}");
      });
    });
    animatedContainer.addStatusListener((status) {
      if(status==AnimationStatus.completed){
        animatedContainer.reverse();
      }else if(status==AnimationStatus.dismissed){
        animatedContainer.forward();
      }
    });
    animatedContainer.forward();
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Animation 学习"),
        centerTitle: true,
      ),
      body: Container(
        child: Center(
          child: Text(
            "重复渐隐动画",
            style: TextStyle(
              color: Color.fromARGB(animation.value, 1, 1, 1),
            ),
          ),
        ),
      ),
    );
  }
}

这里先梳理一下创建动画的过程,

1 创建AnimationController 指定动画时间 ,和 TickerProvider

2 创建Animation 指定AnimationController 和 动画速度变化器curve

我们执行动画的过程中可能同时执行多个动画,但是每个动画执行的时机可能不同,这就要求我们要控制每个动画开始的时间和结束的时间,

CurvedAnimation(parent: _controller,curve: Interval(0.2,0.8,curve: Curves.easeOut))

Interval 两个必要参数,开始和结束时间 还有一个非必要参数速度变化器,这样就能指定这个动画执行的时机

3 创建Tween 利用Tween改变Animation 的值,可以是color offset rect double int 等等

4 添加监听,在数据发生变化时利用setState 方法重新绘制widget,

整个过程中我们发现第4步其实是通用的,而且每次添加监听在关闭页面的时候还需要移除,如果每次定义动画都这么写的话太繁琐,而且移除部分还会忘记,其实可以精简一下
这里就引出了 AnimatedWidget

AnimatedWidget

先来看一下AnimatedWidget 类

abstract class AnimatedWidget extends StatefulWidget {
  /// Creates a widget that rebuilds when the given listenable changes.
  ///
  /// The [listenable] argument is required.
  const AnimatedWidget({
    Key key,
    @required this.listenable,
  }) : assert(listenable != null),
       super(key: key);

  /// The [Listenable] to which this widget is listening.
  ///
  /// Commonly an [Animation] or a [ChangeNotifier].
  final Listenable listenable;

  /// Override this method to build widgets that depend on the state of the
  /// listenable (e.g., the current value of the animation).
  @protected
  Widget build(BuildContext context);

  /// Subclasses typically do not override this method.
  @override
  _AnimatedState createState() => _AnimatedState();

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty<Listenable>('animation', listenable));
  }
}

class _AnimatedState extends State<AnimatedWidget> {
  @override
  void initState() {
    super.initState();
    widget.listenable.addListener(_handleChange);
  }

  @override
  void didUpdateWidget(AnimatedWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.listenable != oldWidget.listenable) {
      oldWidget.listenable.removeListener(_handleChange);
      widget.listenable.addListener(_handleChange);
    }
  }

  @override
  void dispose() {
    widget.listenable.removeListener(_handleChange);
    super.dispose();
  }

  void _handleChange() {
    setState(() {
      // The listenable's state is our build state, and it changed already.
    });
  }

  @override
  Widget build(BuildContext context) => widget.build(context);
}

入参需要一个Listenable ,而Animation 就是继承自 Listenable,这里给一个animation就可以了,
其他的放在state ,在initState 方法中添加监听,在 didUpdateWidget 移除原有监听,并添加新的监听,dispose中移除监听

例子

class TsmAnimatedBuilderPage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _TsmAnimatedBuilderState();
}

class TextAnimatedWidget extends AnimatedWidget {
  TextAnimatedWidget({
    Key key,
    @required Animation<int> animation,
  }) : super(key: key, listenable: animation);

  @override
  Widget build(BuildContext context) {
    Animation<int> animation = listenable;
    printString(animation.value);
    return Text(
      "上面图片Hero动画",
      style: TextStyle(
        color: Color.fromARGB(animation.value, 1, 1, 1),
      ),
    );
  }
}

class _TsmAnimatedBuilderState extends State<TsmAnimatedBuilderPage> with SingleTickerProviderStateMixin{

  AnimationController  _controller;
  Animation<int> _animation;
  @override
  void initState() {
    _controller=AnimationController(duration: Duration(seconds: 1,),vsync: this);
    _animation=IntTween(begin: 0,end: 255,)
     .animate(CurvedAnimation(parent: _controller,curve: Interval(0.2,0.8,curve: Curves.easeOut)));
    _controller.addStatusListener((status) {
      if(_controller.status==AnimationStatus.completed){
        _controller.reverse();
      }else if(_controller.status==AnimationStatus.dismissed){
        _controller.forward();
      }
    });
    _controller.forward();
    super.initState();
  }




  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Animation 学习"),
        centerTitle: true,
      ),
      body: Container(
        child: Center(
          child: TextAnimatedWidget(animation: _animation,),
        ),
      ),
    );
  }
}

这里我们发现使用AnimatedWidget 可以把widget从动画里面分离出来,但是给widget 设置动画属性还是必须写在widget 里面,这样不是很友好,而且动画如果这么写有一个弊端,就是整个widget 可能部分需要动画,一部分不需要动画,那么不需要动画的部分也会跟着重建,这样不是很友好,

AnimatedBuilder

AnimatedBuilder 继承自 AnimatedWidget 他增加了一个build 方法,
源码对他的介绍是这样的,如果子树的返回值不是跟着动画而改变的,不用每次随着动画的刷新更新child,
这样的做法会更大限度的提升动画的流畅性
先来看一下类

class AnimatedBuilder extends AnimatedWidget {

  const AnimatedBuilder({
    Key key,
    @required Listenable animation,
    @required this.builder,
    this.child,
  }) : assert(animation != null),
       assert(builder != null),
       super(key: key, listenable: animation);

  /// Called every time the animation changes value.
  final TransitionBuilder builder;

  /// The child widget to pass to the [builder].
  ///
  /// If a [builder] callback's return value contains a subtree that does not
  /// depend on the animation, it's more efficient to build that subtree once
  /// instead of rebuilding it on every animation tick.
  ///
  /// If the pre-built subtree is passed as the [child] parameter, the
  /// [AnimatedBuilder] will pass it back to the [builder] function so that it
  /// can be incorporated into the build.
  ///
  /// Using this pre-built child is entirely optional, but can improve
  /// performance significantly in some cases and is therefore a good practice.
  final Widget child;

  @override
  Widget build(BuildContext context) {
    return builder(context, child);
  }
}

继承自 AnimatedWidget ,就是对有不需要变化的子widget做了一层封装,这样更流畅

例子

class _TsmAnimatedBuilderState extends State<TsmAnimatedBuilderPage> with SingleTickerProviderStateMixin{

  AnimationController  _controller;
  Animation<int> _animation;
  @override
  void initState() {
    _controller=AnimationController(duration: Duration(seconds: 1,),vsync: this);
    _animation=IntTween(begin: 0,end: 255,)
        .animate(CurvedAnimation(parent: _controller,curve: Interval(0.2,0.8,curve: Curves.easeOut)));
    _controller.addStatusListener((status) {
      if(_controller.status==AnimationStatus.completed){
        _controller.reverse();
      }else if(_controller.status==AnimationStatus.dismissed){
        _controller.forward();
      }
    });
    _controller.forward();
    super.initState();
  }




  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Animation 学习"),
        centerTitle: true,
      ),
      body: AnimatedBuilder(
        animation: _animation,
        builder: (context,child){
          return Container(
            child: child,
            color: Color.fromARGB(_animation.value, 255, 1, 1),
          );
        },
        child: Center(
          child: Text('渐变动画'),
        ),
      ),
    );
  }
}

效果图


GIF 2020-9-15 15-33-41.gif

这次是改变背景色,要是改变自己颜色和使用animatedWidget没啥区别了,

我学习flutter的整个过程都记录在里面了
https://www.jianshu.com/c/36554cb4c804

最后附上demo 地址

https://github.com/tsm19911014/tsm_flutter

上一篇下一篇

猜你喜欢

热点阅读