如果沒有看過之前兩節(jié)的,建議先看看前兩節(jié)的內(nèi)容
上一節(jié)中我們主要做的事情如下:
- 在 Animation 的
addListener(...)
中調(diào)用setState(...)
來給 widget 添加動畫 - 使用
AnimatedWidget
的方法來給 widget 添加動畫
上一節(jié)中我們也通過案例的形式展現(xiàn)出以上兩種方式的區(qū)別,得出的結(jié)論就是如下:
使用 addListener(...)
和 setState(...)
給 Widget
添加動畫的時候挺邀,會導(dǎo)致其他的 Widget
也跟著重繪蛉拙,而使用 AnimatedWidget
的方式給 Widget
添加動畫的時候只會重繪當(dāng)前的 Widget
這是為什么呢 岛请?压怠?掐场?
我們先來看看動畫的執(zhí)行流程躯保。
我們在使用動畫的時候會初始化一個 AnimationController
AnimationController controller = AnimationController(vsync: this, duration: Duration(milliseconds: 300));
可以看出旋膳,初始化 AnimationController
需要傳遞兩個參數(shù),一個是 vsync
一個是 duration
途事,這個 duration
很容易理解验懊,就是動畫執(zhí)行完畢需要的時長擅羞,那 vsync
是什么啊义图?上述代碼中我們傳入了 this
祟滴,那是因?yàn)槲覀冊?State
類 with(混入,第一節(jié) 有講) 了 SingleTickerProviderStateMixin
歌溉,下面我們看看初始化 AnimationController
時都干了什么事垄懂,源碼如下:
AnimationController({
double value,
this.duration,
this.debugLabel,
this.lowerBound: 0.0,
this.upperBound: 1.0,
@required TickerProvider vsync,
}) : assert(lowerBound != null),
assert(upperBound != null),
assert(upperBound >= lowerBound),
assert(vsync != null),
_direction = _AnimationDirection.forward {
_ticker = vsync.createTicker(_tick);
_internalSetValue(value ?? lowerBound);
}
我們抽重點(diǎn)出來看
_ticker = vsync.createTicker(_tick);
這里調(diào)用了 vsync
的 createTicker(...)
方法創(chuàng)建了一個 Ticker
, 而我們在初始化 AnimationController
的時候 vsync
參數(shù)傳的是 SingleTickerProviderStateMixin
痛垛,SingleTickerProviderStateMixin#createTicker(...)
的源碼如下:
Ticker createTicker(TickerCallback onTick) {
...
_ticker = new Ticker(onTick, debugLabel: 'created by $this');
...
return _ticker;
}
這個 Ticker
是個什么東西呢草慧?看看它的注釋吧!以下為 Ticker
類的部分注釋
Calls its callback once per animation frame.
When created, a ticker is initially disabled. Call [start] to enable the ticker.
Creates a ticker that will call the provided callback once per frame while running.
- 第一句的大概意思就是 Ticker 類在每一幀動畫中都會調(diào)用他自己的回調(diào)一次
- 第二句的大概意思就是 當(dāng) Ticker 被創(chuàng)建之后匙头,默認(rèn)是 不可用(disabled)的漫谷,可以調(diào)用
start
方法來使 Ticker 變得可用(enable) - 第三句的大概意思就是 創(chuàng)建一個 Ticker,他將在運(yùn)行時的每一幀都會調(diào)用一次所提供的回調(diào)
創(chuàng)建 Ticker
到這兒就完了蹂析。
如何讓Ticker
可用呢舔示,也就是如何調(diào)用它的 start
方法呢?
我們在啟動動畫的時候會調(diào)用 AnimationController#forward()
电抚, 在 forward()
方法中就間接的會調(diào)用到 Ticker#start()
惕稻, Ticker#start()
最后會調(diào)用到 scheduleFrame()
,而這個方法里面調(diào)用了 ui.window.scheduleFrame();
蝙叛,這個 scheduleFrame()
是一個 Native 方法俺祠,如下:
void scheduleFrame() native 'Window_scheduleFrame';
上面的方法應(yīng)該就是調(diào)度屏幕刷新的(如有錯誤,請指出借帘,謝謝)蜘渣。
到這里,Ticker
也就 start
了肺然,然后 Ticker 就會在動畫的每一幀去調(diào)一次自己的回調(diào)蔫缸,這個回調(diào)
是在 AnimationController
中的構(gòu)造方法中創(chuàng)建 Ticker
的時候傳入的 _ticker = vsync.createTicker(_tick);
我們來看看 _tick
部分源碼:
void _tick(Duration elapsed) {
...
notifyListeners();
_checkStatusChanged();
}
這里面調(diào)用了 notifyListeners()
和 _checkStatusChanged()
,我們先來看看 notifyListeners()
际起,部分源碼如下:
void notifyListeners() {
final List<VoidCallback> localListeners = new List<VoidCallback>.from(_listeners);
for (VoidCallback listener in localListeners) {
try {
if (_listeners.contains(listener))
// 執(zhí)行回調(diào)
listener();
} catch (exception, stack) {
...
}
}
}
可以看出拾碌,notifyListeners()
中有一個 VoidCallback
的集合,然后遍歷這個集合加叁,并且執(zhí)行集合中所有的回調(diào)倦沧。
通常我們會使用 .addListener((){...})
給動畫添加監(jiān)聽,我們在調(diào)用addListener((){...})
之后會執(zhí)行如下源碼
void addListener(VoidCallback listener) {
didRegisterListener();
_listeners.add(listener);
}
將我們傳入的回調(diào)方法添加到_listeners
集合中它匕,也就是上面 notifyListeners()
方法中遍歷的集合。
這里我們來小結(jié)一下動畫的執(zhí)行流程
- 創(chuàng)建動畫
- 創(chuàng)建 Ticker窖认,并 傳入回調(diào)豫柬,回調(diào)里面會執(zhí)行
notifyListeners()
方法告希,此方法中會去遍歷_listeners
集合,并執(zhí)行集合中的每一個回調(diào)方法
- 創(chuàng)建 Ticker窖认,并 傳入回調(diào)豫柬,回調(diào)里面會執(zhí)行
- 給動畫添加監(jiān)聽
- 這里監(jiān)聽時傳入的回調(diào)會被添加到
_listeners
集合中去
- 這里監(jiān)聽時傳入的回調(diào)會被添加到
- 啟動動畫
- 會調(diào)用
ticker.srtart()
來啟動Ticker
烧给,然后Ticker
在動畫的每一幀都會 執(zhí)行回調(diào)燕偶,也就是我們在第一步中傳入的
- 會調(diào)用
接下來就是我們在 addListener((){...})
中干的事情了,通常會這樣
_controller.addListener(() {
setState(() {
});
});
也就是我們會在傳入的回調(diào)方法中去執(zhí)行 setState((){})
方法础嫡,我們可以看看文檔:
setState(VoidCallback fn)
部分文檔如下:
Notify the framework that the internal state of this object has changed.
Calling [setState] notifies the framework that the internal state of this
object has changed in a way that might impact the user interface in this
subtree, which causes the framework to schedule a [build] for this [State]
object.
大概意思就是:通知 framework
當(dāng)前對象的內(nèi)部狀態(tài)發(fā)生改變指么,這個操作會導(dǎo)致再次調(diào)用當(dāng)前 State 對象的 build 方法,相當(dāng)于重繪當(dāng)前對象榴鼎。
結(jié)合之前的分析伯诬,整個動畫過程就是,當(dāng)動畫啟動之后巫财,會不停的去執(zhí)行addListener((){...})
中的回調(diào)盗似,而我們在監(jiān)聽回調(diào)中又調(diào)用了setState(...)
方法,所以這就導(dǎo)致當(dāng)前對象不停的重繪平项,也就出現(xiàn)了屏幕上的動畫的效果赫舒。
回到之前的問題:
使用 addListener(...) 和 AnimatedWidget 為什么會出現(xiàn)那種區(qū)別呢?
其實(shí)我們來看下 AnimatedWidget
的源碼立馬就明白了
abstract class AnimatedWidget extends StatefulWidget {
const AnimatedWidget({
Key key,
@required this.listenable
}) : assert(listenable != null),
super(key: key);
final Listenable listenable;
@protected
Widget build(BuildContext context);
@override
_AnimatedState createState() => new _AnimatedState();
...
}
我們可以看到 AnimatedWidget
繼承自 StatefulWidget
闽瓢, 而 StateFulWidget
在初始化的時候會去創(chuàng)建一個 State
對象來管理自身的狀態(tài)接癌,也就是回去執(zhí)行 createState()
方法。
其實(shí)到這里已經(jīng)可以解釋兩種動畫方式存在的區(qū)別扣讼,因?yàn)?AnimatedWidget
內(nèi)部也有一個 State
對象來管理這自身的狀態(tài)扔涧,而我們之前通過查看文檔也知道一個 State
對象只會維護(hù)當(dāng)前對象的狀態(tài),所以即使重繪届谈,也只會導(dǎo)致當(dāng)前 State
對象的重繪枯夜,而不會導(dǎo)致其他 State
對象的重繪。
我們來繼續(xù)看看 AnimatedWidget
中的 createState()
干了什么事艰山,它里面調(diào)用了_AnimatedState()
方法湖雹,部分源碼如下:
class _AnimatedState extends State<AnimatedWidget> {
@override
void initState() {
super.initState();
widget.listenable.addListener(_handleChange);
}
...
@override
void dispose() {
widget.listenable.removeListener(_handleChange);
super.dispose();
}
void _handleChange() {
setState(() {
// The listenable's state is our build state, and it changed already.
});
}
@override
Widget build(BuildContext context) => widget.build(context);
明白了吧!J锇帷摔吏!
_AnimatedState
在 initState
中給 widget
的 listenable
添加了監(jiān)聽,這里的listenable
就是我們在初始化一個 AnimatedWidget
是傳入的 Animation
對象纵装。
而 addListener
中傳入的回調(diào)是 _handleChange()
征讲, 在 _handleChange()
中同樣調(diào)用了 setState(...)
來觸發(fā)當(dāng)前對象重繪。
好了橡娄, 整個過程就是這樣了诗箍。如果有什么錯誤,歡迎大家指出挽唉,謝謝B俗妗?昀恰!