【-Flutter 探索-】AutomaticKeepAliveClientMixin 保持 State 状态


主题列表: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 的选择情况,点击时可切换选中状态

| 色块列表 | 色块列表可选中 | | -------- | -------------- | | image-20201218125140487 | image-20201218125114851 |

_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 个。

image-20201218125933074


然后滑动一下列表,看一下 State 方法回调的情况。在下滑到底时,可以看到在 13 之后 0 被 dispose 了,然后前面几个 item 随着滑动被逐步 dispose。 后面 上滑到顶 时,前面的 State 又会被逐渐初始化。

| 下滑到底 | 上滑到顶 | | -------- | -------------- | | image-20201218130658359 | image-20201218130721619 |

所以一个现象就会呼之欲出: 状态丢失

| 下滑到底 | 上滑到顶 | | -------- | -------------- | | 滑动 | 状态丢失 |

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) 。 然后,即使 _ColorBoxStatewantKeepAlive 为 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 的功劳。可以得出 AutomaticKeepAliveClientMixinAutomaticKeepAlive 一定是 故(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 发送的信息
  • KeepAliveNotificationmixin 发送的通知
  • AutomaticKeepAliveClientMixin 很明显,就是用来发送保活信息的 客户端(Clinet)

image-20201218185125940

为了加深理解,我们完全可以把核心逻辑自己写出来。如下,这样操作,即使不混入 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

image-20201218195650440

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 组件基于 SliverFixedExtentListSliverList 组件实现的,它们也可以。


PageView 也使用了 SliverChildBuilderDelegate ,所以也具有相关特性。不过没有对外界暴露设置addAutomaticKeepAlives 的途径,永远为true。

image-20201218194906862

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 ~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值