Flutter Widget 生命周期 & key探究

这篇具有很好参考价值的文章主要介绍了Flutter Widget 生命周期 & key探究。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Widget

在Flutter中,一切皆是Widget(组件),Widget的功能是“描述一个UI元素的配置数据”,它就是说,Widget其实并不是表示最终绘制在设备屏幕上的显示元素,它只是描述显示元素的一个配置数据。

实际上,Flutter中真正代表屏幕上显示元素的类是 Element,也就是说Widget 只是描述 Element 的配置数据。并且一个 Widget 可以对应多个 Element,因为同一个 Widget 对象可以被添加到 UI树的不同部分,而真正渲染时,UI树的每一个 Element 节点都会对应一个 Widget 对象。

两种Widget模型

StatelessWidget

StatelessWidget用于不需要维护状态的场景,其对应的Element是StatelessElement
Flutter Widget 生命周期 & key探究
Flutter Widget 生命周期 & key探究

StatefulWidget

Flutter Widget 生命周期 & key探究
Flutter Widget 生命周期 & key探究
相反,StatefulWidget用于需要维护状态的场景,其对应的Element是StatefulElement,StatefulElement持有State
createState() 用于创建和StatefulWidget相关的状态,它在StatefulWidget的生命周期中可能会被多次调用。
例如,当一个StatefulWidget同时插入到widget树的多个位置时,Flutter framework就会调用该方法为每一个位置生成一个独立的State实例,其实,本质上就是一个StatefulElement对应一个State实例。

生命周期

理论

Flutter 中说的生命周期,是独指有状态组件(StatefulWidget)的生命周期,对于无状态组件生命周期只有一次 build 这个过程,也只会渲染一次,StatefulWidget生命周期图如下:
Flutter Widget 生命周期 & key探究
Flutter 中的生命周期,包含以下几个阶段:

  • createState :该函数为 StatefulWidget 中创建 State 的方法,当 StatefulWidget 被调用时会立即执行 createState 。
  • initState :该函数为 State 初始化调用,紧接着createState之后调用,可以在此期间执行 State 各变量的初始赋值,同时也可以在此期间与服务端交互
  • didChangeDependencies :第一种情况是StatefulElement mount时会回调,这种情况会紧跟initState被回调
    还有一种情况是当State对象的“依赖”发生变化时会被调用,这种依赖是指通过context.dependOnInheritedWidgetOfExactType进行的依赖
    Flutter Widget 生命周期 & key探究
    Flutter Widget 生命周期 & key探究
    Flutter Widget 生命周期 & key探究
    Flutter Widget 生命周期 & key探究
  • build :主要是返回需要渲染的 Widget ,由于 build 会被调用多次,因此在该函数中只能做返回 Widget 相关逻辑
  • reassemble, 在 debug 模式下,每次热重载都会调用该函数,因此在 debug 阶段可以在此期间增加一些 debug 代码,来检查代码问题。
  • didUpdateWidget ,在widget重新构建时,Flutter framework会调用Widget.canUpdate来检测Widget树中同一位置的新旧节点,然后决定是否需要更新,如果Widget.canUpdate返回true则会调用此回调。Widget.canUpdate会在新旧widget的key和runtimeType同时相等时会返回true,也就是说在在新旧widget的key和runtimeType同时相等时didUpdateWidget()就会被调用。父组件发生 build 的情况下,子组件该方法才会被调用,其次该方法调用之后一定会再调用本组件中的 build 方法。
  • deactivate ,在组件被移除节点后会被调用,如果该组件被移除节点,然后未被插入到其他节点时,则会继续调用 dispose 永久移除。
  • dispose ,永久移除组件,并释放组件资源。

Flutter 生命周期的整个过程可以分为四个阶段

  1. 初始化阶段:createState 和 initState
  2. 组件创建阶段:didChangeDependencies didUpdateWidget 和 build
  3. 组件销毁阶段:deactivate 和 dispose

实例

