scoped_model 源码阅读

最近做的项目有一个需求, 就是有不同的主题, 那么在设置页面就应该让生效, 这就是全局状态的一个管理了, Flutter 系统提供了 InheritedWidget, 但是这里我们来使用 scoped_model(基于InheritedWidget进行了封装).

InheritedWidget

我们先来看一下InheritedWidget是如何实现共享数据的.

以下是官网上的一个小例子

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
class ShareDataWidget extends InheritedWidget {
ShareDataWidget({
@required this.data,
Widget child
}) :super(child: child);

final int data; //需要在子树中共享的数据,保存点击次数

//定义一个便捷方法,方便子树中的widget获取共享数据
static ShareDataWidget of(BuildContext context) {
return context.inheritFromWidgetOfExactType(ShareDataWidget);
}

//该回调决定当data发生变化时,是否通知子树中依赖data的Widget
@override
bool updateShouldNotify(ShareDataWidget old) {
//如果返回true,则子树中依赖(build函数中有调用)本widget
//的子widget的`state.didChangeDependencies`会被调用
return old.data != data;
}
}

class _TestWidget extends StatefulWidget {
@override
__TestWidgetState createState() => new __TestWidgetState();
}

class __TestWidgetState extends State<_TestWidget> {
@override
Widget build(BuildContext context) {
//使用InheritedWidget中的共享数据
return Text(ShareDataWidget
.of(context)
.data
.toString());
}

@override
void didChangeDependencies() {
super.didChangeDependencies();
//父或祖先widget中的InheritedWidget改变(updateShouldNotify返回true)时会被调用。
//如果build中没有依赖InheritedWidget,则此回调不会被调用。
print("Dependencies change");
}
}

class InheritedWidgetTestRoute extends StatefulWidget {
@override
_InheritedWidgetTestRouteState createState() => new _InheritedWidgetTestRouteState();
}

class _InheritedWidgetTestRouteState extends State<InheritedWidgetTestRoute> {
int count = 0;

@override
Widget build(BuildContext context) {
return Center(
child: ShareDataWidget( //使用ShareDataWidget
data: count,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: _TestWidget(),//子widget中依赖ShareDataWidget
),
RaisedButton(
child: Text("Increment"),
//每点击一次,将count自增,然后重新build,ShareDataWidget的data将被更新
onPressed: () => setState(() => ++count),
)
],
),
),
);
}
}

这里需要注意:

  • context.inheritFromWidgetOfExactType(ShareDataWidget)来获取到指定InheritedWidget中的数据, 实际通知的时候是父Widget往下传递还是子Widget往上遍历呢?

接下lai 探索原因

InheritedWidget的源码很简单, 继承了ProxyWidget, 也没有实现太多逻辑, 对createElement进行了实现, 还定义了updateShouldNotify, 该方法的意思就是更新的时候是否应该通知在 build 阶段通过inheritFromWidgetOfExactType查找该 Widget 的子 Widget.

1
2
3
4
5
6
7
8
9
10
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);
}

顺藤摸瓜, 我们去就去看InheritedElement(this)做了什么, 进去之后我们可以发现_updateInheritance方法.

1
2
3
4
5
6
7
8
9
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;
}

同时我们对比一下普通的Element的该方法实现, 只是简单的将父Element_inheritedWidgets属性保存到自身(这样就保证了父级的向子集传递特性).

1
2
3
4
void _updateInheritance() {
assert(_active);
_inheritedWidgets = _parent?._inheritedWidgets;
}

但是这个_inheritedWidgets属性又是在哪里出现呢? 它定义在 Element中, 每一个实例都有这个属性. 它的作用是存储上级节点WidgetElement之间的映射.

1
2
3
4
5
6
7
8
9
10
11
abstract class Element extends DiagnosticableTree implements BuildContext {
/// Creates an element that uses the given widget as its configuration.
///
/// Typically called by an override of [Widget.createElement].
Element(Widget widget)
: assert(widget != null),
_widget = widget;

Element _parent;
...
Map<Type, InheritedElement> _inheritedWidgets;

现在我们回到InheritedElement的实现.

1
2
3
4
5
6
7
8
9
10
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会将自身的信息添加到_inheritedWidgets属性中, 然后子孙都可以通过他们自身的该属性访问当前的InheritedElement了.

现在我们知道如何访问_inheritedWidgets属性以及包含的内容了, 那么通知机制是如何实现呢?

一开始例子中就是使用inheritFromWidgetOfExactType(Type)方法去获取到指定的InherientWidget的, 那么这个方法是怎么实现呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
if (ancestor != null) {
assert(ancestor is InheritedElement);
return inheritFromElement(ancestor, aspect: aspect);
}
_hadUnsatisfiedDependencies = true;
return null;
}

