如果沒有看過之前兩節的,建議先看看前兩節的內容
上一節中我們主要做的事情如下:
- 在 Animation 的
addListener(...)
中呼叫setState(...)
來給 widget 新增動畫 - 使用
AnimatedWidget
的方法來給 widget 新增動畫
上一節中我們也通過案例的形式展現出以上兩種方式的區別,得出的結論就是如下:
使用 addListener(...)
和 setState(...)
給 Widget
新增動畫的時候,會導致其他的 Widget
也跟著重繪,而使用 AnimatedWidget
的方式給 Widget
新增動畫的時候只會重繪當前的 Widget
這是為什麼呢 ???
我們先來看看動畫的執行流程。
我們在使用動畫的時候會初始化一個 AnimationController
AnimationController controller = AnimationController(vsync: this, duration: Duration(milliseconds: 300));
複製程式碼
可以看出,初始化 AnimationController
需要傳遞兩個引數,一個是 vsync
一個是 duration
,這個 duration
很容易理解,就是動畫執行完畢需要的時長,那 vsync
是什麼啊?上述程式碼中我們傳入了 this
,那是因為我們在 State
類 with(混入,第一節 有講) 了 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);
}
複製程式碼
我們抽重點出來看
_ticker = vsync.createTicker(_tick);
複製程式碼
這裡呼叫了 vsync
的 createTicker(...)
方法建立了一個 Ticker
, 而我們在初始化 AnimationController
的時候 vsync
引數傳的是 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 類在每一幀動畫中都會呼叫他自己的回撥一次
- 第二句的大概意思就是 當 Ticker 被建立之後,預設是 不可用(disabled)的,可以呼叫
start
方法來使 Ticker 變得可用(enable) - 第三句的大概意思就是 建立一個 Ticker,他將在執行時的每一幀都會呼叫一次所提供的回撥
建立 Ticker
到這兒就完了。
如何讓Ticker
可用呢,也就是如何呼叫它的 start
方法呢?
我們在啟動動畫的時候會呼叫 AnimationController#forward()
, 在 forward()
方法中就間接的會呼叫到 Ticker#start()
, Ticker#start()
最後會呼叫到 scheduleFrame()
,而這個方法裡面呼叫了 ui.window.scheduleFrame();
,這個 scheduleFrame()
是一個 Native 方法,如下:
void scheduleFrame() native 'Window_scheduleFrame';
複製程式碼
上面的方法應該就是排程螢幕重新整理的(如有錯誤,請指出,謝謝)。
到這裡,Ticker
也就 start
了,然後 Ticker 就會在動畫的每一幀去調一次自己的回撥,這個回撥
是在 AnimationController
中的構造方法中建立 Ticker
的時候傳入的 _ticker = vsync.createTicker(_tick);
我們來看看 _tick
部分原始碼:
void _tick(Duration elapsed) {
...
notifyListeners();
_checkStatusChanged();
}
複製程式碼
這裡面呼叫了 notifyListeners()
和 _checkStatusChanged()
,我們先來看看 notifyListeners()
,部分原始碼如下:
void notifyListeners() {
final List<VoidCallback> localListeners = new List<VoidCallback>.from(_listeners);
for (VoidCallback listener in localListeners) {
try {
if (_listeners.contains(listener))
// 執行回撥
listener();
} catch (exception, stack) {
...
}
}
}
複製程式碼
可以看出,notifyListeners()
中有一個 VoidCallback
的集合,然後遍歷這個集合,並且執行集合中所有的回撥。
通常我們會使用 .addListener((){...})
給動畫新增監聽,我們在呼叫addListener((){...})
之後會執行如下原始碼
void addListener(VoidCallback listener) {
didRegisterListener();
_listeners.add(listener);
}
複製程式碼
將我們傳入的回撥方法新增到_listeners
集合中,也就是上面 notifyListeners()
方法中遍歷的集合。
這裡我們來小結一下動畫的執行流程
- 建立動畫
- 建立 Ticker,並 傳入回撥,回撥裡面會執行
notifyListeners()
方法,此方法中會去遍歷_listeners
集合,並執行集合中的每一個回撥方法
- 建立 Ticker,並 傳入回撥,回撥裡面會執行
- 給動畫新增監聽
- 這裡監聽時傳入的回撥會被新增到
_listeners
集合中去
- 這裡監聽時傳入的回撥會被新增到
- 啟動動畫
- 會呼叫
ticker.srtart()
來啟動Ticker
,然後Ticker
在動畫的每一幀都會 執行回撥,也就是我們在第一步中傳入的
- 會呼叫
接下來就是我們在 addListener((){...})
中乾的事情了,通常會這樣
_controller.addListener(() {
setState(() {
});
});
複製程式碼
也就是我們會在傳入的回撥方法中去執行 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
當前物件的內部狀態發生改變,這個操作會導致再次呼叫當前 State 物件的 build 方法,相當於重繪當前物件。
結合之前的分析,整個動畫過程就是,當動畫啟動之後,會不停的去執行addListener((){...})
中的回撥,而我們在監聽回撥中又呼叫了setState(...)
方法,所以這就導致當前物件不停的重繪,也就出現了螢幕上的動畫的效果。
回到之前的問題: 使用 addListener(...) 和 AnimatedWidget 為什麼會出現那種區別呢?
其實我們來看下 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
在初始化的時候會去建立一個 State
物件來管理自身的狀態,也就是回去執行 createState()
方法。
其實到這裡已經可以解釋兩種動畫方式存在的區別,因為 AnimatedWidget
內部也有一個 State
物件來管理這自身的狀態,而我們之前通過檢視文件也知道一個 State
物件只會維護當前物件的狀態,所以即使重繪,也只會導致當前 State
物件的重繪,而不會導致其他 State
物件的重繪。
我們來繼續看看 AnimatedWidget
中的 createState()
幹了什麼事,它裡面呼叫了_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);
複製程式碼
明白了吧!!!
_AnimatedState
在 initState
中給 widget
的 listenable
新增了監聽,這裡的listenable
就是我們在初始化一個 AnimatedWidget
是傳入的 Animation
物件。
而 addListener
中傳入的回撥是 _handleChange()
, 在 _handleChange()
中同樣呼叫了 setState(...)
來觸發當前物件重繪。
好了, 整個過程就是這樣了。如果有什麼錯誤,歡迎大家指出,謝謝!!!