0
点赞
收藏
分享

微信扫一扫

【Flutter 专题】71 图解基本隐式动画 Widget #yyds干货盘点#

      小菜前段时间自定义 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)));
  2. reverseDuration 为切换反向动画时长;

    reverseDuration: Duration(milliseconds: 500),
  3. firstCurve / secondCurve 为两个 Widget 切换时动画效果;动画效果有多种,小菜不在此赘述;

    firstCurve: Curves.fastOutSlowIn,
    secondCurve: Curves.easeInExpo,
  4. alignment 为尺寸动画切换时对齐位置,当两个 Widget 大小不同时效果明显,小菜尝试了两种位置进行对比;
    
    alignment: Alignment.bottomRight,

alignment: Alignment.center,

![7104.gif](https://s2.51cto.com/images/20220221/1645403232568009.gif)

5. **sizeCurve** 为尺寸切换动画,当两个 **Widget** 大小不同时效果明显;

sizeCurve: Curves.easeInExpo,

sizeCurve: Curves.fastOutSlowIn,

![7105.gif](https://s2.51cto.com/images/20220221/1645403240224658.gif)

6. **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)
]);
}

![7106.gif](https://s2.51cto.com/images/20220221/1645403251994401.gif)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[**AnimatedCrossFade  源码**](https://github.com/ACE-YANGCE/FlutterApp/blob/master/lib/page/animated_cross_fade_page.dart)

### 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 布局构造器
})

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;分析源码可知,**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)));

![7107.gif](https://s2.51cto.com/images/20220221/1645403261587547.gif)

2. 小菜在切换过程中尝试不同的显示隐藏动画效果;

switchInCurve: Curves.easeInCubic,
switchOutCurve: Curves.fastLinearToSlowEaseIn,

switchInCurve: Curves.easeInExpo,
switchOutCurve: Curves.fastOutSlowIn,

![7108.gif](https://s2.51cto.com/images/20220221/1645403270847332.gif)

3. **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));
}));

![7109.gif](https://s2.51cto.com/images/20220221/1645403283139927.gif)

4. **child** 中 **old/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;
}

![7110.gif](https://s2.51cto.com/images/20220221/1645403292238378.gif)

5. **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;
}
}


![7111.gif](https://s2.51cto.com/images/20220221/1645403302700084.gif)

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[**AnimatedSwitcher  源码**](https://github.com/ACE-YANGCE/FlutterApp/blob/master/lib/page/animated_switcher_page.dart)
***
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;**Flutter** 还提供了很多灵活的隐式动画 **Widget**,小菜认为这两类最灵活,使用场景最多;小菜对隐式动画研究还不够深入,如有错误请多多指导!

>  来源: 阿策小和尚
举报

相关推荐

0 条评论