前言
进入这个帖子,可以先看看前面的介绍哈。
Selector
介绍
selector.dart中有段关于Selector的介绍
/// {@template provider.selector}
....
/// {@endtemplate}
class Selector<A, S> extends Selector0<S> {
}
class Selector0<T> extends SingleChildStatefulWidget {
final ValueWidgetBuilder<T> builder;
final T Function(BuildContext) selector;
final ShouldRebuild<T> _shouldRebuild;
}
我们大致领会到一下关键点。
- Selector相当于Cosumer,但是可以在某些值不变的情况下,防止rebuild。
- selector方法:Selector使用 Provider.of获取共享的数据。数据作为selector方法的入参A,执行selector方法,返回build需要的数据S,返回的数据要尽可能少,能满足build就好。
- shouldRebuild:默认判断前后两次S相等性,来决定是否rebuild。并且也提供了自定义的shouldRebuild方法来判断,参数是前后两次S。
- S:selector的数据,必须是immutable(不可变)的.因此 selector通常返回集合或覆盖了"=="的类。如果需要selector多个值,推荐[tuple](pub.dev/packages/tu…)
immutable:一个类的对象在通过构造方法创建后如果状态不会再被改变,那么它就是一个不可变(immutable)类。它的所有成员变量的赋值仅在构造方法中完成,不会提供任何 setter 方法供外部类去修改。
//tuple举例
Selector<Foo, Tuple2<Bar, Baz>>(
selector: (_, foo) => Tuple2(foo.bar, foo.baz),
builder: (_, data, __) {
return Text('${data.item1} ${data.item2}');
}
)
使用举例
接下来,是一个Selector的使用场景。 Demo仓库地址
效果
页面上有三个字母按钮,统计每个按钮点击次数。点击按钮,则按钮上数字+1。
代码
//首先我们有一个model,做数据处理,ChangeNotifier用到Provider中
class CountModel extends ChangeNotifier {
//key为字母,value为点击次数统计
Map<String, int> contentMap = SplayTreeMap();
//初始化数据
initData() {
contentMap["a"] = 0;contentMap["b"] = 0;contentMap["c"] = 0;
}
//增加字母按钮的点击次数
increment(String content) {
contentMap[content] = contentMap[content] + 1;
//通知刷新
notifyListeners();
}
}
class SelectorDemoWidget extends StatefulWidget {
...
}
class _SelectorDemoWidgetState extends State<SelectorDemoWidget> {
CountModel _model;
@override
void initState() {
super.initState();
//初始化数据
_model = new CountModel()..initData();
}
@override
Widget build(BuildContext context) {
//构建一组字母按钮(CountItemWidget)
List<CountItemWidget> _children = _model.contentMap.keys
.map((key) => CountItemWidget(content: key))
.toList();
return Scaffold(
...
body: ChangeNotifierProvider.value(
value: _model,
child: ListView(children: _children),
));
}
}
//字母按钮
class CountItemWidget extends StatelessWidget {
final String content;
CountItemWidget({this.content});
@override
Widget build(BuildContext context) {
print("CountItemWidget:build");
return Container(
height: 80,
padding: EdgeInsets.all(15),
alignment: Alignment.center,
child: RaisedButton(
onPressed: () =>
Provider.of<CountModel>(context, listen: false).increment(content),
child: Selector<CountModel, int>(
//从 CountModel得到对应字母的count
selector: (context, model) => model.contentMap[content],
//如果前后两次的count不相等,则刷新
shouldRebuild: (preCount, nextCount) => preCount != nextCount,
builder: (context, count, child) {
print("$content Selector:builder");
return Text("$content : $count");
}),
),
);
}
}
源码分析
一个简单的示例就完成啦。
我们来看看Selector内部的实现。
当更新S时,比较前后S值是否相同。 不相同,则重新构建并且缓存住。相同,则使用cache的Widget
解读
//省略了部分代码
typedef ShouldRebuild<T> = bool Function(T previous, T next);
class Selector<A, S> extends Selector0<S> {
Selector({
Key key,
@required ValueWidgetBuilder<S> builder,
//这部分代码重点就是selector,Function参数,如何从A->S
@required S Function(BuildContext, A) selector,
ShouldRebuild<S> shouldRebuild,
Widget child,
}) : assert(selector != null),
super(
key: key,
shouldRebuild: shouldRebuild,
builder: builder,
//写成Provider.of<A>(context),更明确点
selector: (context) => selector(context, Provider.of(context)),
child: child,
);
}
class Selector0<T> extends SingleChildStatefulWidget {
final ValueWidgetBuilder<T> builder;
final T Function(BuildContext) selector;
final ShouldRebuild<T> _shouldRebuild;
}
class _Selector0State<T> extends SingleChildState<Selector0<T>> {
//selector方法 A -> S
T value;
//如果不重新build,返回cache Widget
Widget cache;
//oldWidget --> Selector
Widget oldWidget;
@override
Widget buildWithChild(BuildContext context, Widget child) {
//实际调用过程,我们从示例代码的角度来看: CountModel是A,int是S
//1.CountModel model=Provider.of<CountModel>(context);
//2.int count = model.contentMap[content];count即为selected
final selected = widget.selector(context);
//3.value==selected? value:preCount, selected:nextCount
var shouldInvalidateCache = oldWidget != widget
||(widget._shouldRebuild != null
&& widget._shouldRebuild.call(value, selected))
||(widget._shouldRebuild == null
&& !const DeepCollectionEquality().equals(value, selected));
//4.不相等,则cache需要失效。
if (shouldInvalidateCache) {
//5.生成当次的Widget,并cache住value和Widget
value = selected;
oldWidget = widget;
cache = widget.builder(
context,
selected,
child,
);
}
return cache;
}
}
流程图
适用场景
Selector相较于 Cosumer,提供了更细粒度的刷新控制。
某个组件只关心Model中某个数据变化,可以考虑用Selector,即整体与局部。
特别需要指明的是selector的结果,必须是不可变的对象。 如果同一个对象,只是改变对象属性,那shouldRebuild的两个值永远是相等的。
我们在 前一篇Provider开发应用 提到的点赞帖子刷新单个的场景,同样可以用到Selector
我们列出代码参考
class PostItemWidget2 extends StatelessWidget {
final PostBean post;
final void Function(BuildContext context, PostBean post) click;
const PostItemWidget2({Key key, this.post, this.click}) : super(key: key);
@override
Widget build(BuildContext context) {
print("PostItemWidget2:build");
return GestureDetector(
onTap: () => click?.call(context, post),
child: Container(
height: 80,
child: Row(
children: <Widget>[
Expanded(
child: Text(
"${post?.content}",
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
Container(
width: 50,
child: Selector<PostItemChange, bool>(
selector: (context, itemChange) => post.isLike,
shouldRebuild: (pre, next) => pre != next,
builder: (context, isLike, child) {
return Icon(
Icons.favorite,
color: (isLike ?? false) ? Colors.red : Colors.grey,
);
}),
),
],
),
));
}
}
ItemRefresher
引入
正如 Selector库所指出的
selector的结果,必须是不可变的对象
然而有时,UI层往往会直接使用数据层的对象,而不是转成UI层对象。
例如我们的例子中,PostItemWidget始终对应同一个PostBean。 前后区别就在于 PostBean.isLike发生了变化。同时我们的刷新Notify只是简单的 PostNotifier(id),并不会包含PostBean的全貌。
可以参考Selector自定义一个Item Refresh。
下面这个可以参考
源码
//区别点 是否需要rebuild,是notifier与value结合判断
typedef ShouldRebuild<A, T> = bool Function(A notifier, T value);
class ItemRefresher<A, T> extends SingleChildStatefulWidget {
final ShouldRebuild<A, T> _shouldRebuild;
final T value;
ItemRefresher({
Key key,
//区别点 value有初始值,
@required this.value,
ShouldRebuild<A, T> shouldRebuild,
@required this.builder,
Widget child,
}) : assert(builder != null),
this._shouldRebuild = shouldRebuild,
super(key: key, child: child);
final ValueWidgetBuilder<T> builder;
@override
_ItemRefresherState<A, T> createState() => _ItemRefresherState<A, T>();
}
class _ItemRefresherState<A, T> extends SingleChildState<ItemRefresher<A, T>> {
Widget cache;
Widget oldWidget;
@override
Widget buildWithChild(BuildContext context, Widget child) {
//逻辑与selector类似
A notifier = Provider.of(context);
var shouldInvalidateCache = oldWidget != widget ||
(widget._shouldRebuild != null &&
notifier != null &&
widget._shouldRebuild.call(notifier, widget.value));
if (shouldInvalidateCache) {
oldWidget = widget;
cache = widget.builder(
context,
widget.value,
child,
);
}
return cache;
}
}
使用
定义一个PostNotifier
class PostNotifier with ChangeNotifier {
int id;
}
构建PostItemWidget
Widget _buildListItem(BuildContext context, PostBean post) {
return ItemRefresher<PostNotifier, PostBean>(
value: post,
//判断是否是当前post
shouldRebuild: (notifier, value) =>
(notifier.id != null && notifier.id == value.id),
builder: (context, value, child) {
return PostItemWidget(
post: value,
click: _skipPostDetail,
);
},
);
// return PostItemWidget2(post: post, click: _skipPostDetail);
}