本文首要从 scoped_model 的简略运用说起,然后再深化源码进行分析(InheritedWidget、Listenable、AnimatedBuilder),不会探讨 Flutter 状况管理的好坏,单纯为了学习作者的规划思维。
一、什么是 scoped_model
scoped_model 是一个第三方 Dart 库,能够让您轻松的将数据模型从父 Widget 传递到子 Widget。此外,它还会在模型更新时从头构建一切运用该模型的子 Widget。
它直接来自于 Google 正在开发的新系统 Fuchsia 核心 Widgets 中对 Model 类的简略提取,作为独立运用的独立 Flutter 插件发布。
二、用法
class CounterModel extends Model {
int _counter = 0;
int get counter => _counter;
static CounterModel of(BuildContext context) =>
ScopedModel.of<CounterModel>(context, rebuildOnChange: true);
void increment() {
_counter++;
notifyListeners();
}
}
class ScopedModelDemoPage extends StatelessWidget {
const ScopedModelDemoPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('scopedModel'),
centerTitle: true,
),
body: ScopedModel(
model: CounterModel(),
child: ScopedModelDescendant<CounterModel>(
builder: (context, child, model) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('${model.counter}'),
OutlinedButton(
onPressed: ScopedModel.of<CounterModel>(context).increment,
// onPressed: model.increment,
child: Text('add'),
),
],
),
),
),
),
);
}
}
从上面的代码能够看出 scoped_model 的运用非常简略,只需求以下三步:
- 定义
Model
的完成,如 CounterModel,这里需求注意的是 CounterModel 一定要承继自Model
(为什么一定要承继 Model 咱们后面细说)并且在状况改动的时分履行 notifyListeners。 - 运用
ScopedModel
Widget 包裹需求用到Model
的 Widget。 - 运用
ScopedModelDescendant
或许ScopedModel.of<CounterModel>(context)
来进行获取数据。
三、完成原理
在 scoped_model 中的整个完成中,它很巧妙的借助了 AnimatedBuilder、Listenable、InheritedWidget 等 Flutter 的根底特性。
scoped_model 运用了观察者形式,将数据放在父 Widget,子 Widget 经过找到父 Widget 的 model 进行数据烘托,最终改动数据的时分再将数据传回,父 Widget 再通知一切用到了该 model 的子 Widget 去更新状况。
咱们首先从 ScopedModel
下手,经过源码咱们不难发现,ScopedModel 是一个 StatelessWidget
最终回来一个 AnimatedBuilder
,在 AnimatedBuilder
中在经过 builder 回来 _InheritedModel
。
咱们再从 Model
下手,能够看出 Model
是一个 承继自 Listenable
的抽象类,首要有一个 _listeners 变量用 Set 来进行存储,复写了 addListener
、removeListener
、notifyListeners
办法。在这里不知道咱们有没有想过 Model
为什么要承继 Listenable
? 在这里先卖个关子,在后面会详细讲解。
假如仅仅单单看 ScopedModel
和 Model
好像也看不出来什么巧妙之处,但是假如把 ScopedModel
中回来的 AnimatedBuilder
和 Model
所承继的 Listenable
结合起来进行考虑就会发现,AnimatedBuilder
承继自 AnimatedWidget
,在 AnimatedWidget
的生命周期中会对 Listenable
增加监听,而 Model
正好就完成了 Listenable
接口。
Model
完成了 Listenable
接口,内部刚好有一个 Set<VoidCallback> _listeners
用来保存接收者。当 Model
赋值给 AnimatedBuilder
中的 animation 时,Listenable
的 addListener 就会被调用,然后增加一个 _handleChange
办法,_handleChange
内部只有一行代码 setState((){})
,当调用 notifyListeners
时,会从创建一个 Microtask,去履行一遍 _listeners
中的 _handleChange
,当 _handleChange
被调用时就会进行更新 UI 界面。其实这里也就解释了 Model
为什么要承继 Listenable
。
不知道咱们发现没有,讲了这么多,还没有讲到 ScopedModelDescendant
到底是干什么的?那我就不得不先说起 InheritedWidget
了。
InheritedWidget
是 Flutter 中非常重要的一个功能型组件,它供给了一种在 Widget 树中从上到下同享数据的办法,比如咱们在运用的根 Widget 中经过 InheritedWidget
同享了一个数据,那么咱们便能够在任意子 Widget 中来获取该同享的数据。它首要有以下两个作用:
- 子 Widget 能够经过
Inherited
Widgets 供给的静态 of 办法拿到离他最近的父Inherited
widgets 实例。 - 当
Inherited
Widgets 改动 state 之后,会主动触发 state 消费者的 rebuild 行为。
在 scoped_model
中咱们能够经过 ScopedModel.of<CountModel>(context)
来获取 咱们的 model,最首要的便是在 ScopedModel
中回来了 AnimatedBuilder
,而 AnimatedBuilder
中 builder 又回来了 _InheritedModel
, _InheritedModel
又承继了 InheritedWidget
。
言归正传,咱们一起回到 ScopedModelDescendant
的主题,不知道咱们有没有尝试过,不用 ScopedModelDescendant
来获取 model 会产生什么样的状况?经过窥视源码咱们发现有 ScopedModelError
这样一个反常类,说的现已很清晰了,必需要供给 ScopedModelDescendant
。what ?其实它首要做了以下 2 件工作:
- 隐式调用
ScopedModel.of<T>(context)
来获取 model。 - 清晰语义化,不然咱们每次都需求用
Builder
来进行构建,不然将获取不到 model,还会抛出反常。
// 不运用 ScopedModelDescendant 运用 Builder 的用法
class ScopedModelDemoPage extends StatelessWidget {
const ScopedModelDemoPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('scopedModel'),
centerTitle: true,
),
body: ScopedModel(
model: CounterModel(),
child: Builder(
builder: (context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('${CounterModel.of(context).counter}'),
OutlinedButton(
onPressed: CounterModel.of(context).increment,
child: Text('add1'),
),
],
);
},
),
// child: ScopedModelDescendant<CounterModel>(
// builder: (context, child, model) => Center(
// child: Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Text('${model.counter}'),
// OutlinedButton(
// onPressed: ScopedModel.of<CounterModel>(context).increment,
// // onPressed: model.increment,
// child: Text('add'),
// ),
// ],
// ),
// ),
// ),
),
);
}
}
除了上面这种运用 Builder 的办法,当然咱们还能够运用下面的办法,把它独自提取出一个 Widget,代码如下:
// 独自提取 Widget 的办法
class ScopedModelDemoPage extends StatelessWidget {
const ScopedModelDemoPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('scopedModel'),
centerTitle: true,
),
body: ScopedModel<CounterModel>(
model: CounterModel(),
// 不运用 ScopedModelDescendant 的用法
// child: Builder(
// builder: (context) {
// return Column(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Text('${CounterModel.of(context).counter}'),
// OutlinedButton(
// onPressed: CounterModel.of(context).increment,
// child: Text('add1'),
// ),
// ],
// );
// },
// ),
child: NewWidget(),
),
);
}
}
class NewWidget extends StatelessWidget {
const NewWidget({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('${CounterModel.of(context).counter}'),
OutlinedButton(
onPressed: CounterModel.of(context).increment,
// onPressed: model.increment,
child: Text('add'),
),
],
),
);
}
}
class CounterModel extends Model {
int _counter = 0;
int get counter => _counter;
static CounterModel of(BuildContext context) =>
ScopedModel.of<CounterModel>(context, rebuildOnChange: true);
void increment() {
_counter++;
notifyListeners();
}
}
是不是很意外?首要起作用的是下面这一段代码:
独自提取出来 Widget,能够获取到正确的 context,从而能够获取到离他最近的父 Inherited
widgets 实例。
四、完毕
以上便是我对 scoped_model 的运用以及部分源码的解读,假如有不足之处,还请指导。 最终,咱们也能够考虑一下,咱们是怎么经过 context
就能获取到同享的 Model 呢?