AnimatedWidget
還記得 上一節 裡面是怎麼更新 widget 的狀態的嗎?我們上次的步驟是:首先建立動畫,然後給動畫新增監聽 addListener(...)
, 在 addListener(...)
方法中我們幹了件 很重要 的事兒:setState((){})
,因為只有呼叫這個,才會讓 widget 重繪。
這一次我們使用 AnimatedWidget
來實現動畫,使用它就不需要給動畫 addListener(...)
和 setState((){})
了,AnimatedWidget
自己會使用當前 Animation
的 value
來繪製自己。當然,這裡 Animation
我們是以構造引數的方式傳遞進去的。
看程式碼:
class AnimatedContainer extends AnimatedWidget {
AnimatedContainer({Key key, Animation<double> animation})
: super (key: key, listenable: animation);
@override
Widget build(BuildContext context) {
final Animation<double> animation = listenable;
return Center(
child: Container(
decoration: BoxDecoration(
color: Colors.redAccent
),
margin: EdgeInsets.symmetric(vertical: 10.0),
height: animation.value,
width: animation.value,
),
);
}
}
複製程式碼
上述程式碼中,我們定義了一個 AnimatedContainer
繼承了 AnimatedWidget
,然後定義了一個構造方法,注意,構造方法中我們定義了一個 Animation
然後把這個 Animation
傳到父類(super)中去了,我們可以看看 listenable: animation
這個引數,是一個 Listenable
型別,如下:
/// The [Listenable] to which this widget is listening.
///
/// Commonly an [Animation] or a [ChangeNotifier].
final Listenable listenable;
複製程式碼
然後再看看 Animation 類:
abstract class Animation<T> extends Listenable implements ValueListenable<T> {
...
}
複製程式碼
可以看到 Animation
是 Listenable
的子類,所以在我們自定義的 AnimatedContainer
類中可以傳一個 Animation
型別的的引數作為父類中 listenable
的值。
使用我們上面定義的 AnimatedContainer
也很簡單,直接作為 widget
使用就好,部分程式碼如下:
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'AnimatedWidgetDemo',
theme: ThemeData(
primaryColor: Colors.redAccent
),
home: Scaffold(
appBar: AppBar(
title: Text('AnimatedWidgetDemo'),
),
body: AnimatedContainer(animation: animation,)
),
);``
}
複製程式碼
可以看出我們在例項化 AnimatedContainer
的時候傳入了一個 Animation
物件。
效果如下:
AnimatedBuilder
我們先看看如何使用 AnimatedBuilder
做一個上面一樣的效果
部分程式碼如下:
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'AnimatedBuilderExample',
theme: ThemeData(primaryColor: Colors.redAccent),
home: Scaffold(
appBar: AppBar(
title: Text('animatedBuilderExample'),
),
body: Center(
child: AnimatedBuilder(
animation: _animation,
child: Container(
decoration: BoxDecoration(color: Colors.redAccent),
),
builder: (context, child) {
return Container(
width: _animation.value,
height: _animation.value,
child: child,
);
},
),
),
),
);
}
複製程式碼
因為 AnimatedBuilder
是繼承於 AnimatedWidget
的,
class AnimatedBuilder extends AnimatedWidget { ... }
複製程式碼
所以可以直接把 AnimatedBuilder
當作 widget
使用
上述程式碼關鍵部分如下:
body: Center(
child: AnimatedBuilder(
animation: _animation,
child: Container(
decoration: BoxDecoration(color: Colors.redAccent),
),
builder: (context, child) {
return Container(
width: _animation.value,
height: _animation.value,
child: child,
);
},
),
),
複製程式碼
效果如下:
builder
這個匿名類是每次動畫值改變的時候就會被呼叫
AnimatedBuilder 使用簡化後的結構如下:
AnimatedBuilder(
animateion: ... ,
child: ... ,
builder: (context, child) {
return Container(
width: ... ,
height: ... ,
child: child
)
}
)
複製程式碼
上述程式碼看著可能會有迷糊的地方,裡面的 child
好像被指定了兩次,外面一個,裡面一個。其實,外面的 child
是傳給 AnimatedBuilder
的,而 AnimatedBuilder
又將這個 child
作為引數傳遞給了裡面的匿名類(builder
)。
我們可以驗證上述說明,就是給外面的 child
指定一個 key
,然後在 builder
裡面列印出引數 child
的 key
。
body: Center(
child: AnimatedBuilder(
animation: _animation,
child: Container(
decoration: BoxDecoration(color: Colors.redAccent),
key: Key("android"),
),
builder: (context, child) {
print("child.key: ${child.key}");
return Container(
width: _animation.value,
height: _animation.value,
child: child,
);
},
),
),
複製程式碼
我們在外面的 child
裡面的新增了一個 key
值,然後在 builder
裡面列印出引數 child
的 key
值
輸出如下:
flutter: child.key: [<'android'>]
複製程式碼
區別
我們來看看 AnimatedBuilder
AnimatedWidget
和新增 addListener{}
監聽並在裡面觸發 setState(...)
這三種方式做動畫有什麼區別。
為了更直觀的看出它們的區別,這裡使用一個第三方控制元件:RandomContainer
,這個控制元件會在螢幕每次重繪的時候改變自身的顏色。
首先在pubspec.yaml中新增依賴 random_pk: any
,如下:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
# RandomContainer
random_pk: any
複製程式碼
先寫一個小例子來看看 RandomContainer
這個控制元件每次在螢幕重繪的時候自身顏色改變的情況。
在螢幕上繪製一個寬高都為200.0
的 RandomContainer
然後給 RandomContainer
新增點選事件,點選事件裡面做的就是呼叫 setState(...)
讓 widget 重繪,部分程式碼如下:
body: Center(
child: GestureDetector(
onTap: _changeColor,
child: RandomContainer(
width: 200.0,
height: 200.0,
),
),
),
複製程式碼
使用
RandomContainer
的時候需要引入import 'package:random_pk/random_pk.dart';
點選事件程式碼如下:
void _changeColor() {
setState(() {});
}
複製程式碼
效果如下:
接下來我們使用三種方式實現同一種效果來看看有什麼不同:
AnimatedWidget
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 5))
..repeat();
複製程式碼
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'AnimatedBuilder',
theme: ThemeData(primaryColor: Colors.redAccent),
home: Scaffold(
appBar: AppBar(
title: Text('AnimatedBuilder'),
),
body: Center(
child: RandomContainer(
width: 200.0,
height: 200.0,
child: AnimatedWidgetDemo( // new
animation: _controller,
),
),
),
),
);
}
複製程式碼
效果如下:
AnimatedBuilder
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 5))
..repeat();
複製程式碼
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'AnimatedBuilder',
theme: ThemeData(primaryColor: Colors.redAccent),
home: Scaffold(
appBar: AppBar(
title: Text('AnimatedBuilder'),
),
body: Center(
child: RandomContainer(
width: 200.0,
height: 200.0,
child: AnimatedBuilderDemo( // new
child: getContainer(),
animation: _controller,
),
),
),
),
);
}
複製程式碼
AnimatedBuilder 的效果和 AnimatedWidget 的效果是一樣的。
接下來我們看看在 addListener{}
裡面呼叫 setState(...)
的效果,也就是我們在上一節中實現動畫的方式
_controller =
AnimationController(vsync: this, duration: Duration(seconds: 5))
..repeat()
..addListener(() {
setState(() {});
});
複製程式碼
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'AnimatedBuilder',
theme: ThemeData(primaryColor: Colors.redAccent),
home: Scaffold(
appBar: AppBar(
title: Text('AnimatedBuilder'),
),
body: Center(
child: RandomContainer(
width: 200.0,
height: 200.0,
child: Transform.rotate( // new
child: getContainer(),
angle: _controller.value * 2.0 * pi,
),
),
),
),
);
}
複製程式碼
效果如下
看出來了吧。。。
如有錯誤,還請指出,謝謝!