一. 什么是key

在常规flutter开发中,widget可以有stateful和stateless两种,key能帮助开发者保存widget的状态

key涉及到flutter渲染机制

  1. Widget只是一个配置项(color,height,width…),最终生成对应类型element(内部存有state状态)
  2. Widget更新时,会匹配默认的对应的生成的element,如果类型相同,且key相同,更新视图; 如果类型相同,key不相同,element会找widget同级相同的key进行匹配更新
@immutable
abstract class Widget extends DiagnosticableTree {
  const Widget({ this.key });
  final Key key;
  ···
  static bool canUpdate(Widget oldWidget, Widget newWidget) {
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}

1.2 图解

// 当没有使用key时,解析flutter更新机制

class Screen extends StatefulWidget {
  @override
  _ScreenState createState() => _ScreenState();
}

class _ScreenState extends State<Screen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
              StatelessContainer(color : Colors.green)
              StatelessContainer(color : Colors.blue )
              StatelessContainer(color : Colors.red )
          ],
        ),
      ),
    );
  }
}

class StatefulContainer extends StatefulWidget {
  final Color color;
  StatefulContainer({Key key , color}) : super(key: key);
  @override
  _StatefulContainerState createState() => _StatefulContainerState();
}

class _StatefulContainerState extends State<StatefulContainer> {
  int count  = 0;
  
  @override
  Widget build(BuildContext context) {
    return InkWell(
        onTap: () {
            setState(()=>{
                count++
            })
        },
        child : Container(
            width: 100,
            height: 100,
            color: widget.color,
            child : Text(count.toString())
        )
    ); 
  }
}
  1. flutter会根据widget生成对应的element,将element渲染到界面上,
    widget只保存ui配置项,element保存state状态

  1. 动态的把绿色和蓝色的widget互换位置,在进行热跟新

发现两个widget互换之后,结果尽然只是颜色发生了互换,state的值并没有发生互换
原因是,当widget互换位置之后,element tree会依次向左边的widget tree进行匹配,ContainerElement1比对Container2时,类型一致,那么就直接把当前的element指定到了Container2上

  1. 动态的把第一个蓝色widget移除,在进行热更新
    第一个蓝色的widget移除后,视图重新刷新,ContainerElement1先匹配Container1,发现类型匹配上了,那么ContainerElement1指向Container1,依次类推,最后ContainerElement3因为没有匹配项了,会进行销毁处理;


    注意 : element匹配,只会匹配同级的widget,不会匹配父级和子级的widget

  1. 此时在最前面新增一个紫色的widget,过程图如下

  1. 如果此时给每个widget传入对应的key,基于上面这三个例子,都会很合理的进行渲染;

二. 解决了什么问题

key 的种类

Localkey

  1. ValueKey : ValueKey(‘String’)
  2. ObjectKey : ObjectKey(Object)
  3. UniqueKey : UniqueKey2)

GlobalKey

GlobalKey 使用了一个静态常量 Map 来保存它对应的 Element。 你可以通过 GlobalKey 找到持有该GlobalKey的 Widget,State 和 Element。

注意:GlobalKey 是非常昂贵的,需要谨慎使用。

三. 如何使用

  1. ValueKey
// 如果您有一个 Todo List 应用程序,它将会记录你需要完成的事情。我们假设每个 Todo 事情都各不相同,而你想要对每个 Todo 进行滑动删除操作。这时候就需要使用 ValueKey!

return TodoItem(
    key: ValueKey(todo.task),
    todo: todo,
    onDismissed: (direction){
        _removeTodo(context, todo);
    },
);

  1. ObjectKey
如果你有一个生日应用,它可以记录某个人的生日,并用列表显示出来,同样的还是需要有一个滑动删除操作。
我们知道人名可能会重复,这时候你无法保证给 Key 的值每次都会不同。但是,当人名和生日组合起来的 Object 将具有唯一性。
这时候你需要使用 ObjectKey!。
  1. UniqueKey
如果组合的 Object 都无法满足唯一性的时候,你想要确保每一个 Key 都具有唯一性。那么,你可以使用 UniqueKey。它将会通过该对象生成一个具有唯一性的 hash 码。
不过这样做,每次 Widget 被构建时都会去重新生成一个新的 UniqueKey,失去了一致性。也就是说你的小部件还是会改变。(还不如不用😂)
  1. PageStorageKey
当你有一个滑动列表,你通过某一个 Item 跳转到了一个新的页面,当你返回之前的列表页面时,你发现滑动的距离回到了顶部。这时候,给 Sliver 一个 PageStorageKey!它将能够保持 Sliver 的滚动状态。
  1. GlobalKey
class SwitcherScreenState extends State<SwitcherScreen> {
  bool isActive = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Switch.adaptive(
            value: isActive,
            onChanged: (bool currentStatus) {
              isActive = currentStatus;
              setState(() {});
            }),
      ),
    );
  }

  changeState() {
    isActive = !isActive;
    setState(() {});
  }
}

// 但是我们想要在外部改变该状态,这时候就需要使用 GlobalKey。

class _ScreenState extends State<Screen> {
  final GlobalKey<SwitcherScreenState> key = GlobalKey<SwitcherScreenState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SwitcherScreen(
        key: key,
      ),
      floatingActionButton: FloatingActionButton(onPressed: () {
        key.currentState.changeState();
      }),
    );
  }
}

四. 注意事项

五. 总结

学习到了flutter key的使用,和更新机制原理

六. 参考

Flutter | 深入浅出Key

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