先上程式碼
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter InheritWidget',
home: Scaffold(
appBar: AppBar(),
body: Center(
child: TestWidget(),
),
),
);
}
}
class TestWidget extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return TestWidgetState();
}
}
class TestWidgetState extends State<TestWidget> {
String text = "init";
@override
Widget build(BuildContext context) {
return CustomInheritedWidget(
text: text, //1
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
InheritedWidgetChild(),
CustomRaisedButton(
onPressed: () {
setState(() {
text = "modify"; //2
});
},
child: Text("修改值"),
)
],
),
);
}
}
class CustomRaisedButton extends RaisedButton {
const CustomRaisedButton({
@required VoidCallback onPressed,
Widget child,
}) : super(onPressed: onPressed, child: child);
@override
Widget build(BuildContext context) {
print("CustomRaisedButton build");
return super.build(context);
}
}
class InheritedWidgetChild extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return InheritedWidgetChildState();
}
}
class InheritedWidgetChildState extends State<InheritedWidgetChild> {
@override
Widget build(BuildContext context) {
print("InheritedWidgetChildState build");
final CustomInheritedWidget widget = context.dependOnInheritedWidgetOfExactType<CustomInheritedWidget>();
return Text(widget.text);
}
@override
void didChangeDependencies() {
print("InheritedWidgetChildState didChangeDependencies");
super.didChangeDependencies();
}
}
class CustomInheritedWidget extends InheritedWidget {
CustomInheritedWidget({Key key, this.text, Widget child}) : super(key: key, child: child);
final String text;
@override
bool updateShouldNotify(CustomInheritedWidget oldWidget) {
return oldWidget.text != text;
}
}
複製程式碼
在註釋1,text預設值為init
,當點選按鈕的時候,修改text值為modify
並重新整理頁面,我們會發現InheritedWidgetChild
呼叫了didChangeDependencies()
方法,我們來分析一下,點選按鈕的時候都發生了什麼。
_inheritedWidgets
的傳遞
首先說一下流程,在Element
被activate
和mount
的時候,會呼叫_updateInheritance
方法,把自己的_inheritedWidgets
指向_parent
的_inheritedWidgets
,而InheritedElement
覆蓋了該方法,程式碼分別如下
//Element
void _updateInheritance() {
assert(_active);
_inheritedWidgets = _parent?._inheritedWidgets;
}
複製程式碼
//InheritedElement
@override
void _updateInheritance() {
assert(_active);
final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = HashMap<Type, InheritedElement>.from(incomingWidgets);
else
_inheritedWidgets = HashMap<Type, InheritedElement>();
_inheritedWidgets[widget.runtimeType] = this;
}
複製程式碼
可以看到InheritedElement
拷貝了_parent
的_inheritedWidgets
並把自己的 widget
的runtimeType
作為key
,this
作為value
儲存在了自己的inheritedWidgets
屬性裡,所以最開始的程式碼的UI樹是這樣的
TestWidget
-CustomInheritedWidget
--Column
---InheritedWidgetChild
---RaisedButton
CustomInheritedWidget
拷貝了TestWidget
的_inheritedWidgets
到自己的_inheritedWidgets
並將自己儲存其中,然後Column
的_inheritedWidgets
直接指向了CustomInheritedWidget
的_inheritedWidgets
,然後InheritedWidgetChild
和RaisedButton
的_inheritedWidgets
直接指向了Column
的_inheritedWidgets
InheritedElement
的查詢
在InheritedWidgetChild
裡呼叫context.dependOnInheritedWidgetOfExactType<CustomInheritedWidget>()
方法可以查詢到CustomInheritedWidget
,程式碼如下:
//Element
@override
T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({Object aspect}) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[T];
if (ancestor != null) {
assert(ancestor is InheritedElement);
return dependOnInheritedElement(ancestor, aspect: aspect) as T;
}
_hadUnsatisfiedDependencies = true;
return null;
}
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
複製程式碼
//InheritedElement
@protected
void updateDependencies(Element dependent, Object aspect) {
setDependencies(dependent, null);
}
@protected
void setDependencies(Element dependent, Object value) {
_dependents[dependent] = value;
}
複製程式碼
我們可以看到,在dependOnInheritedWidgetOfExactType
這個方法裡,查詢到泛型Widget
對應的InheritedElement
,儲存到ancestor
變數裡,然後在自己(InheritedWidgetChild
)的_dependencies
集合中新增ancestor
,並把自己儲存到找到ancestor
的_dependents
集合裡,這樣在InheritedElement
更新內部資料的時候,就可以通知到所有依賴了本InheritedElement
的Widget
。同時InheritedWidgetChild
的_dependencies
集合中新增了ancestor
,所以在InheritedWidgetChild
銷燬的時候,可以把自己(InheritedWidgetChild
)從ancestor
的_dependents
集合中移除,避免不必要的更新和記憶體洩漏。
總結
總結一下上面的內容,在Element
被activate
和mount
的時候,會呼叫_updateInheritance
方法,把自己的_inheritedWidgets
指向_parent
的_inheritedWidgets
,而InheritedElement
覆蓋了該方法,將自己儲存到了_inheritedWidgets
中,所以在層層向下傳遞的時候,_inheritedWidgets
就包含了傳遞過程中所有的InheritedElement
,然後在通過dependOnInheritedWidgetOfExactType
方法獲取InheritedElement
的時候,方法呼叫方把自己儲存到了InheritedElement
的_dependencies
集合中,並且方法呼叫方在自己的_dependencies
集合中也新增了自己所依賴的InheritedElement
的引用,在方法呼叫方銷燬的時候,把自己從自己所依賴的InheritedElement
的_dependencies
集合中刪除自己除,避免不必要的更新和記憶體洩漏。
思考題
Element
的activate
和mount
方法在什麼時候被呼叫?- 在
CustomInheritedWidget
改變內部的資料的時候,為什麼InheritedWidgetChild
會呼叫didChangeDependencies()
?