class LifeCycleTest extends StatefulWidget {
  final String TAG = "LifeCycleTest";

  
  State<StatefulWidget> createState() {
    print('$TAG createState');
    return LifeCycleTestState();
  }
}

class LifeCycleTestState extends State<LifeCycleTest> {
  
  void initState() {
    print('${widget.TAG} initState');
    super.initState();
  }

  
  void reassemble() {
    print('${widget.TAG} reassemble');
    super.reassemble();
  }

  
  void didChangeDependencies() {
    print('${widget.TAG} didChangeDependencies');
    super.didChangeDependencies();
  }

  
  void didUpdateWidget(covariant LifeCycleTest oldWidget) {
    print('${widget.TAG} didUpdateWidget');
    super.didUpdateWidget(oldWidget);
  }

  
  void deactivate() {
    print('${widget.TAG} deactivate');
    super.deactivate();
  }

  
  void dispose() {
    print('${widget.TAG} dispose');
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    print('${widget.TAG} build');
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            GestureDetector(
              onTap: () {
                setState(() {});
              },
              child: Container(
                width: 100,
                height: 100,
                color: Colors.red,
              ),
            ),
            MyInheritedWidget(LifeCycleTestChild(), 20)
          ],
        ),
      ),
    );
  }
}

class LifeCycleTestChild extends StatefulWidget {
  final String TAG = "LifeCycleTestChild";

  
  State<StatefulWidget> createState() {
    print('$TAG createState');
    return LifeCycleTestChildState();
  }
}

class LifeCycleTestChildState extends State<LifeCycleTestChild> {
  
  void initState() {
    print('${widget.TAG} initState');
    super.initState();
  }

  
  void reassemble() {
    print('${widget.TAG} reassemble');
    super.reassemble();
  }

  
  void didChangeDependencies() {
    print('${widget.TAG} didChangeDependencies');
    super.didChangeDependencies();
  }

  
  void didUpdateWidget(covariant LifeCycleTestChild oldWidget) {
    print('${widget.TAG} didUpdateWidget');
    super.didUpdateWidget(oldWidget);
  }

  
  void deactivate() {
    print('${widget.TAG} deactivate');
    super.deactivate();
  }

  
  void dispose() {
    print('${widget.TAG} dispose');
    super.dispose();
  }

  
  Widget build(BuildContext context) {
    print('${widget.TAG} build');
    var dependOnInheritedWidgetOfExactType =
        context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
    return Container(
      child: Text("${(dependOnInheritedWidgetOfExactType as MyInheritedWidget).count}"),
    );
  }
}

class MyInheritedWidget extends InheritedWidget {
  final int count;

  MyInheritedWidget(
    Widget child,
    this.count,
  ) : super(child: child);

  
  bool updateShouldNotify(covariant InheritedWidget oldWidget) {
    return true;
  }
}

Flutter Widget 生命周期 & key探究
Flutter Widget 生命周期 & key探究
Flutter Widget 生命周期 & key探究
Flutter Widget 生命周期 & key探究
上面图说明了几个点

  • 第二个图:didChangeDependencies在widget第一次初始化的时候都会调用
  • 第二个图:LifeCycleTest组件发生build,LifeCycleTestChild子组件调用didUpdateWidget,自身并没有调用didUpdateWidget,第三个图,热加载后都调用了didUpdateWidget,说明了父组件发生 build 的情况下,子组件该方法才会被调用
  • 第三个图:热加载后,只有LifeCycleTestChild调用了didChangeDependencies,说明通过context.dependOnInheritedWidgetOfExactType进行的依赖会调用该方法

Getx生命周期

