Flutter 71: 图解基本隐式动画 Widget

作者: 阿策神奇 | 来源:发表于2019-12-20 18:52 被阅读0次

          小菜前段时间自定义 ACEStepper 步进器时,在 ACEStep 中尝试过 AnimatedCrossFade 用于在两个 Widget 切换过度,简单实用,今天小菜重点学习一下并尝试相关隐式动画 Widget

    AnimatedCrossFade 淡入淡出动画

    源码分析

    const AnimatedCrossFade({
        Key key,
        @required this.firstChild,                  // 首个展示 Widget
        @required this.secondChild,                 // 第二展示 Widget
        this.firstCurve = Curves.linear,            // 首个 Widget 展示动画
        this.secondCurve = Curves.linear,           // 第二 Widget 展示动画
        this.sizeCurve = Curves.linear,             // 切换时尺寸动画
        this.alignment = Alignment.topCenter,       // 对齐方式
        @required this.crossFadeState,              // 切换状态(是否切换)
        @required this.duration,                    // 切换动画时长
        this.reverseDuration,                       // 切换反向动画时长
        this.layoutBuilder = defaultLayoutBuilder,  // Widget 布局构造器
    })
    

          分析源码可知,AnimatedCrossFade 可以在指定时间内从一个 Widget 到另一个 Widget 的平滑过渡或反向过渡;其中切换状态和时长是必要属性;

    案例尝试

    1. 小菜尝试一个基本的动画过程,两个方块之间进行切换;
    return GestureDetector(
        onTap: () { setState(() => isChanged = !isChanged); },
        child: Container(
            child: AnimatedCrossFade(
                firstChild: Container(width: 100, height: 100, color: Colors.purpleAccent.withOpacity(0.4)),
                secondChild: Container(width: 200, height: 200, color: Colors.blueGrey.withOpacity(0.4)),
                duration: Duration(milliseconds: 1500),
                crossFadeState: isChanged ? CrossFadeState.showSecond : CrossFadeState.showFirst)));
    
    1. reverseDuration 为切换反向动画时长;
    reverseDuration: Duration(milliseconds: 500),
    
    1. firstCurve / secondCurve 为两个 Widget 切换时动画效果;动画效果有多种,小菜不在此赘述;
    firstCurve: Curves.fastOutSlowIn,
    secondCurve: Curves.easeInExpo,
    
    1. alignment 为尺寸动画切换时对齐位置,当两个 Widget 大小不同时效果明显,小菜尝试了两种位置进行对比;
    alignment: Alignment.bottomRight,
    
    alignment: Alignment.center,
    
    1. sizeCurve 为尺寸切换动画,当两个 Widget 大小不同时效果明显;
    sizeCurve: Curves.easeInExpo,
    
    sizeCurve: Curves.fastOutSlowIn,
    
    1. layoutBuilder 为布局构造器,这个是小菜认为最值得研究的地方,构造器并不陌生,但在这里的作用却比较特殊,通过 Stack 将两个 Widget 层级叠放,底部 Widget 默认尺寸位置以上层 Widget 为基准,默认 Position 边距均为 0.0;我们可以自定义调整动画起始位置;
    // 默认
    static Widget defaultLayoutBuilder(Widget topChild, Key topChildKey, Widget bottomChild, Key bottomChildKey) {
      return Stack(
          overflow: Overflow.visible,
          children: <Widget>[
            Positioned(key: bottomChildKey, left: 0.0, top: 0.0, right: 0.0, child: bottomChild),
            Positioned(key: topChildKey, child: topChild)
          ]);
    }
    // 调整 Position 位置
    layoutBuilder: (topChild, topChildKey, bottomChild, bottomChildKey) {
      return Stack(children: <Widget>[
        Positioned(key: bottomChildKey, left: 50.0, top: 50.0, right: 50.0, bottom: 50.0, child: bottomChild),
        Positioned(key: topChildKey, child: topChild)
      ]);
    }
    

          AnimatedCrossFade 源码

    AnimatedSwitcher 切换动画

    源码分析

    const AnimatedSwitcher({
        Key key,
        this.child,
        @required this.duration,                // 切换动画时长
        this.reverseDuration,                   // 反向切换动画时长
        this.switchInCurve = Curves.linear,     // 切换显示时动画曲线
        this.switchOutCurve = Curves.linear,    // 切换隐藏时动画曲线
        this.transitionBuilder = AnimatedSwitcher.defaultTransitionBuilder,  // Widget 动画构造器
        this.layoutBuilder = AnimatedSwitcher.defaultLayoutBuilder,          // Widget 布局构造器
    })
    

          分析源码可知,AnimatedSwitcher 更加灵活,可自由设置切换动画之间显示隐藏动画效果;当 child Widget 内容或 Key 有变更时,old child 会执行隐藏动画,new child 会执行展现动画;

    案例尝试

    1. 小菜尝试切换两个基本的方块,但刚开始切换动画时长和反向切换动画时长没有效果,两个 Widget 只有参数更新,动画效果未执行;小菜尝试加入 Key 区分之后正常;
    return GestureDetector(
        onTap: () => setState(() => isChanged = !isChanged),
        child: AnimatedSwitcher(
            duration: Duration(milliseconds: 500),
            reverseDuration: Duration(milliseconds: 1500),
            child: isChanged
                ? Container(key: UniqueKey(), color: Colors.purpleAccent.withOpacity(0.4),  width: 100, height: 100)
                : Container(key: UniqueKey(), color: Colors.green.withOpacity(0.4), width: 150, height: 120)));
    
    1. 小菜在切换过程中尝试不同的显示隐藏动画效果;
    switchInCurve: Curves.easeInCubic,
    switchOutCurve: Curves.fastLinearToSlowEaseIn,
    
    switchInCurve: Curves.easeInExpo,
    switchOutCurve: Curves.fastOutSlowIn,
    
    1. transitionBuilder 为动画构造器,可以自定义动画效果;小菜尝试了两种简单的缩放动画和平移动画,暂未尝试复杂动画;且动画属性与显示隐藏的 switchInCurve / switchOutCurve 动画曲线共同展示效果;
    // 缩放动画效果
    return GestureDetector(
        onTap: () => setState(() => isChanged = !isChanged),
        child: AnimatedSwitcher(
            duration: Duration(milliseconds: 500),
            reverseDuration: Duration(milliseconds: 1500),
            switchInCurve: Curves.easeInCubic,
            switchOutCurve: Curves.fastLinearToSlowEaseIn,
            child: isChanged
                ? Container(key: UniqueKey(), color: Colors.purpleAccent.withOpacity(0.4), width: 100, height: 100)
                : Container(key: UniqueKey(), color: Colors.green.withOpacity(0.4), width: 150, height: 120),
            transitionBuilder: (Widget child, Animation<double> animation) {
              return ScaleTransition(scale: animation, child: child);
            }));
            
    // 平移动画效果
    return GestureDetector(
        onTap: () => setState(() => isChanged = !isChanged),
        child: AnimatedSwitcher(
            duration: Duration(milliseconds: 500),
            reverseDuration: Duration(milliseconds: 1500),
            switchInCurve: Curves.easeInExpo,
            switchOutCurve: Curves.fastOutSlowIn,
            child: isChanged
                ? Container(key: UniqueKey(), color: Colors.purpleAccent.withOpacity(0.4), width: 100, height: 100)
                : Container(key: UniqueKey(), color: Colors.green.withOpacity(0.4), width: 150, height: 120),
            transitionBuilder: (Widget child, Animation<double> animation) {
              return SlideTransition(child: child, position: Tween<Offset>(begin: Offset(1, 0), end: Offset(0, 0)).animate(animation));
            }));
    
    1. childold/new Widget 一般是以 Stack 层级存储,在动画过程中两个 Widget 均要展示,可以通过 layoutBuilder 布局构造器进行自定义;小菜尝试调整对齐方式和只展示 current Widget 动画效果;
    // 调整层级对齐方式
    layoutBuilder: (Widget currentChild, List<Widget> previousChildren) {
      return Stack(children: <Widget>[
        ...previousChildren,
        if (currentChild != null) currentChild
      ], alignment: Alignment.bottomRight);
    }
                
    // 只展示当前 Widget 动画效果
    layoutBuilder: (Widget currentChild, List<Widget> previousChildren) {
        return currentChild;
    }
    
    1. AnimatedSwitcher 可以设置多个 Widget 平滑切换,相对于 AnimatedCrossFade 可扩展性更高;小菜尝试三个 Widget 平移切换;
    return GestureDetector(
        onTap: () => setState(() {
              ++index;
              index = index % 3;
            }),
        child: AnimatedSwitcher(
            duration: Duration(milliseconds: 500),
            child: _animatedItemWid(index),
            transitionBuilder: (Widget child, Animation<double> animation) {
              return SlideTransition(child: child, position: Tween<Offset>(begin: Offset(1, 0), end: Offset(0, 0)).animate(animation));
            }));
    
    Widget _animatedItemWid(index) {
      switch (index) {
        case 0:
          return Container(key: UniqueKey(), color: Colors.purpleAccent.withOpacity(0.4), width: 100, height: 100);
          break;
        case 1:
          return Container(key: UniqueKey(), color: Colors.green.withOpacity(0.4), width: 150, height: 120);
          break;
        case 2:
          return Container(key: UniqueKey(), color: Colors.orange.withOpacity(0.4), width: 120, height: 140);
          break;
      }
    }
    

          AnimatedSwitcher 源码


          Flutter 还提供了很多灵活的隐式动画 Widget,小菜认为这两类最灵活,使用场景最多;小菜对隐式动画研究还不够深入,如有错误请多多指导!

    来源: 阿策小和尚

    相关文章

      网友评论

        本文标题:Flutter 71: 图解基本隐式动画 Widget

        本文链接:https://www.haomeiwen.com/subject/lsiinctx.html