android 里面咱们就能够根据 layout 的变化设计本身的 latyout 切换动画,甚至矢量动画还能够进行 path 方面的无缝切换,好比从圆形天然过分到矩形html
Flutter 这里天然也响应提供了相关动画,可是区别确定仍是有的,首先 Flutter 这里名字就改了叫作:隐式动画
,我想说何须给 coder 找不自在呢,延续 android 的传统很差嘛~java
AnimatedSwitcher
- widget 内容改变时能够播放本身指定的动画AnimatedContainer
- 带动画的 Container,像 Container 一眼使用,在其中 color、width、height、圆角改变时会触发过分动画,动画不能控制,有些相似与 path 动画AnimatedCrossFade
- 切换不一样布局时能够显示动画,可是不能本身设置动画,默认就是淡入淡出,而且在大小不通切换时显示很差DecoratedBoxTransition
- 边框动画,核心是经过圆角角度的改变实现形状上的变化,这个变化是天然过分的,这点和 path 动画是同样了AnimatedDefaultTextStyle
- 文字样式改变时的切换动画,主要呈现的大小变换方面的动画,颜色的渐变过分不明显,可是体验很差的地方在于,大小字切换时字体粗细的变化真实有点辣眼,有点卡顿AnimatedModalBarrier
- 颜色改变的变换动画,特殊的地方在于其必须放到所操的 widget 的 child 中,有明确的应用场景,就是点击时改变背景色,好比 dialog 弹出时,背景变灰色AnimatedOpacity
- 透明度的变化动画AnimatedPhysicalModel
- 阴影变换动画,设置有些复杂AnimatedPositioned
- stack 中 widget 位置,大小变换动画AnimatedSize
- widget 大小变换动画AnimatedContainer
顾名思义就是带动画的 Container,属性设置使用和 Container 时如出一辙的,区别就是能够设置动画时间和插值器android
动画效果这块和 矢量动画
相似,均可以实现先后状态间的无缝切换,由系统完成动画每帧的数值输出。可是能作到 矢量动画
那种效果的属性只有:color
、width
、height
、圆角
,其余都不行,好比图片切换就是一下就切换了,shape 形状的切换,好比放大从圆形到矩形,圆形在达到最大时一瞬间会切换到矩形,矩形再慢慢放大缓存
AnimatedContainer
的使用套路就是,把属性值写在外部,经过 setState 改变属性值就能够实现切换动画了dom
这方面看我写的例子: ide
class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
double width = 50;
double height = 50;
Color color = Colors.blueAccent;
BoxShape shape = BoxShape.circle;
@override
Widget build(BuildContext context) {
// TODO: implement build
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedContainer(
duration: Duration(seconds: 1),
width: width,
height: height,
decoration: BoxDecoration(
color: color,
shape: shape,
),
),
RaisedButton(
child: Text("AAA"),
onPressed: () {
setState(() {
width = 300;
height = 300;
color = Colors.pink;
shape = BoxShape.rectangle;
});
},
),
],
);
}
}
复制代码
官方文档这里给出了一个很是好的例子,AnimatedContainer
能实现的极限都在这里面了,其中形状的改变是经过改变圆角矩形的角度实现的:BorderRadius.circular(8);
布局
class TestWidgetState extends State<TestWidget> with SingleTickerProviderStateMixin {
double _width = 50;
double _height = 50;
Color _color = Colors.green;
BorderRadiusGeometry _borderRadius = BorderRadius.circular(8);
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: AnimatedContainer(
// Use the properties stored in the State class.
width: _width,
height: _height,
decoration: BoxDecoration(
color: _color,
borderRadius: _borderRadius,
),
// Define how long the animation should take.
duration: Duration(seconds: 1),
// Provide an optional curve to make the animation feel smoother.
curve: Curves.fastOutSlowIn,
),
),
RaisedButton(
child: Icon(Icons.play_arrow),
// When the user taps the button
onPressed: () {
// Use setState to rebuild the widget with new values.
setState(() {
// Create a random number generator.
final random = Random();
// Generate a random width and height.
_width = random.nextInt(300).toDouble();
_height = random.nextInt(300).toDouble();
// Generate a random color.
_color = Color.fromRGBO(
random.nextInt(256),
random.nextInt(256),
random.nextInt(256),
1,
);
// Generate a random border radius.
_borderRadius =
BorderRadius.circular(random.nextInt(100).toDouble());
});
},
),
],
);
}
}
复制代码
最后你们注意啊,AnimatedContainer 动画只能直接操做 Container 自己的属性,child 里的子 widget 就管不了了字体
AnimatedSwitcher
是 Flutter 中提供的用于 widget 切换内容时得动画样式,目前看到只能支持同一个 widget 得内容变化,切换不一样类型的 widget 还在研究动画
AnimatedSwitcher
属性有几个:ui
child
- 内容切换动画做用于得 widgetduration
- 动画从 A -> B 的时间reverseDuration
- 动画反过来从 B -> A 的时间switchInCurve
- 动画从 A -> B 的动画插值器,Curves.linear
switchOutCurve
- 反过来得插值器transitionBuilder
- 动画layoutBuilder
- 包装新旧 Widget 的组件,默认是一个 Stack其中注意点是 key
,flutter widget 树自身有缓存的,同一个 widget 只是内容更新的话是不会重建该 widget 的,可是 AnimatedSwitcher
想要有动画出来,必须是2个 widget 才行的,因此咱们要手动设置 key 以规避 Flutter 中的 widget 缓存机制
网上这部份内容基本都出自官方文档:9.6 通用"切换动画"组件(AnimatedSwitcher)
官方文档没 gif,看不出效果,这里我帖一下效果和代码:
class TestWidgetState extends State<TestWidget> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
transitionBuilder: (Widget child, Animation<double> animation) {
//执行缩放动画
return ScaleTransition(child: child, scale: animation);
},
child: Text(
'$_count',
//显示指定key,不一样的key会被认为是不一样的Text,这样才能执行动画
key: ValueKey<int>(_count),
style: Theme.of(context).textTheme.display1,
),
),
RaisedButton(
child: const Text('+1',),
onPressed: () {
setState(() {
_count += 1;
});
},
),
],
),
);
}
}
复制代码
而后咱们再来一个 icon 切换的例子
class TestWidgetState extends State<TestWidget> {
IconData _actionIcon = Icons.delete;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedSwitcher(
transitionBuilder: (child, anim) {
return ScaleTransition(child: child, scale: anim);
},
duration: Duration(milliseconds: 200),
child: IconButton(
key: ValueKey(_actionIcon),
icon: Icon(_actionIcon),
),
),
RaisedButton(
child: Text("切换图标"),
onPressed: () {
setState(() {
if (_actionIcon == Icons.delete)
_actionIcon = Icons.done;
else
_actionIcon = Icons.delete;
});
},
),
],
),
);
}
}
复制代码
其实原理很简单,一说就明白。由于 AnimatedSwitcher 其中的 child widget 的key 不同,那么每次 child widget 内容变化时都会被认为是有新的 widget 出现,会从新 build,咱们在 didUpdateWidget 中拿到新久 widget,对旧 widget 执行反向动画,对新 widget 执行正向动画,就是这样,下面是源码截取:
void didUpdateWidget(AnimatedSwitcher oldWidget) {
super.didUpdateWidget(oldWidget);
// 检查新旧child是否发生变化(key和类型同时相等则返回true,认为没变化)
if (Widget.canUpdate(widget.child, oldWidget.child)) {
// child没变化,...
} else {
//child发生了变化,构建一个Stack来分别给新旧child执行动画
_widget= Stack(
alignment: Alignment.center,
children:[
//旧child应用FadeTransition
FadeTransition(
opacity: _controllerOldAnimation,
child : oldWidget.child,
),
//新child应用FadeTransition
FadeTransition(
opacity: _controllerNewAnimation,
child : widget.child,
),
]
);
// 给旧child执行反向退场动画
_controllerOldAnimation.reverse();
//给新child执行正向入场动画
_controllerNewAnimation.forward();
}
}
复制代码
AnimatedSwitcher 的特征你们应该都明白了,新旧 widget 之间会执行一个动画的前进和反转,奇特证就是从哪里来旧回到哪里,好比新文字从右边进来,那么老的文字就从右边出去,整体上动画的执行必须是顺序的
那么咱们能够实现本身想要的效果嘛,好比右边进,左边出。其实这样是能够的,AnimatedSwitcher 咱们也不用改,咱们能够改一改动画API FlideTransition,全部的动画都是在 AnimationWidget 基础上写的
针对这个需求,咱们仿照 FlideTransition 内部实现,把动画在反转时把 X 轴的值加个-
号就是咱们要的效果啦,大多数时候咱们都是用这种思路实现的
这个例子是官方文档上面的,代码上我多少改了一下,主要是在使用上更方便一点,封装度高了一点,针对 X/Y 轴作了封装
enum FreeSlideTransitionMode {
reverseX,
reverseY,
}
class FreeSlideTransition extends AnimatedWidget {
Animation<Offset> animation;
var child;
Offset begin;
Offset end;
FreeSlideTransitionMode type;
// x,y 轴反转播放时不一样的数据处理,用 map 承载
Map<FreeSlideTransitionMode, Function(Animation animation, Offset offset)> worksMap = {
// x 轴反转操做,典型应用,右进左出
FreeSlideTransitionMode.reverseX: (Animation animation, Offset offset) {
if (animation.status == AnimationStatus.reverse) {
return offset = Offset(-offset.dx, offset.dy);
}
return offset;
},
FreeSlideTransitionMode.reverseY: (Animation animation, Offset offset) {
if (animation.status == AnimationStatus.reverse) {
return offset = Offset(offset.dx, -offset.dy);
}
return offset;
},
};
// 构造方法的多态,写着有点麻烦,看起来也不省事
FreeSlideTransition(this.type, this.child,
{Key key, Animation<double> animation, Offset begin, Offset end})
: super(key: key, listenable: animation) {
this.animation = Tween<Offset>(begin: begin, end: end).animate(animation);
}
FreeSlideTransition.reverseX(
{Widget child,
Animation<double> animation,
Key key,
Offset begin,
Offset end})
: this(FreeSlideTransitionMode.reverseX, child,
key: key, animation: animation, begin: begin, end: end);
FreeSlideTransition.reverseY(
{Widget child,
Animation<double> animation,
Key key,
Offset begin,
Offset end})
: this(FreeSlideTransitionMode.reverseY, child,
key: key, animation: animation, begin: begin, end: end);
@override
Widget build(BuildContext context) {
var offset = animation.value;
offset = worksMap[type](animation, offset);
// SlideTranslation 内部就是这么实现的
return FractionalTranslation(
translation: offset,
child: child,
);
}
}
复制代码
class Test3 extends State<TestWidget> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
transitionBuilder: (Widget child, Animation<double> animation) {
//执行缩放动画
return FreeSlideTransition.reverseY(
animation: animation,
begin: Offset(0, 1),
end: Offset(0, 0),
child: child,
);
},
child: Text(
'$_count',
//显示指定key,不一样的key会被认为是不一样的Text,这样才能执行动画
key: ValueKey<int>(_count),
style: Theme.of(context).textTheme.display1,
),
),
// Padding(
// padding: EdgeInsets.all(20.0),
// ),
// Text("AAA"),
RaisedButton(
child: Text(
'+1',
),
onPressed: () {
setState(() {
_count += 1;
});
},
),
],
),
);
}
}
复制代码
好了就是这样,有的朋友可能看官方文档时挺简单的,可是仍是推荐你们本身写一下,文档的代码封装度不够,也有些繁琐,本身试试也能练习一下功能封装不是,至少我这个 FreeSlideTransition 是能够放到 lib 库里面的
有的朋友不理解为何还要在 AnimatedSwitcher 里面本身写 tween 呢。其实一开始我也是不理解的,后来一想啊,AnimationControl 的数值默认是 [0-1] 的,咱们要是想用本身的数据设置,可不就得本身写 Tween 啊
AnimatedCrossFade
不一样布局切换时能够显示动画,可是不能本身设置动画,默认就是淡入淡出,而且在大小不通切换时显示很差
我走一个 gif,让你们看看 widget 在大小不一样切换时那种强烈的突兀感,小变大还行,可是大变小就不行了
AnimatedCrossFade
惋惜不能本身设置动画,默认也就是个渐变更画,限制挺大的
代码上呢,须要咱们指定显示 frist widget 仍是 second widget,咱们在 widget 外部写一个标记,setState 改变这个标记就能触发动画了
class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
var isFristShow = true;
@override
Widget build(BuildContext context) {
// TODO: implement build
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedCrossFade(
firstChild: Container(
alignment: Alignment.center,
width: 200,
height: 200,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.blueAccent,
),
child: Text("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
),
secondChild: Container(
alignment: Alignment.center,
width: 300,
height: 300,
child: Text("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
decoration:
BoxDecoration(shape: BoxShape.rectangle, color: Colors.pinkAccent),
),
crossFadeState: isFristShow
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
duration: Duration(milliseconds: 300),
reverseDuration: Duration(milliseconds: 300),
),
RaisedButton(
child: Text("AAA"),
onPressed: () {
setState(() {
isFristShow = !isFristShow;
});
},
),
],
);
}
}
复制代码
DecoratedBoxTransition
是边框变化动画,只能进行边框变化的动画,但他是咱们一直所追求的,他能实现 widget 形状变化的天然过分,固然和咱们说 AnimatedContainer
时同样,起形状天然过分依然仍是依靠圆角矩形的圆角度数实现的
DecoratedBoxTransition 属性:
child
-decoration
- 表示由外传递进来的动画属性值的变化,经过获取其值,填充到child的边框上 position
- 表示边框动画的位置,能够是前景位置或者是背景位置,前景位置会盖住child元素DecoratedBoxTransition 的 child 不用设置背景,DecorationTween 数值生成器会自动给 child 加上设定的 shape 背景的
_animation = DecorationTween(
begin: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(0.0)),
color: Colors.red,
),
end: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(100.0)),
color: Colors.green,
),
)
复制代码
class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
Animation<Decoration> _animation;
AnimationController _controller;
Animation _curve;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,
);
_curve = CurvedAnimation(parent: _controller, curve: Curves.fastOutSlowIn);
_animation = DecorationTween(
begin: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(0.0)),
color: Colors.red,
),
end: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(100.0)),
color: Colors.green,
),
).animate(_curve)
..addStatusListener((AnimationStatus state) {
if (state == AnimationStatus.completed) {
_controller.reverse();
} else if (state == AnimationStatus.dismissed) {
_controller.forward();
}
});
_controller.forward();
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return Column(
children: <Widget>[
DecoratedBoxTransition(
position: DecorationPosition.background,
decoration: _animation,
child: Container(
child: Container(
padding: EdgeInsets.all(50),
child: Text("AAAAAA"),
)),
)
],
);
}
}
复制代码
AnimatedDefaultTextStyle
文字样式改变时的切换动画,主要呈现的大小变换方面的动画,颜色的渐变过分不明显,可是体验很差的地方在于,大小字切换时字体粗细的变化真实有点辣眼,尤为是文字字号大的时候
吐槽一下,Google 团队的代码质量也在降低啊,在前有 AnimatedContainer
的前提下,AnimatedDefaultTextStyle
和 AnimatedContainer
的设计,使用,命名套路彻底不同,Google 你是要闹哪样,有代码质量审查吗?AnimatedDefaultTextStyle
和 AnimatedContainer
他们二者实际上是一种东西,可是开发者之间没有沟通,搞出2种东西,真是用着蛋疼啊
AnimatedDefaultTextStyle
使用上做为 text 的外层 widget 来用的,其实搞成 AnimatedContainer
那样直接代替 text 多好,其实 text 那些属性大多数 AnimatedDefaultTextStyle
也有,搞得不乱不类的,真让人火大
动画的触发同样仍是经过外层变量控制,经过 staState 来触发
class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
var _isSelected = true;
var info1 = "Flutter !!!";
var info2 = "is not you !!!";
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
AnimatedDefaultTextStyle(
softWrap: false,
textAlign: TextAlign.right,
maxLines: 1,
overflow: TextOverflow.ellipsis,
curve: Curves.linear,
duration: Duration(milliseconds: 300),
child: Text( info2),
style: _isSelected
? TextStyle(
fontSize: 10.0,
color: Colors.red,
fontWeight: FontWeight.bold,
)
: TextStyle(
fontSize: 30.0,
color: Colors.black,
fontWeight: FontWeight.w300,
),
),
RaisedButton(
child: Text("AA"),
onPressed: () {
setState(() {
_isSelected = !_isSelected;
});
},
),
],
);
}
}
复制代码
文字如果切换先后行数不同的话,动画就比较难看了,你们看下面这个例子,在用的时候你们切记一行变多行
AnimatedModalBarrier
颜色改变的变换动画,特殊的地方在于其必须放到所操的 widget 的 child 中,有明确的应用场景,就是点击时改变背景色,好比 dialog 弹出时,背景变灰色AnimatedModalBarrier
有几个参数,除了 color
外具体有啥用我也不知道:
color
- 颜色值动画变化dismissible
- 是否触摸当前ModalBarrier将弹出当前路由,配合点击事件弹出路由使用semanticsLabel
- 语义化标签barrierSemanticsDismissible
- 语义树中是否包括ModalBarrier语义color
这里接收一个 animation 动画对象,这样的话咱们能够本身设置先后颜色值,时长,添加控制等,API 自由度比其余同类变换动画 API 强多了
下面的例子里我设置了一个循环播放
class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation _curve;
Animation<Color> animation;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 1000),
vsync: this,
)
..addStatusListener((AnimationStatus state) {
if (state == AnimationStatus.completed) {
_controller.reverse();
} else if (state == AnimationStatus.dismissed) {
_controller.forward();
}
});
animation = ColorTween(
begin: Colors.blue,
end: Colors.pinkAccent,
).animate(_controller);
}
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
fit: StackFit.expand,
children: <Widget>[
Container(
width: 300,
height: 300,
child: AnimatedModalBarrier(
semanticsLabel: "StackBarrier",
barrierSemanticsDismissible: true,
dismissible: true,
color: animation,
),
),
Positioned(
left: 20,
top: 20,
child: RaisedButton(
child: Text("AA"),
onPressed: () {
_controller.forward();
},
),
),
],
);
}
}
复制代码
AnimatedOpacity
透明度的变化动画,没什么可说的,看代码就是,下面我把颜色变换的动画一块儿加进来
class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<Color> animation;
double opacity = 1.0;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
);
animation = ColorTween(
begin: Colors.blue,
end: Colors.pinkAccent,
).animate(_controller);
}
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
fit: StackFit.expand,
children: <Widget>[
AnimatedOpacity(
curve: Curves.fastOutSlowIn,
opacity: opacity,
duration: Duration(seconds: 1),
child: Container(
width: 300,
height: 300,
child: AnimatedModalBarrier(
semanticsLabel: "StackBarrier",
barrierSemanticsDismissible: true,
dismissible: true,
color: animation,
),
),
),
Positioned(
left: 20,
top: 20,
child: RaisedButton(
child: Text("AA"),
onPressed: () {
setState(() {
opacity = 0.3;
_controller.forward();
});
},
),
),
],
);
}
}
复制代码
AnimatedPhysicalModel
属性以下,有点多,你们仔细看,看不懂的看下面 demo 就行:
shape
:阴影的形状clipBehavior
:阴影的裁剪方式
Clip.none
:无模式Clip.hardEdge
:裁剪速度稍快,但容易失真,有锯齿Clip.antiAlias
:裁剪边缘抗锯齿,使得裁剪更平滑,这种模式裁剪速度比antiAliasWithSaveLayer快,可是比hardEdge慢Clip.antiAliasWithSaveLayer
:裁剪后具备抗锯齿特性并分配屏幕缓冲区,全部后续操做在缓冲区进行borderRadius
:背景的边框elevation
:阴影颜色值的深度color
:背景色animateColor
:背景色是否用动画形式展现shadowColor
:阴影的动画值animateShadowColor
:阴影是否用动画形式展现看着不少,可是你们不要懵,其实就一个有用,就是 shadowColor
,咱们改 shadowColor
就会触发动画,不过 color
这个属性必须设置,要不报错
总体动画来讲,我是真不知道这个动画应用在哪里,是否是像上面那个同样,作点击后背景色的变化呢,谁知道呢...
class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
var isShadow = true;
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
fit: StackFit.loose,
children: <Widget>[
AnimatedPhysicalModel(
curve: Curves.fastOutSlowIn,
color: Colors.grey.withOpacity(0.2),
clipBehavior: Clip.antiAliasWithSaveLayer,
borderRadius: BorderRadius.circular(12.0),
animateColor: true,
animateShadowColor: true,
shape: BoxShape.rectangle,
shadowColor: isShadow ? _shadowColor1 : _shadowColor2,
elevation: 5.0,
duration: Duration(milliseconds: 300),
child: Container(
width: 200,
height: 200,
child: Text("AA"),
),
),
Positioned(
left: 20,
top: 20,
child: RaisedButton(
child: Text("AA"),
onPressed: () {
setState(() {
isShadow = !isShadow;
});
},
),
),
],
);
}
}
复制代码
AnimatedPositioned
这个你们看名字就知道了,就是严格按照以前的 API 命名的,这样才好嘛,这样才会一看就知道是干啥的,这里再次 diss 一下其余垃圾的起名和设计
这个我不想说了,你们看代码都清楚
class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
var isPosition = true;
var top1 = 20.0;
var left1 = 20.0;
var width1 = 200.0;
var height1 = 200.0;
var top2 = 100.0;
var left2 = 100.0;
var width2 = 300.0;
var height2 = 300.0;
@override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
fit: StackFit.expand,
children: <Widget>[
AnimatedPositioned(
top: isPosition ? top1 : top2,
left: isPosition ? left1 : left2,
width: isPosition ? width1 : width2,
height: isPosition ? height1 : height2,
child: Container(
color: Colors.blueAccent,
),
duration: Duration(milliseconds: 300),
),
Positioned(
left: 20,
top: 20,
child: RaisedButton(
child: Text("AA"),
onPressed: () {
setState(() {
isPosition = !isPosition;
});
},
),
),
],
);
}
}
复制代码
上面看了这么多了,到这你们啥套路都知道了吧,就是效果不是很满意,看下面的 gif,你们能看到如果由于 widget 的大小变化而形成 widget 有位移的话,那么会进行位移动画,而大小就没动画了,这点挺不爽的
class Test3 extends State<TestWidget> with SingleTickerProviderStateMixin {
double size1 = 200;
double size2 = 300;
var isSize = true;
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
RaisedButton(
child: Text("AA"),
onPressed: () {
setState(() {
isSize = !isSize;
});
},
),
AnimatedSize(
alignment: Alignment.center,
curve: Curves.fastOutSlowIn,
vsync: this,
duration: Duration(seconds: 1),
reverseDuration: Duration(seconds: 2),
child: Container(
width: isSize ? size1 : size2,
height: isSize ? size1 : size2,
color: Colors.blueAccent,
),
),
],
);
}
}
复制代码