Flutter中的Key

news2024/12/24 0:52:46

在Flutter 中,Key 是 几乎所有 widget 都具有的属性。为什么 widget 具有 Key 呢?Key的作用是什么?

什么是 Key

Key是Widget、Element 和 SemanticNodes 的标识符。 Key 是Widget、Element 和 SemanticNodes的唯一标识。例如对于 Widget 在 Widget 树中改变了位置,Key 可以帮助它们保留状态。相对于无状态的Widget,Key对于有状态的 Widget 作用更大。

 Key的使用场景

在添加、删除或重排同一类型的 widget 集合时,Key 可以让这些 widget 保持状态,并且在 widget 树中处于相同的级别。

例子:两个颜色块单击按钮时交换两者位置。

两种实现方式:

第一种实现:widget 是无状态的,色值保存在 widget 本身中。当点击 FloatingActionButton,色块会交换位置。

import 'dart:math';

import 'package:flutter/material.dart';

//色块widget是无状态的
class StatelessColorTitles extends StatelessWidget {
  //色值保存在本身控件中
  var r = Random().nextInt(256);
  var g = Random().nextInt(256);
  var b = Random().nextInt(256);

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 100,
      width: 100,
      color: Color.fromRGBO(r, g, b, 1),
    );
  }
}

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

  @override
  State<StatefulWidget> createState() {
    return _PositionTilesState();
  }
}

class _PositionTilesState extends State<PositionTiles> {
  List<Widget> titles = [];

  @override
  void initState() {
    super.initState();
    titles = [StatelessColorTitles(), StatelessColorTitles()];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: titles,
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.sentiment_very_satisfied),
        onPressed: swapTitles,
      ),
    );
  }

  void swapTitles() {
    setState(() {
      titles.insert(1, titles.removeAt(0));
    });
  }
}

class MyCustomApp extends StatelessWidget {
  const MyCustomApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const PositionTiles(),
    );
  }
}

下面是第二种实现:色块 widget 有状态,色值保存在状态中。为了正确交换平铺位置,我们需要向有状态的 widget 添加 key 参数。

import 'dart:math';

import 'package:flutter/material.dart';

class _StatefulColorTilesState2 extends State<StatefulColorTiles2> {
  //色值保存在本身控件中
  var r = Random().nextInt(256);
  var g = Random().nextInt(256);
  var b = Random().nextInt(256);

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 100,
      width: 100,
      color: Color.fromRGBO(r, g, b, 1),
    );
  }
}

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

  @override
  State<StatefulWidget> createState() {
    return _StatefulColorTilesState2();
  }
}

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

  @override
  State<StatefulWidget> createState() {
    return _PositionTilesState2();
  }
}

class _PositionTilesState2 extends State<PositionTiles2> {
  List<Widget> titles = [];

  @override
  void initState() {
    super.initState();
    titles = [
      //添加了key参数,若不添加则点击按钮色块不会交互
      StatefulColorTiles2(key: UniqueKey()),
      StatefulColorTiles2(key: UniqueKey())
    ];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
          child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: titles,
      )),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.sentiment_very_satisfied),
        onPressed: swapTitles,
      ),
    );
  }

  void swapTitles() {
    setState(() {
      titles.insert(1, titles.removeAt(0));
    });
  }
}

class MyCustomApp2 extends StatelessWidget {
  const MyCustomApp2({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: "FLutter Demo about key",
        theme: ThemeData(primarySwatch: Colors.blue),
        home: const PositionTiles2());
  }
}

widget 有状态时,才需要设置 key。如果是无状态的 widget 则可以不需要设置 key。

原理

渲染 widget 时,Flutter 在构建 widget 树的同时,还会构建其对应的Element树。Element树持有 widget 树中 widget 的信息及其子 widget 的引用。在修改和重新渲染的过程中,Flutter 查找Element树查看其是否改变,以便在元素未改变时可以复用旧元素。

注意:

1.widget 树封装配置信息,Element树相当于实例对象。widget 类似于 json串,Element树类似于 json 解析后的 bean。

2. widget 类型 和 key 值 ,在没用 key 的情况下,类型相同表示新旧 widget 可复用。

键类型

Key 是抽象类,有两个重要的子类 LocalKey和 GlobalKey。

Key

