美文网首页
Flutter -- 15.Key

Flutter -- 15.Key

作者: MissStitch丶 | 来源:发表于2021-11-26 11:35 被阅读0次

    一.引入key的概念

    • 这里有一个小demo
    • 每次点击按钮,删除第一个Widget

    1.使用StatefulWidget

    void main() {
      runApp(const App());
    }
    
    class App extends StatelessWidget {
      const App({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return const MaterialApp(
          home: Home(),
        );
      }
    }
    
    class Home extends StatefulWidget {
      const Home({Key? key}) : super(key: key);
    
      @override
      _HomeState createState() => _HomeState();
    }
    
    class _HomeState extends State<Home> {
    
      final List<Widget> _widgets = [const StateFulTest('1111'), const  StateFulTest('2222'), const StateFulTest('33333')];
    
      void _onPressed() {
        setState(() {
          _widgets.removeAt(0);
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(title: const Text('Key Demo'),),
            floatingActionButton: FloatingActionButton(
              onPressed: _onPressed,
              child: const Icon(Icons.add),
            ),
            body: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: _widgets,
            )
        );
      }
    }
    
    class StateFulTest extends StatefulWidget {
      const StateFulTest(this.title ,{Key? key}) : super(key: key);
    
      final String title;
    
      @override
      _StateFulTestState createState() => _StateFulTestState();
    }
    
    class _StateFulTestState extends State<StateFulTest> {
      Color color = Color.fromRGBO(Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);
    
      @override
      Widget build(BuildContext context) {
        return Container(
          height: 100,
          width: 100,
          color: color,
          child: Center(
            child: Text(widget.title),
          ),
        );
      }
    }
    
    
    class StatelessTest extends StatelessWidget {
      StatelessTest(this.title, {Key? key}) : super(key: key);
    
      final String title;
    
      Color color = Color.fromRGBO(Random().nextInt(256), Random().nextInt(256), Random().nextInt(256), 1.0);
    
      @override
      Widget build(BuildContext context) {
        return Container(
          height: 100,
          width: 100,
          color: color,
          child: Center(
            child: Text(title),
          ),
        );
      }
    }
    
    key_colors_bug.gif
    • 文字显示正常,但是Widget的颜色却是不正常的
    • 看起来就像删除的第三个Widget

    2.使用StatelessWidget

    • 那这里可否怀疑是Stateful导致的问题?
    • _widgets中的StateFulTest更换为StatelessDemo
    • 发现居然正常了

    3.使用StatelessWidget,将State中的color放到Widget

    • 经过测试正常

    4.问题排查思路

    • 出现该问题是因为state没有被刷新或者重置
    • 通过渲染原理可知state的创建是在StatefulElement的构造方法中,Element与state是绑定
    • 出现的问题是因为删除后的第一个Widget绑定了之前删掉的Element,导致state不会被刷新,出现颜色不会变化的bug
    • 针对Widget刷新时,绑定了之前一个Element导致的bug。查看Widget源码是否有关于Widget与Element绑定的方法
    • 在Widget类中,有一种非常重要的方法canUpdate
    static bool canUpdate(Widget oldWidget, Widget newWidget) {
        return oldWidget.runtimeType == newWidget.runtimeType
            && oldWidget.key == newWidget.key;
      }
    
    • 进入这个方法的前提是2个Widget的父Element是相同的
    • 这个函数的注释写得非常清楚,新的Wdiget是否可以更新到老的Widget的Elment
    • 简答来说,是否可以复用Element
    • 还有一点我们也需要了解,新老Widget的子部件不同也不会影响到是否可以update

    • runtimeType为对象的运行时类型的形式
    • 通过这个方法我们可以很明确的分析到,很明显新老Widget是一种类型,并且runtimeType肯定是一致的。key没有传值为nil,因此该方法必定返回true。
    • 将Element1更新到Widget2中
    • 至此,问题问题排查清楚

    • 下面用一张图来简单的分析一下


      statefulWidget_Element_Error.png

    5.当我们移除一个Widget时,同时再添加一个Widget,此时的Element是否会复用移除的?

      final List<Widget> _widgets = [const StateFulTest('1111'), const  StateFulTest('2222'), const StateFulTest('33333')];
      
      void _onPressed() {
        setState(() {
          _widgets.removeAt(0);
          _widgets.add(const StateFulTest('4444'));
        });
      }
    
    1.添加一个不带key的Widget

    按上图逻辑,Element3会被释放
    那么现在断点调试,查看一下Flutter在这块是怎么优化的
    此时断点断在createElement()

      StatefulElement createElement() => StatefulElement(this);
    

    结果:并没有进入断点,添加新的Widget(不含key)没有执行createElement

    2.添加一个带key的Widget
      void _onPressed() {
        setState(() {
          _widgets.removeAt(0);
          _widgets.add(const StateFulTest('4444', key: ValueKey(4),));
        });
      }
    

    结果:进入断点,添加新的Widget(含key)执行createElement

    3.总结
    • 在setState方法中,当我们移除一个Widget时,同时再添加一个Widget,此时的Element会先去复用移除的(canUpdate判断是否能复用)

    二.Key

    • 抽象类,一般使用它的派生类LocalKeyGlobalKey
    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 [new Key] factory
      /// constructor shadows the implicit constructor.
      @protected
      const Key.empty();
    }
    
    • 默认Key的工程构造方法也是使用ValueKey实现,ValueKey为LocalKey的派生类

    • 在之前的代码修改_widgets,加入key
    final List<Widget> _widgets = [const StateFulTest('1111', key: Key('1111'),), const  StateFulTest('2222', key: Key('2222')), const StateFulTest('33333', key: Key('33333'))];
    
    key_colors_ferfect.gif
    • 结果显示正常
    • 至此关于构造方法中Key的作用相信大家应该比较明白了

    三.LocalKey

    • 一般用于相同父Element小部件的比较。也就是在Widget中update方法使用
    1.ValueKey
    • 指的是通过一个值来创建的key。其中传入的值类型是泛型,任意类型
    • 使用场景,通过value值来对比
    2.ObjectKey
    • 指的是通过一个对象来创建的key
    • 使用场景,通过Object指针地址来对比
    3.UniqueKey
    • 指的是创建了一个唯一的key。通过该对象生成一个唯一的hash码
    • 使用场景,每次构建时key都是不同的,因此Element永远不会复用
    keyDemo() {
    
      //创建测试对象
      TestKeyClass testK = TestKeyClass();
    
      //ValueKey
      ValueKey key1 = ValueKey(testK);
      ValueKey key2 = ValueKey(testK);
      ValueKey key3 = const ValueKey(3);
      print(key1 == key2); //true
      print(key1 == key3); //false
    
      //ObjectKey
      ObjectKey objectKey1 = ObjectKey(testK);
      ObjectKey objectKey2 = ObjectKey(testK);
      ObjectKey objectKey3 = ObjectKey(TestKeyClass());
      print(objectKey1 == objectKey2); //true
      print(objectKey1 == objectKey3); //false
    
      //UniqueKey
      print(UniqueKey() == UniqueKey()); //false
    }
    

    四.GlobalKey

    • 一般通过使用GlobalKey来保存/获取某一部件的Widget、State、Element
    • 概念类似于iOS中的tag

    • 这里介绍一个简单的使用场景,在StatelessWidget中刷新StatefulWidget的状态
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      MyApp({Key? key}) : super(key: key);
    
      final GlobalKey _globalKey = GlobalKey();
    
      void _onPressed() {
        _GlobalKeyTestState state = _globalKey.currentState as _GlobalKeyTestState ;
    
        /*
        * 下面写法会报出警告
        * The member 'setState' can only be used within instance members of subclasses of 'package:flutter/src/widgets/framework.dart'.
        * 大致意思是setState这个方法应该只能在state方法里面调用
        * 因此这里写了一个refreshState方法中转一下来消除警告
        * */
        // state.setState(() {
        //   state.count ++;
        // });
    
        state.count ++;
        state.refreshState();
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(title: const Text('Global Key Demo'),),
            body: GlobalKeyTest(key: _globalKey,),
            floatingActionButton: FloatingActionButton(
              onPressed: _onPressed,
              child: const Icon(Icons.add),
            ),
          ),
        );
      }
    
    }
    
    class GlobalKeyTest extends StatefulWidget {
      const GlobalKeyTest({Key? key}) : super(key: key);
    
      @override
      _GlobalKeyTestState createState() => _GlobalKeyTestState();
    }
    
    class _GlobalKeyTestState extends State<GlobalKeyTest> {
    
      var count = 0;
    
      refreshState() {
        setState(() {
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Center(
          child: Text('$count'),
        );
      }
    }
    
    globalKey.gif

    相关文章

      网友评论

          本文标题:Flutter -- 15.Key

          本文链接:https://www.haomeiwen.com/subject/mwiatrtx.html