先看下Controller的集成层级
Flutter Widget 生命周期 & key探究
再看下对应类的定义
Flutter Widget 生命周期 & key探究
Flutter Widget 生命周期 & key探究
Flutter Widget 生命周期 & key探究
Flutter Widget 生命周期 & key探究
其中 GetxController只是有个 update 方法用于通知组件刷新。
在 DisposableInterface 中覆盖了onInit 方法,实际多干了一件事,就是监听第一帧回调,等第一帧回调过来之后再调用onReady
然后我们再看下这些生命周期分别是在什么时候调用的
Flutter Widget 生命周期 & key探究
Flutter Widget 生命周期 & key探究
Flutter Widget 生命周期 & key探究

  • onInit:组件在内存分配后会被马上调用,可以在这个方法对 controller 做一些初始化工作。
  • onReady:在 onInit 一帧后被调用,适合做一些导航进入的事件,例如对话框提示、SnackBar 或异步网络请求。
  • onClose:在 onDelete 方法前调用、用于销毁 controller 使用的资源,例如关闭事件监听,关闭流对象,或者销毁可能造成内存泄露的对象,例如 TextEditingController,AniamtionController。也适用于将数据进行离线持久化。
    Flutter Widget 生命周期 & key探究
    所以有了 GetxController 的生命周期后,我们就可以完全替换掉 StatefulWidget 了。
    onInit 或 onReady替换 initState
    onClose 替换 dispose,比如关闭流

Key

key的作用是:控制Element树上的Element是否被复用
如果两个widget的runtimeType和key相等(用==比较),那么原本指向旧widge的element,它的指针会指向新的widget上(通过Element.update方法)。如果不相等,那么旧element会从树上移除,根据当前新的widget重新构建新element,并加到树上指向新widget。
Flutter Widget 生命周期 & key探究
Flutter Widget 生命周期 & key探究
基于Element的复用机制的解释

在Flutter中,Widget是不可变的,它仅仅作为配置信息的载体而存在,并且任何配置或者状态的更改都会导致Widget的销毁和重建,但好在Widget本身是非常轻量级的,因此实际耗费的性能很小。与之相反,RenderObject就不一样了,实例化一个RenderObject的成本是非常高的,频繁地实例化和销毁RenderObject对性能的影响非常大,因此为了高性能地构建用户界面,Flutter使用Element的复用机制来尽可能地减少RenderObject的频繁创建和销毁。当Widget改变的时候,Element会通过组件类型以及对应的Key来判断旧的Widget和新的Widget是否一致:

1、如果某一个位置的旧Widget和新Widget不一致,就会重新创建Element,重建Element的同时也重建了RenderObject;
2、如果某一个位置的旧Widget和新Widget一致,只是配置发生了变化,比如组件的颜色变了,此时Element就会被复用,而只需要修改Widget对应的Element的RenderObject中的颜色设置即可,无需再进行十分耗性能的RenderObject的重建工作。
Flutter Widget 生命周期 & key探究

Flutter Widget 生命周期 & key探究

分类

flutter 中的key总的来说分为以下两种:

  • 局部键(LocalKey):ValueKey、ObjectKey、UniqueKey
  • 全局键(GlobalKey):GlobalObjectKey

ValueKey

ValueKey是通过某个具体的Value值来做区分的Key,如下:

key:ValueKey(1),
key:ValueKey("2"),
key:ValueKey(true),
key:ValueKey(0.1),
key:ValueKey(Person()), // 自定义类实例

可以看到,ValueKey的值可以是任意类型,甚至可以是我们自定义的类的实例。判断2个ValueKey是否相等是根据里面的value是否来判断的,如果value是自定义类,则可以通过重写自定义类的操作符来实现

例如,现在有一个展示所有学生信息的ListView列表,每一项itemWidget所对应的学生对象均包含某个唯一的属性,例如学号、身份证号等,那么这个时候就可以使用ValueKey,其值就是对应的学号或者身份证号。

Flutter Widget 生命周期 & key探究

ObjectKey

ObjectKey的使用场景如下:
现有一个所有学生信息的ListView列表,每一项itemWidget对应的学生对象不存在某个唯一的属性(比如学号、身份证号),任一属性均有可能与另外一名学生重复,只有多个属性组合起来才能唯一的定位到某个学生,那么此时使用ObjectKey就最合适不过了。