/// A [Key] is an identifier for [Widget]s, [Element]s and [SemanticsNode]s.
///
/// A new widget will only be used to update an existing element if its key is
/// the same as the key of the current widget associated with the element.
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=kn0EOS-ZiIc}
///
/// Keys must be unique amongst the [Element]s with the same parent.
///
/// Subclasses of [Key] should either subclass [LocalKey] or [GlobalKey].
///
/// See also:
///
///  * [Widget.key], which discusses how widgets use keys.
@immutable
abstract class Key {
  /// Construct a [ValueKey<String>] with the given [String].
  ///
  /// This is the simplest way to create keys.
  const factory Key(String value) = ValueKey<String>;

  /// Default constructor, used by subclasses.
  ///
  /// Useful so that subclasses can call us, because the [Key.new] factory
  /// constructor shadows the implicit constructor.
  @protected
  const Key.empty();
}

Localkey 

LocalKey 是 Key 的子类,用于标识局部范围内的 Widgets。它有两个具体实现:ValueKey 和 ObjectKey。

/// A key that is not a [GlobalKey].
///
/// Keys must be unique amongst the [Element]s with the same parent. By
/// contrast, [GlobalKey]s must be unique across the entire app.
///
/// See also:
///
///  * [Widget.key], which discusses how widgets use keys.
abstract class LocalKey extends Key {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const LocalKey() : super.empty();
}

ValueKey

ValueKey 是简单的值(如字符串、数字)来标识 Widget。它通常用于具有唯一标识的对象。

/// A key that uses a value of a particular type to identify itself.
///
/// A [ValueKey<T>] is equal to another [ValueKey<T>] if, and only if, their
/// values are [operator==].
///
/// This class can be subclassed to create value keys that will not be equal to
/// other value keys that happen to use the same value. If the subclass is
/// private, this results in a value key type that cannot collide with keys from
/// other sources, which could be useful, for example, if the keys are being
/// used as fallbacks in the same scope as keys supplied from another widget.
///
/// See also:
///
///  * [Widget.key], which discusses how widgets use keys.
class ValueKey<T> extends LocalKey {
  /// Creates a key that delegates its [operator==] to the given value.
  const ValueKey(this.value);

  /// The value to which this key delegates its [operator==]
  final T value;

  @override
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType) {
      return false;
    }
    return other is ValueKey<T>
        && other.value == value;
  }

  @override
  int get hashCode => Object.hash(runtimeType, value);

  @override
  String toString() {
    final String valueString = T == String ? "<'$value'>" : '<$value>';
    // The crazy on the next line is a workaround for
    // https://github.com/dart-lang/sdk/issues/33297
    if (runtimeType == _TypeLiteral<ValueKey<T>>().type) {
      return '[$valueString]';
    }
    return '[$T $valueString]';
  }
}

ObjectKey

ObjectKey 使用对象作为标识符。这对于需要使用对象来唯一标识 Widget 的情况非常有用。

/// A key that takes its identity from the object used as its value.
///
/// Used to tie the identity of a widget to the identity of an object used to
/// generate that widget.
///
/// See also:
///
///  * [Key], the base class for all keys.
///  * The discussion at [Widget.key] for more information about how widgets use
///    keys.
class ObjectKey extends LocalKey {
  /// Creates a key that uses [identical] on [value] for its [operator==].
  const ObjectKey(this.value);

  /// The object whose identity is used by this key's [operator==].
  final Object? value;

  @override
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType) {
      return false;
    }
    return other is ObjectKey
        && identical(other.value, value);
  }

  @override
  int get hashCode => Object.hash(runtimeType, identityHashCode(value));

  @override
  String toString() {
    if (runtimeType == ObjectKey) {
      return '[${describeIdentity(value)}]';
    }
    return '[${objectRuntimeType(this, 'ObjectKey')} ${describeIdentity(value)}]';
  }
}

UniqueKey

UniqueKey:通过该对象可以生成一个具有唯一性的 hash 码。又因为每次 Widget 构建时都会重新生成一个新的 UniqueKey,失去了使用意义。(所以UniqueKey使用价值不高)

/// A key that is only equal to itself.
///
/// This cannot be created with a const constructor because that implies that
/// all instantiated keys would be the same instance and therefore not be unique.
class UniqueKey extends LocalKey {
  /// Creates a key that is equal only to itself.
  ///
  /// The key cannot be created with a const constructor because that implies
  /// that all instantiated keys would be the same instance and therefore not
  /// be unique.
  // ignore: prefer_const_constructors_in_immutables , never use const for this class
  UniqueKey();

