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,
),
),
],
);
}
}
複製代碼