ObjectKey判断两个Key是否相同的依据是:两个对象是否具有相同的内存地址,不论自定义对象是否重写了==运算符判断,均会被视为不同的Key

Flutter Widget 生命周期 & key探究

UniqueKey

顾名思义,UniqueKey是一个唯一键,不需要参数,并且每一次刷新都会生成一个新的Key。
一旦使用UniqueKey那么就不存在Element复用了

GlobalKey

GlobalKey是全局唯一的键,一般而言,GlobalKey有如下几种用途:

  • 获取配置、状态以及组件Element
    • _globalKey.currentWidget:获取当前组件的配置信息(存在widget树中)
    • _globalKey.currentState:获取当前组件的状态信息(存在Element树中)
    • _globalKey.currentContext:获取当前组件的Element
  • 实现组件的局部刷新
    将需要单独刷新的widget从复杂的布局中抽离出去,然后通过传GlobalKey引用,这样就可以通过GlobalKey实现跨组件的刷新了。

Flutter Widget 生命周期 & key探究
Flutter Widget 生命周期 & key探究
Flutter Widget 生命周期 & key探究

key作用示例

一般情况下我们不使用key,程序也是能正常运行的,只有部分特殊情况下需要使用key,下面我们看一个例子

import 'dart:math';

import 'package:flutter/material.dart';

class PositionedTiles extends StatefulWidget {
  
  State<StatefulWidget> createState() => PositionedTilesState();
}

class PositionedTilesState extends State<PositionedTiles> {
  late List<Widget> tiles;

  
  void initState() {
    super.initState();
    tiles = [
      // StatefulColorfulTile(),
      // StatefulColorfulTile(),
      // StatefulColorfulTile(key: UniqueKey()),
      // StatefulColorfulTile(key: UniqueKey()),
      StatelessColorfulTile(),
      StatelessColorfulTile(),
    ];
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: tiles,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.sentiment_very_satisfied),
        // child: Icon(Icons.sentiment_very_dissatisfied),
        onPressed: swapTiles,
      ),
    );
  }

  void swapTiles() {
    setState(() {
      tiles.insert(1, tiles.removeAt(0));
    });
  }
}

// ignore: must_be_immutable
class StatelessColorfulTile extends StatelessWidget {
  Color color = ColorUtil.randomColor();  //color属性直接在widget中

  
  Widget build(BuildContext context) {
    return Container(color: color, child: Padding(padding: EdgeInsets.all(70.0)));
  }
}

class StatefulColorfulTile extends StatefulWidget {
  StatefulColorfulTile({Key? key}) : super(key: key);

  
  State<StatefulWidget> createState() => StatefulColorfulTileState();
}

class StatefulColorfulTileState extends State<StatefulColorfulTile> {
  Color? color; //color属性在State中

  
  void initState() {
    super.initState();
    color = ColorUtil.randomColor();
    print('initState');
  }

  
  Widget build(BuildContext context) {
    return Container(color: color, child: Padding(padding: EdgeInsets.all(70.0)));
  }
}

class ColorUtil {
  static Color randomColor() {
    var red = Random.secure().nextInt(255);
    var greed = Random.secure().nextInt(255);
    var blue = Random.secure().nextInt(255);
    return Color.fromARGB(255, red, greed, blue);
  }
}

上面的代码效果如下,可以看到使用StatelessColorfulTile时,点击按钮后两个色块能成功交换:

当我们把代码换成下面这样
Flutter Widget 生命周期 & key探究
神奇的事情发生了,点击交换按钮没有任何反应
那在使用StatefulColorfulTile的前提下,如何让色块再次点击按钮后能发生交换呢?我猜聪明的你已经想到了,就是设置key属性,即把代码改成下面这个样子
Flutter Widget 生命周期 & key探究
下面我们来解释下为什么会出现这样的结果:

  1. 为什么StatelessWidget的能交换
    Flutter Widget 生命周期 & key探究
    Flutter Widget 生命周期 & key探究
    当代码调用PositionedTiles.setState交换两个Widget后,flutter会从上到下逐一对比Widget树和Element树中的每个节点,如果发现节点的runtimeType和key一致的话(这里没有key,因此只对比runtimeType),那么就认为该Element仍然是有效的,可用复用,于是只需要更改Element的指针,就可以直接复用
    对于StatelessWidget中的color信息是直接在widget中的,那widget重新build直接就更新了颜色

  2. 为啥StatefulColorfulTile要加key才能交换
    StatefulWidget的color属性是放在State中的,我们上面说过State被Element管理
    我们先看下不带key时的树结构

首先还是Widget更新后,flutter会根据runtimeType和key比较Widget从而判断是否需要重新构建Element,这里key为空,只比较runtimeType,比较结果必然相等,所以Element直接复用。
StatefulColorfulTile在重新渲染时,Color属性不再是从Widget对象(即自身)里获取,而是从Element的State里面获取,而Element根本没发生变化,所以取到的Color也没有变化,最终就算怎么渲染,颜色都是不变的,视觉效果上也就是两个色块没有交换了。
接着看有了key之后的树结构

交换前:
Flutter Widget 生命周期 & key探究

交换后,发现两边key不相等,于是尝试在Element 列表里面查找是否还有相同的key的Element,发现有,于是重新排列Element让相同key的配对
Flutter Widget 生命周期 & key探究
rebuild后,Element已交换,重新渲染后视觉上就看到两个色块交换位置了:
Flutter Widget 生命周期 & key探究
在这种加了key又交换位置的情况下,Element和widget都是直接复用的,所以点击交换位置,widget没有触发build方法,原因在于canUpdate方法返回false,didUpdateWidget也没有回调,build方法也不会被触发

接下来我们在原来的demo上做些小改动,在要交换的2个Widget外面分别套上Padding,我们看下效果:
Flutter Widget 生命周期 & key探究
我们发现每次点击交换位置,2个Widget都变成了新的颜色,即两个 Widget 的 Element 并不是交换顺序,而是被重新创建了

当交换子节点的位置时,Flutter 的 element-to-widget 匹配逻辑一次只会检查树的一个层级。
在Column这一层级,padding 部分的 runtimeType 并没有改变,且不存在 Key。Element复用,然后再比较下一个层级。由于内部的 StatefulColorfulTile 存在 key,且现在的层级在 padding 内部,该层级没有多子 Widget。canUpdate 返回 flase,Flutter 将会认为这个 Element 需要被替换。然后重新生成一个新的 Element 对象装载到 Element 树上替换掉之前的 Element。第二个 Widget 同理。

所以为了解决这个问题,我们需要将 key 放到 Padding 的 这一层级就可以了

根据上面的例子我们能了解到:如果要在有状态的、类型相同、同一层级的 widget 集合上进行添加、删除、排序等操作,可能需要使用到 key。文章来源地址https://www.toymoban.com/news/detail-480888.html

