主题列表:juejin, github, smartblue, cyanosis, channing-cyan, fancy, hydrogen, condensed-night-purple, greenwillow, v-green, vue-pro, healer-readable, mk-cute, jzman, geek-black
贡献主题:https://github.com/xitu/juejin-markdown-themes
theme: juejin
highlight:
1.前置知识
先对
ListView
组件做个测试,这是一个色块列表,其中每个Item
是一个自定义的StatefulWidget
,名为ColorBox
,其中状态量是Checkbox
的选择情况,点击时可切换选中状态
。
| 色块列表 | 色块列表可选中 | | -------- | -------------- | | | |
在
_ColorBoxState#initState
和_ColorBoxState#dispose
回调方法中分别打印信息。
```dart class ColorBox extends StatefulWidget { final Color color; final int index;
ColorBox({Key key, this.color, this.index}) : super(key: key);
@override _ColorBoxState createState() => _ColorBoxState(); }
class _ColorBoxState extends State { bool _checked = false;
@override void initState() { super.initState(); checked = false; print('-----ColorBoxState#initState---${widget.index}-------'); }
@override void dispose() { print('-----_ColorBoxState#dispose---${widget.index}-------'); super.dispose(); }
@override Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
height: 50,
color: widget.color,
child: Row(
children: [
SizedBox(width: 60),
buildCheckbox(),
buildInfo(),
],
),
);
}
Text buildInfo() => Text( "index ${widget.index}: ${colorString(widget.color)}", style: TextStyle(color: Colors.white, shadows: [ Shadow(color: Colors.black, offset: Offset(.5, .5), blurRadius: 2) ]), );
Widget buildCheckbox() => Checkbox( value: _checked, onChanged: (v) { setState(() { _checked = v; }); }, );
String colorString(Color color) => "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; } ```
使用
ListView.builder
构建色块列表。
```dart void main() => runApp(MyApp());
class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: HomePage()); } }
class HomePage extends StatelessWidget { final List data = [ Colors.purple[50], Colors.purple[100], Colors.purple[200], Colors.purple[300], Colors.purple[400], Colors.purple[500], Colors.purple[600], Colors.purple[700], Colors.purple[800], Colors.purple[900], Colors.red[50], Colors.red[100], Colors.red[200], Colors.red[300], Colors.red[400], Colors.red[500], Colors.red[600], Colors.red[700], Colors.red[800], Colors.red[900], ];
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Container( height: 300, child: ListView.builder( itemCount: data.length, itemBuilder: (_, index) => ColorBox( color: data[index], index: index, ), ), ), ); } } ```
运行后可以发现,屏幕上只显示了 5 个 item ,但是初始化了 10 个,说明
ListView
是会预先初始化
后面一定数目 item 的状态类。通过cacheExtent
可以控制预先加载的数量,比如 item 高 50 ,cacheExtent = 50 *3
就会预加载 3 个。
然后滑动一下列表,看一下 State 方法回调的情况。在
下滑到底
时,可以看到在13
之后0
被 dispose 了,然后前面几个 item 随着滑动被逐步dispose
。 后面上滑到顶
时,前面的 State 又会被逐渐初始化。
| 下滑到底 | 上滑到顶 | | -------- | -------------- | | | |
所以一个现象就会呼之欲出:
状态丢失
。
| 下滑到底 | 上滑到顶 | | -------- | -------------- | | | |
2. 保持 State 状态
你可能会发现
ListView
中存在一个addAutomaticKeepAlives
属性,但是用起来似乎没有什么效果,可能很多人都不知道它的真正作用是什么,这个暂且按下不表
。先看如何使State
保持状态。
```dart class _ColorBoxState extends State with AutomaticKeepAliveClientMixin { // [1]. with AutomaticKeepAliveClientMixin bool _checked = false;
@override bool get wantKeepAlive => true; // [2] 是否保持状态
@override
Widget build(BuildContext context) { super.build(context); // [3] 在 _ColorBoxState#build 中 调用super.build ```
用法很简单,将
_ColorBoxState with AutomaticKeepAliveClientMixin
,实现抽象方法wantKeepAlive
,返回 true 表示可以保持状态,反正则否。效果如下:
| wantKeepAlive:true | wantKeepAlive:false | | -------- | -------------- | | | |
是不是感觉很神奇,可能一般的介绍文章到这里就结束了,毕竟已经解决了问题。但可惜,这是在我的 bgm 中。我轻轻地将
addAutomaticKeepAlives 置为 false (默认true)
。 然后,即使_ColorBoxState
的wantKeepAlive 为 true
也无法保持
状态,这就说明addAutomaticKeepAlives
是有作用的。
dart child: ListView.builder( addAutomaticKeepAlives: false,
3. List#addAutomaticKeepAlives
做了什么
下面就来追一下
addAutomaticKeepAlives
是干嘛的。可以看出ListView.builder
中的入参addAutomaticKeepAlives
是 传给SliverChildBuilderDelegate
的。
dart ---->[ListView#builder]---- ListView.builder({ // 略... bool addAutomaticKeepAlives = true, // 略... }) : assert(itemCount == null || itemCount >= 0), assert(semanticChildCount == null || semanticChildCount <= itemCount), childrenDelegate = SliverChildBuilderDelegate( itemBuilder, childCount: itemCount, addAutomaticKeepAlives: addAutomaticKeepAlives, // <--- 入参 addRepaintBoundaries: addRepaintBoundaries, addSemanticIndexes: addSemanticIndexes, ),
在
SliverChildBuilderDelegate
类中的addAutomaticKeepAlives
属性中可以看出,该属性的作用为:是否为每个 child 包裹 AutomaticKeepAlive 组件。
```dart ---->[SliverChildBuilderDelegate]---- /// Whether to wrap each child in an [AutomaticKeepAlive]. /// 是否为每个 child 包裹 AutomaticKeepAlive 组件
/// Typically, children in lazy list are wrapped in [AutomaticKeepAlive] /// widgets so that children can use [KeepAliveNotification]s to preserve /// their state when they would otherwise be garbage collected off-screen.
/// 通常,懒加载列表中的 children 被 AutomaticKeepAlive 组件包裹, /// 以便children可以使用 [KeepAliveNotification] 来保存它们的状态, /// 否则它们将在屏幕外会被作为垃圾收集。
/// /// This feature (and [addRepaintBoundaries]) must be disabled if the children /// are going to manually maintain their [KeepAlive] state. It may also be /// more efficient to disable this feature if it is known ahead of time that /// none of the children will ever try to keep themselves alive.
/// 如果子节点要手动维护它们的[KeepAlive]状态,则必须禁用这个特性(和[addRepaintBoundaries])。 /// 如果提前知道所有子节点都不会试图维持自己的生命,禁用此功能可能会更有效。
/// Defaults to true. final bool addAutomaticKeepAlives; ```
可以看出,
SliverChildBuilderDelegate#build
中,当addAutomaticKeepAlives=true
时,会把 child 套上一层AutomaticKeepAlive
组件。
dart ---->[SliverChildBuilderDelegate#build]---- @override Widget build(BuildContext context, int index) { // 略... if (addAutomaticKeepAlives) child = AutomaticKeepAlive(child: child); return KeyedSubtree(child: child, key: key); }
到这里可以看出
AutomaticKeepAlive
组件是保持 State 的关键之一。所以保持状态并非只是AutomaticKeepAliveClientMixin
的功劳。可以得出AutomaticKeepAliveClientMixin
和AutomaticKeepAlive
一定是故(jian)事(qing)
。
4.AutomaticKeepAliveClientMixin
做了什么
可以它只能用于
State
的子类之中。在initState
中看出如果wantKeepAlive
为 true,则会执行_ensureKeepAlive
,这也是wantKeepAlive
抽象方法的价值所在。
```dart mixin AutomaticKeepAliveClientMixin on State { // 可监听对象 KeepAliveHandle _keepAliveHandle;
@override void initState() { super.initState(); if (wantKeepAlive) _ensureKeepAlive(); } // 昝略... } ```
其中有一个
KeepAliveHandle
类型的成员变量。KeepAliveHandle
继承自ChangeNotifier
,也就是一个Listenable
可监听对象。通过release
方法来触发事件。注释说,此方法被触发时,就表示该组件不再需要保持状态
了。
dart class KeepAliveHandle extends ChangeNotifier { /// Trigger the listeners to indicate that the widget /// no longer needs to be kept alive. void release() { notifyListeners(); } }
现在来看
_ensureKeepAlive
,实例化KeepAliveHandle
,创建KeepAliveNotification
对象并调用dispatch
方法。
dart void _ensureKeepAlive() { assert(_keepAliveHandle == null); _keepAliveHandle = KeepAliveHandle(); KeepAliveNotification(_keepAliveHandle).dispatch(context); }
在
deactivate
中_releaseKeepAlive
。前面看到_keepAliveHandle
执行 release 是,会通知监听者 不再需要保持状态。build
中也是确保在_keepAliveHandle
为 null 时,执行_ensureKeepAlive
,这也是为什么要调用super.build
的原因。
```dart @override void deactivate() { if (_keepAliveHandle != null) _releaseKeepAlive(); super.deactivate(); }
void _releaseKeepAlive() { _keepAliveHandle.release(); _keepAliveHandle = null; }
@mustCallSuper @override Widget build(BuildContext context) { if (wantKeepAlive && _keepAliveHandle == null) _ensureKeepAlive(); return null; } ```
这样看来,整个逻辑也并不是非常复杂。最重要的就是创建
KeepAliveNotification
执行dispatch 方法。来看一下源码中对这几个重要类的解释:
AutomaticKeepAlive
监听mixin
发送的信息KeepAliveNotification
由mixin
发送的通知AutomaticKeepAliveClientMixin
很明显,就是用来发送保活信息的客户端(Clinet)
为了加深理解,我们完全可以把核心逻辑自己写出来。如下,这样操作,即使不混入
AutomaticKeepAliveClientMixin
,也可以实现状态的保持。
```dart class _ColorBoxState extends State { bool _checked = false;
KeepAliveHandle _keepAliveHandle;
void ensureKeepAlive() { _keepAliveHandle = KeepAliveHandle(); KeepAliveNotification(keepAliveHandle).dispatch(context); }
void releaseKeepAlive() { if (keepAliveHandle == null) return; _keepAliveHandle.release(); _keepAliveHandle = null; }
@override void initState() {
super.initState();
_checked = false;
_ensureKeepAlive();
print('-----_ColorBoxState#initState---${widget.index}-------');
}
@override void deactivate() { _releaseKeepAlive(); super.deactivate(); }
@override void dispose() { print('-----_ColorBoxState#dispose---${widget.index}-------'); super.dispose(); }
@override Widget build(BuildContext context) { if (_keepAliveHandle == null) _ensureKeepAlive(); //略... ```
那
AutomaticKeepAliveClientMixin
存在的意义是什么,当然是方便使用
啦。我们也可以反过来想一想
,如果某个场景围绕着 State 的生命周期有什么固定逻辑,我们也可以仿照这样的方式,使用一个 mixin 为 State 增加某些功能。 很多时候,我们得到了想要的目的
,就不会进一步去探究了,以至于只停留在会了而已
。遇到问题,也只想问出解决方案
。有时再往前踏出一步,你将见到完全不一样的风采
。
5. AutomaticKeepAliveClientMixin
除了 ListView 还能用在哪里?
GridView
,和 ListView 一样,内部使用SliverChildBuilderDelegate
dart @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Container( height: 300, child: GridView.builder( gridDelegate:SliverGridDelegateWithFixedCrossAxisCount( childAspectRatio: 1, crossAxisCount: 2, ), itemCount: data.length, itemBuilder: (_, index) => ColorBox( color: data[index], index: index, ), ), ), ); }
由于
GridView
组件是基于SliverGrid
组件实现的,所以SliverGrid
也可以。同理,ListView
组件基于SliverFixedExtentList
或SliverList
组件实现的,它们也可以。
PageView
也使用了SliverChildBuilderDelegate
,所以也具有相关特性。不过没有对外界暴露设置addAutomaticKeepAlives
的途径,永远为true。
dart @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: Container( height: 300, child: PageView.builder( itemCount: data.length, itemBuilder: (_, index) => ColorBox( color: data[index], index: index, ), ), ), ); }
TabBarView
组件内部基于PageView
实现,所以也适用。
```dart @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: DefaultTabController( length: data.length, child: Column( children: [ _buildTabBar(), Container( color: Colors.purple, width: MediaQuery.of(context).size.width, height: 200, child: _buildTableBarView()) ], ), ), ); }
Widget _buildTabBar() => TabBar( onTap: (tab) => print(tab), labelStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), unselectedLabelStyle: TextStyle(fontSize: 16), isScrollable: true, labelColor: Colors.blue, indicatorWeight: 3, indicatorPadding: EdgeInsets.symmetric(horizontal: 10), unselectedLabelColor: Colors.grey, indicatorColor: Colors.orangeAccent, tabs: data.map((e) => Tab(text: colorString(e))).toList(), ); Widget _buildTableBarView() => TabBarView( children: data .map((e) => Center( child: ColorBox( color: e, index: data.indexOf(e), ))) .toList());
String colorString(Color color) => "#${color.value.toRadixString(16).padLeft(8, '0').toUpperCase()}"; ```
这些就是常用的有保持状态需求的组件, 至于什么时候需要进行状态的保存,我只能说:
当你饿了,你自然会知道什么时候想吃饭
。
@张风捷特烈 2020.12.18 未允禁转
我的公众号:编程之王
联系我--邮箱:1981462002@qq.com -- 微信:zdl1994328
~ END ~