@override
InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
assert(ancestor != null);
_dependencies ??= HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor.updateDependencies(this, aspect);
return ancestor.widget;
}

首先会获取Element(Context)_inheritedWidgets指定类型的Element, 如果获取到了, 则会添加到自身的依赖列表中, 祖先节点也有记录这个依赖, 这样在更新时候就可以直接通过_dependencies属性来进行通知了.

每一次InherientElement更新的时候, 都会调用notifyClients方法来通知子节点, 调用子节点的didChangeDependencies方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void notifyClients(InheritedWidget oldWidget) {
if (!widget.updateShouldNotify(oldWidget))
return;
assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
for (Element dependent in _dependents) {
assert(() {
// check that it really is our descendant
Element ancestor = dependent._parent;
while (ancestor != this && ancestor != null)
ancestor = ancestor._parent;
return ancestor == this;
}());
// check that it really depends on us
assert(dependent._dependencies.contains(this));
dependent.didChangeDependencies();
}
}

以上就是InherientWidget的原理, 接下来就是看看scoped_model做了怎样的封装.

scoped_model

该库主要的类有:

abstract class Model extends Listenable
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
abstract class Model extends Listenable {
final Set<VoidCallback> _listeners = Set<VoidCallback>();
int _version = 0;
int _microtaskVersion = 0;

void addListener(VoidCallback listener) {
debugPrint("添加监听器");
listener();
_listeners.add(listener);
}

@override
void removeListener(VoidCallback listener) {
print("监听器被去除");
_listeners.remove(listener);
}

int get listenerCount => _listeners.length;

@protected
void notifyListeners() {
if (_microtaskVersion == _version) {
_microtaskVersion++;
scheduleMicrotask(() {
_version++;
_microtaskVersion = _version;
_listeners.toList().forEach((VoidCallback listener) => listener());
});
}
}
}

Model继承了Listenable, 值在改变的时候调用notifyListeners既可以通知到所有的监听器.

class ScopedModel<T extends Model> extends StatelessWidget
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
class ScopedModel<T extends Model> extends StatelessWidget {

final T model;

final Widget child;

ScopedModel({@required this.model, @required this.child})
: assert(model != null),
assert(child != null);

@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: model,
builder: (context, _) => _InheritedModel<T>(model: model, child: child),
);
}
// 找到 ScopeModel, 并且让子节点和祖先节点建立依赖
static T of<T extends Model>(
BuildContext context, {
bool rebuildOnChange = false,
}) {
final Type type = _type<_InheritedModel<T>>();

Widget widget = rebuildOnChange
? context.inheritFromWidgetOfExactType(type)
: context.ancestorInheritedElementForWidgetOfExactType(type)?.widget;

if (widget == null) {
throw ScopedModelError();
} else {
return (widget as _InheritedModel<T>).model;
}
}

static Type _type<T>() => T;
}

通知方法

作者在构造ScopedModel的时候, 使用了AnimationBuilder, 这里会注册一个监听器到 Model中, 然后每一次值的改变都会调用AnimationBuilder.builder方法, 然后就会触发InherientWidget的改变, 根据updateShouldNotify来决定是否通知子孙控件更新, 但是在这里我们并没有看到InherientWidget的影子, 让我们接着往下看.

class _InheritedModel<T extends Model> extends InheritedWidget
1
2
3
4
5
6
7
8
9
10
11
12
13
class _InheritedModel<T extends Model> extends InheritedWidget {
final T model;
final int version;

_InheritedModel({Key key, Widget child, T model})
: this.model = model,
this.version = model._version,
super(key: key, child: child);

@override
bool updateShouldNotify(_InheritedModel<T> oldWidget) =>
(oldWidget.version != version);
}