到了这里,关于Flutter Widget 生命周期 & key探究的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Flutter生命周期小结

    Flutter 中的生命周期,包含以下几个阶段: createState ,在 StatefulWidget 中创建 State 的方法,当 StatefulWidget 调用时会触发 createState 。 initState ,在 State 初始化时调用,因此可以在此期间执行 State 各变量的初始赋值,同时也可以在此期间与服务端交互,获取服务端数据后调用

    2024年02月12日
    浏览(37)
  • Flutter笔记:Flutter的应用生命周期状态(lifecycleState)管理

    Flutter笔记 Flutter的应用生命周期状态(lifecycleState)管理 作者 : 李俊才 (jcLee95):https://blog.csdn.net/qq_28550263 邮箱 : 291148484@163.com 本文地址 :https://blog.csdn.net/qq_28550263/article/details/134127670 【介绍】: WidgetsBinding.instance 是Flutter中用于管理应用程序事件和生命周期的重要工具

    2024年02月06日
    浏览(42)
  • Flutter中的AppLifecycleListener:应用生命周期监听器介绍及使用

    引言 当你在Flutter中需要监听应用程序的生命周期变化时,可以使用 AppLifecycleListener 。在Flutter 3.13中, AppLifecycleListener 被添加到Framework中,用于监听应用程序的生命周期变化,并响应退出应用程序的请求等支持。 在Flutter 3.13之前,我们通常使用 WidgetsBindingObserver 的 didChange

    2024年01月20日
    浏览(47)
  • Flutter学习四:Flutter开发基础(一)Widget

    目录 0 引言 1 Widget 简介 1.1 Widget 概念 1.2 Widget 接口 1.3 Flutter中的四棵树 1.4 StatelessWidget 1.4.1 简介 1.4.2 Context上下文 1.5 StatefulWidget 1.6  State 1.6.1 简介 1.6.2 State生命周期 1.7  在 widget 树中获取State对象 1.7.1 通过Context获取 1.7.2 通过GlobalKey获取 1.8 通过 RenderObject 自定义 Wid

    2024年02月16日
    浏览(47)
  • Flutter-基础Widget

    ​ 在Flutter中,几乎所有的对象都是一个 Widget ,与原生开发中的 控件 不同的是,Flutter中的 widget 的概念更广泛,它不仅可以表示UI元素,也可以表示一些功能性的组件如:用于手势检测的 GestureDetector widget、用于应用主题数据传递的 Theme 等等。由于Flutter主要就是用于构建用

    2024年02月15日
    浏览(44)
  • flutter笔记-万物皆是widget

    这篇文章后就不见写了,学flutter主要是为了更好的使用 flutter-webrtc,所以到这里基本就了解了大部分的知识,后续边用边查; 在flutter中所有的view都叫widget,类似文本组件Text也是集成自widget; 创建第一个项目如下: runApp传入的参数就是一个Widget;所以我们可以传入Text,示例

    2024年04月28日
    浏览(45)
  • 微信小程序——生命周期,生命周期的分类,页面生命周期,生命周期函数的分类,应用的生命周期函数,页面的生命周期函数,wxs脚本概述

    生命周期( Life Cycle )是指一个对象从创建-运行-销毁的整个阶段,强调的是一个时间段。 例如: .张三出生,表示这个人生命周期的开始 .张三离世,表示这个人生命周期的结束 .中间张三的一生,就是张三的生命周期 我们可以把每个小程序运行的过程,也概括为生命周

    2024年02月01日
    浏览(60)
  • Flutter源码分析笔记:Widget类源码分析

    Flutter源码分析笔记 Widget类源码分析 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at: https://jclee95.blog.csdn.net Email: 291148484@163.com. Shenzhen China Address of this article: https://blog.csdn.net/qq_28550263/article/details/132259681 【介绍】:本文记录阅读与分析Flutter源码 - Widget类源码分析。 Widget类是Flu

    2024年02月12日
    浏览(44)
  • flutter开发实战-父子Widget组件调用方法

    flutter开发实战-父子Widget组件调用方法 在最近开发中遇到了需要父组件调用子组件方法,子组件调用父组件的方法。这里记录一下方案。 父组件使用globalKey.currentState调用子组件具体方法,子组件通过方法回调callback方法调用父组件的方法。 例如示例中的 例如父组件 父组件使

    2024年02月15日
    浏览(42)
  • flutter开发实战-RepaintBoundary实现Widget截图功能

    flutter开发实战-RepaintBoundary实现Widget截图功能 在开发中,遇到需要使用截图,像iOS可以截图UIView获取到UIImage,在flutter中可以使用RepaintBoundary实现截图功能 相机拍摄的图片: RepaintBoundary截图后的图片 RepaintBoundary是绘制边界。 如果CustomPaint有子节点,为了避免子节点不必要的

    2024年02月15日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包