InheritWidget 實現區域性重新整理必須注意到兩個點:
- InheritWidget 下的子 widget 必須是快取過的
- InheritWidget 下的子 widget 需新增進 _dependencies 集合中
- InheritWidget 需要 rebuild
1、為什麼 InheritWidget 下的子 widget 必須是快取過的?
我們看下未做 widget 快取的例子:
class InheritWidgetState extends State<InheritWidgetDemo> {
int count = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
ShareDataWidget(data: count, child: _TestWidget()),
RaisedButton( child: Text("Increment"),onPressed: () => setState(() => ++count), // 觸發 rebuild
)
]);}}
class _TestWidget extends StatefulWidget {
@override
_TestWidgetState createState() => new _TestWidgetState();}
class _TestWidgetState extends State<_TestWidget> {
@override
Widget build(BuildContext context) {
print("我被 build 了");
return Text(ShareDataWidget.of(context,rebuild:true).data.toString());}}
複製程式碼
- 如果通過日誌去檢視的話,你會發現沒有任何問題,單擊
Increment
, print 確實打出了被 build 的日誌,你可能會覺得_TestWidgetState
的rebuild
設定為了true
,他就應該被 rebuild,沒有任何問題 - 但當把
rebuild
設定為false
呢?按照我們對 InheritWidget 的理解來看,_TestWidgetState
不應該被觸發 build 操作,然而事實是,_TestWidgetState
仍然被觸發,主要原因是 widget 未快取,setState 會觸發 widget 以及子 widget 的 build 操作,然後每次都會重新構建_TestWidget
2、 如何快取 widget
class InheritWidgetDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return HomePage(
child: WidgetA()
);
}
}
class HomePage extends StatefulWidget {
HomePage({Key key, this.child, }) : super(key: key);
// 快取的 widget
final Widget child;
@override
HomePageState createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
int counter = 0;
void _incrementCounter() {
setState(() { ++counter; });
}
@override
Widget build(BuildContext context) {
print("home ${widget.child.hashCode}"); // hashcode 一致
return _MyInheritedWidget(
data: this, child: widget.child,
);}}
複製程式碼
- 為什麼這麼寫可以?主要是 setState 觸發的是
HomePage
的 build,並不會影響到InheritWidgetDemo
,又因為 widget 是在InheritWidgetDemo
建立的,所以HomePage
拿到的一直是沒有變化過的 widget,也可以通過列印 widget.hashCode 來驗證一下是否一致
3、InheritWidget 下的子 widget 需新增進 _dependencies 集合中
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Center(
child: Text(
_MyInheritedWidget.of(context,rebuild: true).data.counter.toString(),
),
);
}
}
複製程式碼
- 如上是通過 InheritWidget 來實現資料展示,
rebuild
為 true 即為 InheritWidget 發生資料改變時需要重新整理
static _MyInheritedWidget of(BuildContext context, {bool rebuild = true}) {
if (rebuild) {
return (context.dependOnInheritedWidgetOfExactType<_MyInheritedWidget>());
}
return (context.getElementForInheritedWidgetOfExactType<_MyInheritedWidget>()).widget;
}
複製程式碼
rebuild
為 true 時,呼叫的是dependOnInheritedWidgetOfExactType
方法
@override
InheritedWidget dependOnInheritedElement(InheritedElement ancestor, { Object? aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
// 將當前 element 新增進 _dependencies 集合中
_dependencies!.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}
@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;
}
複製程式碼
- context 的實現類是 element ,最終會將當前 widget 的 element 快取到 _dependencies 集合中
4、如何觸發區域性重新整理
class HomePageState extends State<HomePage> {
int counter = 0;
void _incrementCounter() {
setState(() { ++counter; });
}
@override
Widget build(BuildContext context) {
return _MyInheritedWidget(
data: this, child: widget.child,
);}}
複製程式碼
- 在呼叫
_incrementCounter
方法觸發setState
時,會觸發HomePageState
的 rebuild 操作,從而影響到子 widget 的_MyInheritedWidget
rebuild,也就是 InheritWidget
abstract class ProxyElement extends ComponentElement {
....
@override
void update(ProxyWidget newWidget) {
final ProxyWidget oldWidget = widget;
...
updated(oldWidget);
_dirty = true;
rebuild();
}
複製程式碼
- InheritWidget 的 element 是 InheritElement ,繼承的是 ProxyElement,最終會走到 update 方法中來更新當前的 widget,可以直接看 updated 方法
-> 父類 ProxyElement
@protected
void updated(covariant ProxyWidget oldWidget) {
notifyClients(oldWidget);
}
-> 子類 InheritElement
@override
void updated(InheritedWidget oldWidget) {
if (widget.updateShouldNotify(oldWidget))
super.updated(oldWidget);
}
複製程式碼
- 子類實現了父類的 updated 方法,子類會對 InheritWidget 的
updateShouldNotify
進行判斷,如果返回true
,則子 wdiget 會呼叫didChangeDependencies
方法 - 父類 updated 會觸發 notifyClients 方法,notifyClients 是交由子類 InheritElement 來實現的
@override
void notifyClients(InheritedWidget oldWidget) {
assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
for (final Element dependent in _dependents.keys) {
....
// check that it really depends on us
assert(dependent._dependencies!.contains(this));
notifyDependent(oldWidget, dependent);
}
}
}
複製程式碼
- notifyClients 會遍歷 _dependencies 集合的 element 元素,並判斷 element 是否包含當前 rebuild 的 InheritWidget , 然後呼叫 notifyDependent 方法
@protected
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
dependent.didChangeDependencies();
}
複製程式碼
- notifyDependent 會呼叫 element 的
didChangeDependencies
方法
@mustCallSuper
void didChangeDependencies() {
assert(_lifecycleState == _ElementLifecycle.active); // otherwise markNeedsBuild is a no-op
assert(_debugCheckOwnerBuildTargetExists('didChangeDependencies'));
markNeedsBuild();// build
}
複製程式碼
- 進入到
didChangeDependencies
方法中檢視發現,element 會觸發 markNeedsBuild 進行重新構建,也就是區域性 widget 重新整理
5、如何使區域性重新整理失效
1、InheritWidget 的 updateShouldNotify
實現類返回 false
2、不將 widget 新增到 _dependencies 集合中,可以採用 getElementForInheritedWidgetOfExactType