看到这个类, 我们就可以发现作者的实现方式了, _InheritedModel继承自InheritedWidget, 上方ScopedModel.build()方法, 里面就返回了该inherientWidget对象, 但是与上方的例子还缺少子类对父类进行依赖的一步.

class ScopedModelDescendant<T extends Model> extends StatelessWidget
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
typedef Widget ScopedModelDescendantBuilder<T extends Model>(
BuildContext context,
Widget child,
T model,
);

class ScopedModelDescendant<T extends Model> extends StatelessWidget {
final ScopedModelDescendantBuilder<T> builder;

final Widget child;

final bool rebuildOnChange;

ScopedModelDescendant({
@required this.builder,
this.child,
this.rebuildOnChange = true,
});

@override
Widget build(BuildContext context) {
return builder(
context,
child,
ScopedModel.of<T>(context, rebuildOnChange: rebuildOnChange),
);
}
}

ScopedModelDescendant就是ScopedModel的子孙, 那么可以看到它的 Build方法, 里面有调用ScopedModel.of<T>(context, rebuildOnChange: rebuildOnChange), 我们把这个方法在拿出来看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static T of<T extends Model>(
BuildContext context, {
bool rebuildOnChange = false,
}) {
final Type type = _type<_InheritedModel<T>>();

Widget widget = rebuildOnChange
? context.inheritFromWidgetOfExactType(type)
: context.ancestorInheritedElementForWidgetOfExactType(type)?.widget;

if (widget == null) {
throw ScopedModelError();
} else {
return (widget as _InheritedModel<T>).model;
}
}

static Type _type<T>() => T;

到这里, 就可以发现它和inherient做法相同了, 首先获取到InheritedWidget的类型, _type<_InheritedModel<T>>()得到_InheritedModel<T>, 然后判断是否需要在改变的时候重绘, 默认是True, 如果需要重绘就会调用inheritFromWidgetOfExactType去建立依赖, 如果为false, 则会调用ancestorInheritedElementForWidgetOfExactType, 这个方法不会建立依赖, 所以在改变的时候不会收到通知并重绘, 官方的注释有这么一句:
This method does not establish a relationship with the target in the way that [inheritFromWidgetOfExactType] does.

最后贴一下作者提供的小例子
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
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';

void main() {
CounterModel model = CounterModel();
runApp(MyApp(
model: model,
));
}

class MyApp extends StatelessWidget {
final CounterModel model;

const MyApp({Key key, @required this.model}) : super(key: key);

@override
Widget build(BuildContext context) {
// At the top level of our app, we'll, create a ScopedModel Widget. This
// will provide the CounterModel to all children in the app that request it
// using a ScopedModelDescendant.
return ScopedModel<CounterModel>(
model: model,
child: MaterialApp(
title: 'Scoped Model Demo',
home: CounterHome('Scoped Model Demo'),
),
);
}
}

// Start by creating a class that has a counter and a method to increment it.
//
// Note: It must extend from Model.
class CounterModel extends Model {
int _counter = 0;

int get counter => _counter;

void increment() {
// First, increment the counter
_counter++;

// Then notify all the listeners.
notifyListeners();
}
}

class CounterHome extends StatelessWidget {
final String title;

CounterHome(this.title);

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
// Create a ScopedModelDescendant. This widget will get the
// CounterModel from the nearest parent ScopedModel<CounterModel>.
// It will hand that CounterModel to our builder method, and
// rebuild any time the CounterModel changes (i.e. after we
// `notifyListeners` in the Model).
ScopedModelDescendant<CounterModel>(
builder: (context, child, model) {
return Text(
model.counter.toString(),
style: Theme.of(context).textTheme.display1,
);
},
),
],
),
),
// Use the ScopedModelDescendant again in order to use the increment
// method from the CounterModel
floatingActionButton: ScopedModelDescendant<CounterModel>(
builder: (context, child, model) {
return FloatingActionButton(
onPressed: model.increment,
tooltip: 'Increment',
child: Icon(Icons.add),
);
},
),
);
}
}

这里也能验证我们上方所分析的, 首先需要构建 ScopedModel, 然后共享状态的子孙节点通过ScopedModelDescendant来添加.

Flutter-完成一个图片APP

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×