  @override
  String toString() => '[#${shortHash(this)}]';
}

PageStorageKey

PageStorageKey可以用于滑动列表,在列表页面通过某一个 Item的点击 跳转到了一个新的页面,当返回之前的列表页面时,列表的滑动的距离回到了顶部。此时给 Sliver 设置一个 PageStorageKey,就能够保持 Sliver 的滚动状态。

/// A [Key] that can be used to persist the widget state in storage after the
/// destruction and will be restored when recreated.
///
/// Each key with its value plus the ancestor chain of other [PageStorageKey]s
/// need to be unique within the widget's closest ancestor [PageStorage]. To
/// make it possible for a saved value to be found when a widget is recreated,
/// the key's value must not be objects whose identity will change each time the
/// widget is created.
///
/// See also:
///
///  * [PageStorage], which manages the data storage for widgets using
///    [PageStorageKey]s.
class PageStorageKey<T> extends ValueKey<T> {
  /// Creates a [ValueKey] that defines where [PageStorage] values will be saved.
  const PageStorageKey(super.value);
}

GlobalKey

GlobalKey 适用于在应用程序的全局范围内唯一标识的 Widgets。GlobalKey不应该在每次build的时候重新重建, 它是State拥有的长期存在的对象。GlobalKey 能够跨 Widget 访问状态。

/// A key that is unique across the entire app.
///
/// Global keys uniquely identify elements. Global keys provide access to other
/// objects that are associated with those elements, such as [BuildContext].
/// For [StatefulWidget]s, global keys also provide access to [State].
///
/// Widgets that have global keys reparent their subtrees when they are moved
/// from one location in the tree to another location in the tree. In order to
/// reparent its subtree, a widget must arrive at its new location in the tree
/// in the same animation frame in which it was removed from its old location in
/// the tree.
///
/// Reparenting an [Element] using a global key is relatively expensive, as
/// this operation will trigger a call to [State.deactivate] on the associated
/// [State] and all of its descendants; then force all widgets that depends
/// on an [InheritedWidget] to rebuild.
///
/// If you don't need any of the features listed above, consider using a [Key],
/// [ValueKey], [ObjectKey], or [UniqueKey] instead.
///
/// You cannot simultaneously include two widgets in the tree with the same
/// global key. Attempting to do so will assert at runtime.
///
/// ## Pitfalls
///
/// GlobalKeys should not be re-created on every build. They should usually be
/// long-lived objects owned by a [State] object, for example.
///
/// Creating a new GlobalKey on every build will throw away the state of the
/// subtree associated with the old key and create a new fresh subtree for the
/// new key. Besides harming performance, this can also cause unexpected
/// behavior in widgets in the subtree. For example, a [GestureDetector] in the
/// subtree will be unable to track ongoing gestures since it will be recreated
/// on each build.
///
/// Instead, a good practice is to let a State object own the GlobalKey, and
/// instantiate it outside the build method, such as in [State.initState].
///
/// See also:
///
///  * The discussion at [Widget.key] for more information about how widgets use
///    keys.
@optionalTypeArgs
abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
  /// Creates a [LabeledGlobalKey], which is a [GlobalKey] with a label used for
  /// debugging.
  ///
  /// The label is purely for debugging and not used for comparing the identity
  /// of the key.
  factory GlobalKey({ String? debugLabel }) => LabeledGlobalKey<T>(debugLabel);

  /// Creates a global key without a label.
  ///
  /// Used by subclasses because the factory constructor shadows the implicit
  /// constructor.
  const GlobalKey.constructor() : super.empty();

  Element? get _currentElement => WidgetsBinding.instance.buildOwner!._globalKeyRegistry[this];

  /// The build context in which the widget with this key builds.
  ///
  /// The current context is null if there is no widget in the tree that matches
  /// this global key.
  BuildContext? get currentContext => _currentElement;

  /// The widget in the tree that currently has this global key.
  ///
  /// The current widget is null if there is no widget in the tree that matches
  /// this global key.
  Widget? get currentWidget => _currentElement?.widget;

  /// The [State] for the widget in the tree that currently has this global key.
  ///
  /// The current state is null if (1) there is no widget in the tree that
  /// matches this global key, (2) that widget is not a [StatefulWidget], or the
  /// associated [State] object is not a subtype of `T`.
  T? get currentState {
    final Element? element = _currentElement;
    if (element is StatefulElement) {
      final StatefulElement statefulElement = element;
      final State state = statefulElement.state;
      if (state is T) {
        return state;
      }
    }
    return null;
  }
}

