标签: InheritedWidget

【Flutter】InheritedWidget、InheritedModel的使用介绍

Flutter中有四种widget

StatelessWidget
StatefullWidget
RenderObjectWidget
InheritedWidget
其中StatelessWidget和StatefulWidget是*常见到的,从状态管理角度的分类;RenderObjectWidget是所有需要渲染的Widget的基类。

至于*后一个InheritedWidget,许多初学者不一定了解,但是在一些稍微复杂的项目中是必须要用到的,所以本文介绍一下InheritedWidget的用法

InheritedWidget
To obtain the nearest instance of a particular type of inherited widget from a build context, use BuildContext.inheritFromWidgetOfExactType.

Inherited widgets, when referenced in this way, will cause the consumer to rebuild when the inherited widget itself changes state.

通常情况下,子widget无法单独感知父widget的变化,当父state变化时,通过其build重建所有子widget;

%title插图%num

InheritedWidget可以避免这种全局创建,实现局部的子widget更新:
子widget通过BuildContext.inheritFromWidgetOfExactType从buildContext中获取并监听指定类型的父InheritedWidget,并跟随其重建而rebuild

如上图,点击C按钮,State变化后,A的Text可以单独刷新,B不受到影响

代码演示
接下来通过代码对比一下使用或不使用InheritedWidget的区别:

%title插图%num

点击+,后上面的0变化,中间的文字部分不变化。

传统实现
点击按钮state变化后,widgetA、B、C都会rebuild

class TopPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text(‘Demo’),
),
body: HomePage(),
),
);
}
}

class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
int _counter = 0;

void _incrementCounter() {
setState(() {
_counter++;
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
WidgetA(_counter),
WidgetB(),
WidgetC(_incrementCounter),
],
),
);
}
}

class WidgetA extends StatelessWidget {
final int counter;

WidgetA(this.counter);

@override
Widget build(BuildContext context) {
return Center(
child: Text(
‘${counter}’,
style: Theme.of(context).textTheme.display1,
),
);
}
}

class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(‘I am a widget that will not be rebuilt.’);
}
}

class WidgetC extends StatelessWidget {
final void Function() incrementCounter;

WidgetC(this.incrementCounter);

@override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: () {
incrementCounter();
},
child: Icon(Icons.add),
);
}
}

使用AndroidStudio的Flutter Performance可以看到widgetA、B、C都参与了rebuild

使用InheritedWidget实现
class TopPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(
child: Scaffold(
appBar: AppBar(
title: Text(‘InheritedWidget Demo’),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
WidgetA(),
WidgetB(),
WidgetC(),
],
),
),
),
);
}
}

class _MyInheritedWidget extends InheritedWidget {
_MyInheritedWidget({
Key key,
@required Widget child,
@required this.data,
}) : super(key: key, child: child);

final HomePageState data;

@override
bool updateShouldNotify(_MyInheritedWidget oldWidget) {
return true;
}
}

class HomePage extends StatefulWidget {
HomePage({
Key key,
this.child,
}) : super(key: key);

final Widget child;

@override
HomePageState createState() => HomePageState();

static HomePageState of(BuildContext context, {bool rebuild = true}) {
if (rebuild) {
return (context.inheritFromWidgetOfExactType(_MyInheritedWidget) as _MyInheritedWidget).data;
}
return (context.ancestorWidgetOfExactType(_MyInheritedWidget) as _MyInheritedWidget).data;
// or
// return (context.ancestorInheritedElementForWidgetOfExactType(_MyInheritedWidget).widget as _MyInheritedWidget).data;
}
}

class HomePageState extends State<HomePage> {
int counter = 0;

void _incrementCounter() {
setState(() {
counter++;
});
}

@override
Widget build(BuildContext context) {
return _MyInheritedWidget(
data: this,
child: widget.child,
);
}
}

class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePageState state = HomePage.of(context);

return Center(
child: Text(
‘${state.counter}’,
style: Theme.of(context).textTheme.display1,
),
);
}
}

class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(‘I am a widget that will not be rebuilt.’);
}
}

class WidgetC extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePageState state = HomePage.of(context, rebuild: false);
return RaisedButton(
onPressed: () {
state._incrementCounter();
},
child: Icon(Icons.add),
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
可以看到state变化时,widgetB、C都没有rebuild

关键代码说明
针对InheritedWidget版本中的关键类进行说明

WidgetA、WidgetC
传统版本中WidgetA、C通过构造函数传入父级的state以及回调
InheritedWidget版本中,可以通过如下静态方法获取

final HomePageState state = HomePage.of(context); // WidgetA
final HomePageState state = HomePage.of(context, rebuild: false); // WidgetC
1
2
WidgetC是一个Button需要通过state获取回调方法,但不需要跟随state变化而刷新,所以rebuild指定false

接下来详细看一下获取state的静态方法 HomePage.of

HomePage
static HomePageState of(BuildContext context, {bool rebuild = true}) {
if (rebuild) {
return (context.inheritFromWidgetOfExactType(_MyInheritedWidget) as _MyInheritedWidget).data;
}
return (context.ancestorWidgetOfExactType(_MyInheritedWidget) as _MyInheritedWidget).data;
// or
// return (context.ancestorInheritedElementForWidgetOfExactType(_MyInheritedWidget).widget as _MyInheritedWidget).data;
}
1
2
3
4
5
6
7
8
HomePage.of用来通过buildContext,找到*近的_MyInheritedWidget。然后就可以同_MyInheritedWidget获取其持有的state。

获取上级Widget的几个关键方法如下:

method description
inheritFromWidgetOfExactType 获取*近的给定类型的上级Widget,该widget必须是InheritedWidget的子类,并向上级widget注册传入的context,当上级widget改变时,这个context持有的widget会rebuild以便从该widget获得新的值。这就是child向InheritedWidget注册的方法。
inheritFromWidgetOfExactType 仅仅用来获取*近的给定类型的上级Widget,不会因为上级Widget的改变而rebuild
ancestorInheritedElementForWidgetOfExactType 功能与inheritFromWidgetOfExactType一样,但是只会寻找InheritedWidget的子类,所以可以以O(1)的复杂度查找上级Widget
因此,widgetA随着父widget的变化而rebuild,widgetB并没有rebuild

_MyInheritedWidget
class _MyInheritedWidget extends InheritedWidget {
_MyInheritedWidget({
Key key,
@required Widget child,
@required this.data,
}) : super(key: key, child: child);

final HomePageState data;

@override
bool updateShouldNotify(_MyInheritedWidget oldWidget) {
return true;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
继承自InheritedWidget,所以子Widget可以通过inheritFromWidgetOfExactType获取。

updateShouldNotify控制是否需要子widget感受其变化,如果返回true,则通过inheritFromWidgetOfExactType注册的子widget跟随其变化rebuild

子widget*终目的是要获取共享的父级state,所以这里通过data属性持有了state。

那再来看一下这个HomePageState

HomePageState
@override
Widget build(BuildContext context) {
return _MyInheritedWidget(
data: this,
child: widget.child,
);
}
1
2
3
4
5
6
7
此处_MyInheritedWidget的使用是关键。

传统写法中,build中直接创建widgetA、B、C并返回,因此每当state变化时,会重新创建子widget并rebuild;

InheritedWidget版本中,HomePage保持父widget(TopPage)的children,当state变化时widgetA、B、C不会重建,而是重新传入给_MyInheritedWidget,重建的只有_MyInheritedWidget

TopPage
class TopPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
・・・
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
WidgetA(), // 子widget的创建移动到这里
WidgetB(),
WidgetC(),
],
),
・・・
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
根据上文的说明,为了避免子widget的反复创建和rebuild,将widgetA、B、C的实例化移动到这里

InheritedModel
上面的例子中我们通过自定义了rebuild参数来指定子Widget是否参与rebuild,实际上也可以使用InheritedModel完成此需求

InheritedModel继承自InheritedWidget,可以通过字符串key(aspect)来指定特定子widget进行rebuild。

简单看一下InheritedModel版本与InheritedWidget版本在实现上的不同

@override
HomePageState createState() => HomePageState();

static HomePageState of(BuildContext context, String aspect) {
return InheritedModel.inheritFrom<_MyInheritedWidget>(context, aspect: aspect).data;
}
}
1
2
3
4
5
6
7
使用 InheritedModel.inheritFrom获取widget