GlobalKey使用场景:

参考:

Keys in Flutter. Working with Flutter, many times we… | by Karmacharyasamriddhi | codingmountain | Medium

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2085705.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MyBatis的学习————下篇

目录 一、动态SQL 简介 1、if标签 2、where标签 3、trim标签 4、choose、when、otherwise 5、foreach 5.1、批量删除 5.2、批量添加 6、sql标签 二、MyBatis的缓存 1、一级缓存 2、二级缓存 3、二级缓存的相关配置 4、MyBatis缓存查询的顺序 5、 第三方缓存EHCac…

如何在Windows 11上关闭无响应的应用程序?这里有详细步骤

序言 无响应的应用程序令人沮丧,但更糟糕的是这些应用程序拒绝关闭。如果你发现自己处于这种情况,我们有几种方法可以帮助你强制关闭Windows 11 PC上的这些应用程序。让我们找出可用的解决方案。 使用键盘快捷键结束程序 关闭无响应应用程序的最简单方法是使用Windows键盘…

DataWhale AI夏令营 2024大运河杯-数据开发应用创新赛-task2

DataWhale AI夏令营 2024大运河杯-数据开发应用创新赛 YOLO(You Only Look Once)上分心得分享 YOLO(You Only Look Once) YOLO算的上是近几年最火的目标检测模型了&#xff0c;被广泛的应用在工业、学术等领域。 YOLOv1&#xff08;You Only Look Once 第一版&#xff09;于 2…

基于麒麟信安操作系统的光伏发电功率预测系统完成大规模部署建设

麒麟信安操作系统&#xff0c;作为行业数智化建设的安全根基&#xff0c;为电力业务系统提供了稳定可靠的底层平台&#xff0c;在全球能源结构转型大潮中扮演着至关重要的角色。某光伏电站项目中&#xff0c;基于麒麟信安操作系统的光伏发电功率预测系统完成大规模部署建设&…

c#如何加密exe程序防止反编译附软件

1. 先说软件&#xff0c;使用的软件是Dotfuscator&#xff0c;下载地址如下&#xff1a; 链接&#xff1a;https://pan.quark.cn/s/6f2e785c003f2. 软件使用方法&#xff0c;打开软件&#xff0c;选择Create New Project 3. 找到input&#xff0c;把你需要加密的文件导入 4.…

k8s项目的发布

目录 三种发布方式 1.蓝绿发布 2.金丝雀发布&#xff08;灰度发布&#xff09; 实验&#xff1a;k8s实现金丝雀发布 3.滚动发布&#xff08;默认形式&#xff09; 因为应用升级以及新旧业务切换&#xff0c;所以在这个过程当中如何保证对外的服务正常是一个非常重要的问题…

手把手教你如何使用Python连接MySQL数据

数据库编程是在应用程序中与数据库交互和管理数据的关键部分。MySQL是一种流行的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;在Python中进行MySQL数据库编程相对容易。 本文介绍如何使用Python进行MySQL数据库编程&#xff0c;包括连接数据库、执行SQL查询…

高频面试题:SpringMVC的执行流程

SpringMVC一直以来都是面试中的重点&#xff0c;尽管随着近年来springboot和微服务的广泛流行&#xff0c;关于对springMVC的考察比重略有下降&#xff0c;但依然是面试中的重点&#xff0c;也需要我们对其有一个比较清楚和全面的认识。 如果将java的发展史中重要的组件进行排…

备忘录模式 详解

备忘录模式 简介: 保存一个对象的某个状态&#xff0c;以便在适当的时候恢复对象, 允许在不破坏封装性的前提下&#xff0c;捕获和恢复对象的内部状态。 场景: 很多地方都用到了备忘录模式, 比如网络消息的序列化和反序列化, 数据的本地保存与加载等, 最简单的json的dump和loa…

全能与专精:探索AI模型的未来之路

AI模型&#xff1a;追求全能还是专精&#xff1f; 近日&#xff0c;OpenAI预计在秋季推出代号为“草莓”的新AI。从专注于数学问题到处理主观营销策略&#xff0c;"草莓"模型展现出惊人的多样性。而这种全能型 AI 是否代表了未来趋势&#xff1f;相比专攻于某一领域…