class _MyInheritedWidget extends InheritedModel {

@override
bool updateShouldNotifyDependent(_MyInheritedWidget old, Set aspects) {
return aspects.contains(‘A’); // 当aspect包晗“A”时,通知其rebuild
}
}
1
2
3
4
5
6
7
继承InheritedModel,重写updateShouldNotifyDependent

class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePageState state = HomePage.of(context, ‘A’); // 注册aspect为“A“
1
2
3
4
class WidgetC extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePageState state = HomePage.of(context, ‘C’); // 注册aspect为“C”
1
2
3
4
如上,因为注册的key(aspect)不同,只有widgetA会受到rebuild的通知

更局部的刷新
如果widgetA是下面这样,我们希望能进一步控制其子widget的局部刷新

class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePageState state = HomePage.of(context);

return Column(
children: <Widget>[
Center(
child: Text(
‘${state.counter}’,
style: Theme.of(context).textTheme.display1,
),
),
Text(“AAAAA”), // 此处不需rebuild
],
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
如果彻底理解了BuildContext和InheritedWidget的注册机制,是可以很容易实现的:

return Column(
children: <Widget>[
Center(
child: Builder(builder: (context){
final HomePageState state = HomePage.of(context);
return Text(
‘${state.counter}’,
style: Theme.of(context).textTheme.display1,
);
}),
),
Text(“AAAAA”),
],
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
通过Builder来创建一个匿名类widget,然后将HomePage.of移到其内部。此时InheritedWidget中注册的context不再是widgetA而是这个匿名类widget,因此可以实现widgetA的局部刷新

不使用InheritedWidget
我想通过上文的介绍大家应该能够想到,如果子widget仅仅想访问父级state(不通过构造函数传参的方式),但没有监听其变化的需要,可以不使用InheritedWidget:

class TopPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text(‘Demo’),
),
body: HomePage(),
),
);
}
}

class HomePage extends StatefulWidget {
HomePageState state; // 持有state供子类获取

@override
HomePageState createState() {
state = HomePageState();
return state;
}
}

class HomePageState extends State<HomePage> {
int counter = 0; // 去掉private

void incrementCounter() { // 去掉private
setState(() {
counter++;
});
}

@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
WidgetA(),
WidgetB(),
WidgetC(),
],
),
);
}
}

class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePage widget = context.ancestorWidgetOfExactType(HomePage); // 获取state
final HomePageState state = widget?.state;

return Center(
child: Text(
‘${state == null ? 0 : state.counter}’,
style: Theme.of(context).textTheme.display1,
),
);
}
}

class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(‘I am a widget that will not be rebuilt.’);
}
}

class WidgetC extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePage widget = context.ancestorWidgetOfExactType(HomePage);
final HomePageState state = widget?.state;