OpenAI 将于今年秋天推出新的先进“Strawberry草莓”生成式人工智能产品

今年秋季&#xff0c;OpenAI将推出一款备受瞩目的新型生成式人工智能产品——“草莓”。据悉&#xff0c;这款名为“草莓”的AI程序将带来一系列创新突破&#xff0c;它将大幅提升人工智能处理复杂数学题、执行战略任务以及深入探索各种主题的能力&#xff0c;而这一切无需依赖…

前景堪忧?SaaS巨头Salesforce,25年辉煌后能否继续领跑市场?

最近&#xff0c;时常听到有人说Salesforce失去了活力&#xff0c;这或许是对整个生态系统的普遍感受。多年来&#xff0c;Salesforce一直保持着巨大的发展势头&#xff0c;通过收购、创新和建立良好的合作伙伴关系已发展成为云计算行业巨头。在经历了近25年创纪录的增长和创新…

记Spring HTTP Invoker远程调用的使用(二)基于Servlet方式,配置servlet映射url-pattern实现

目录 前言 一、概念 二、代码实现 1. 服务端实现 2. 客户端实现 前言 本篇接上一篇记Spring HTTP Invoker远程调用的使用&#xff08;一&#xff09;基于Url映射方式&#xff0c;DispatcherServlet统一处理实现-CSDN博客https://blog.csdn.net/u011529483/article/details/141…

论文解读:Prompt-aligned Gradient for Prompt Tuning

摘要 得益于CLIP等大型预训练的视觉语言模型VLM&#xff0c;我们可以通过离散的提示设计构建Zero-shot分类器&#xff0c;例如&#xff0c;利用图像与提示语句" a photo of a [ CLASS ] "之间的相似度&#xff0c;可以获得图像属于某个类别的置信度分数。此外&#…

【Material UI】Select组件的Customization详解

文章目录 一、Customization的基本概念1. 什么是Customization&#xff1f;2. 为什么要定制化Select组件&#xff1f; 二、定制化InputBase组件1. 定制化InputBase的步骤2. 定制化Select组件3. NativeSelect的定制化 三、Customization的最佳实践1. 使用标准变体&#xff08;St…

JAVA后端框架【spring】--超详解

什么是spring? spring是一个轻量级的ioc和Aop的一站式java开发框架&#xff0c;简化企业级开发 轻量级&#xff1a;框架体积小&#xff08;核心模块&#xff09; IOC IOC:inversion of control 控制反转 把创建对象的控制权反转给spring框架 AOP Aop:面向切面编程 将程…

HarmonyOS( Beta5版)鸿蒙开发:应用冷启动与加载绘制首页

应用冷启动即当启动应用时&#xff0c;后台没有该应用的进程&#xff0c;这时系统会重新创建一个新的进程分配给该应用。 应用冷启动过程大致可分成以下四个阶段&#xff1a;应用进程创建&初始化、Application&Ability初始化、Ability生命周期、加载绘制首页。 加载绘…

常用Pandas操作(笔记整理)

目录 一、常用 1、创建DataFrame&#xff08;数据导入&#xff09; 2. 查看数据前⼏⾏&#xff08;head&#xff09; 3. 查看数据后⼏⾏&#xff08;tail&#xff09; 4. 查看数据基本信息&#xff08;info&#xff09; 5. 使⽤ value_counts 计算唯⼀值的频率 6. 描述性…

SpringBoot 3.x+Mybatis Plus多数据源极简配置

1. 创建项目 创建一个名为mybatis-plus-demo的项目&#xff0c;使用MavenJDK17。不会的请看 IntelliJ IDEA快速创建Spring Boot项目&#xff0c;最终项目结构&#xff0c;如下图。 2. 编写代码 根据最终项目结构&#xff0c;从下往上依次列出各个文件的代码。 2.1 pom.xml…

迷雾大陆攻略:VMOS云手机流派辅助和技能加持助力!

在《迷雾大陆》这款游戏中&#xff0c;选择一个合适的流派和技能加点至关重要。使用VMOS云手机&#xff0c;玩家可以享受到专属定制的云手机&#xff0c;内置游戏安装包&#xff0c;无需重新下载安装游戏。同时&#xff0c;VMOS云手机能够24小时不间断运行&#xff0c;自动完成…