return RaisedButton(
onPressed: () {
state?.incrementCounter();
},
child: Icon(Icons.add),
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
通过ancestorWidgetOfExactType寻找指定类型的widget,然后获取其state使用,当然这个遍历是O(n)的,性能比InheritedWidget版本要差

*后
Flutter中很多组件都是基于InheritedWidget实现的,例如Scoped Model、BLoC(Business Logic of component)等,想要掌握这些高级特性的使用先从了解InheritedWidget开始吧

代码:
https://github.com/vitaviva/flutter_inherited_widget_sample/tree/master/flutter_inherited_widget
————————————————
版权声明:本文为CSDN博主「fundroid_方卓」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/vitaviva/article/details/105462686

Flutter InheritedWidget

InheritedWidget 提供了一种数据在 widget 树中从上到下传递、共享的方式,比如我们在应用的根 widget 中通过 InheritedWidget 共享了一个数据,那么我们便可以在任意子 widget 中来获取该共享的数据!这个特性在一些需要在 widget 树中共享数据的场景中非常方便!如 Flutter SDK 中正是通过 InheritedWidget 来共享 Theme (应用主题) 和 Locale (当前语言环境) 信息的.

InheritedWidget 是个只有一个抽象方法的抽象类:

abstract class InheritedWidget extends ProxyWidget {
const InheritedWidget({ Key key, Widget child })
: super(key: key, child: child);

@override
InheritedElement createElement() => InheritedElement(this);

@protected
bool updateShouldNotify(covariant InheritedWidget oldWidget);
}

抽象类表示需要有一个实现类来继承它.

抽象方法 updateShouldNotify(covariant InheritedWidget oldWidget) 会在 InheritedWidget 实例 build 时, 返回值告诉 Framework 框架是否需要通知子树, 数据发生了变动. 方法签名中有个 covariant 关键字, 意思为协变, 告诉编译器 oldWidget 可以窄化成实现类的类型, 方便判断相关数据的变动情况.

使用
首先实现 InheritedWidget 类:

class ShareDataWidget extends InheritedWidget {
ShareDataWidget({
@required this.data,
Key key,
Widget child
}): super(key: key, child: child);

final int data;

@override
bool updateShouldNotify(ShareDataWidget oldWidget) {
return oldWidget.data != data;
}
}

上面代码中, 有个用于共享的数据 data, 覆写了 InheritedWidget 类唯一的抽象方法, 返回 data 有无变动, oldWidget 是 ShareDataWidget 实例在 build 时, Framework 传递过来的, 表示旧的 widget.

接下来是个 demo:

class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;

@override
Widget build(BuildContext context) {
return ShareDataWidget(
data: _counter,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: const Text(“+1”),
onPressed: () => setState(() => ++_counter),
),
RaisedButton(
child: const Text(“-1”),
onPressed: () => setState(() => –_counter),
),
TestWidget(),
],
),
),
);
}
}

class TestWidget extends StatelessWidget {
TestWidget();

@override
Widget build(BuildContext context) {
ShareDataWidget shareDataWidget;
shareDataWidget = context.inheritFromWidgetOfExactType(ShareDataWidget);
String data = shareDataWidget.data.toString();
return Text(data);
}
}

TestWidget 是另一个子控件, 在其 build 方法中引用了 ShareDataWidget 中的数据.

TestWidget 通过 context.inheritFromWidgetOfExactType(ShareDataWidget) 在祖先节点中获取一个距离*近的 ShareDataWidget widget, 在这同时, 也注册了依赖关系, 使得以后 ShareDataWidget 的数据一旦发生的变化, 就会通知所有的后代 widget 去刷新.

当按下 RaisedButton 按钮时, 调用 setState() 方法, 数据 _counter 改变并触发整个页面的 rebuild. InheritedElement 将调用 widget.updateShouldNotify(oldWidget), 如果 updateShouldNotify() 方法返回 true, 则 InheritedElement 会遍历并执行子 Element 的 didChangeDependencies() 方法.

void didChangeDependencies() {
// …
markNeedsBuild(); // 标记子Element需要rebuild
// 如果该Element是StatefullElement类型的话还会调用
// _state.didChangeDependencies();
}

注意, 这里是 Element 的方法, 如果子 Widget是 StatefulWidget 类型的话, State 里面也有一个 didChangeDependencies() 方法, 也会被调用.

可以发现, 只能按下 RaisedButton 按钮, ShareDataWidget 才会 rebuild.

因为 RaisedButton 按钮和 ShareDataWidget 两个 widget 都在 StatefulWidget 的包裹中, 按下按钮会调用 setState() 方法, 让 ShareDataWidget 也会被更新, 而在 TestWidget 中只能读取, 却无法改变 ShareDataWidget 内的 data 数据.

只能在 InheritedWidget 所在的 StatefulWidget 里调用 setState() 方法重建 InheritedWidget 实例和数据, 其他子树 widget 都只能读取到一个副本, 而无法修改 InheritedWidget 共享的值.

例如 ThemeData themeData = Theme.of(context); 拿到的是一个副本 ThemeData,
做些修改后只对单个路由局部换肤, 如果想要对整个应用换肤, 则需要手动去修改 MaterialApp 的 theme 属性.

那么子树 widget 需要修改共享的数据, 就要使用 跨组件状态共享 (Provider) , 其大概原理就是 InheritedWidget + 类似 EventBus 的事件通知, 让 InheritedWidget 进行重建.

 

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速