Flutter中的Widget Key简介

前提

阅读之前你需要知道Flutter Widget刷新机制,可以见我的另一篇文章:
Flutter的Widget刷新时机以及优化

作用

在Flutter中,Key是一个用于标识widget的对象,它的作用有以下几个:

  1. 区分不同的widget
    Key可以帮助Flutter区分不同的widget。当我们在widget树中添加、移除或更新一个widget时,Flutter需要确定哪些widget需要被修改或重新构建。如果两个widget具有相同的Key,则Flutter会将它们视为同一个widget,并且不会重复构建。

  2. 提高性能
    通过使用Key,Flutter可以更加高效地处理widget树的构建和更新。例如,在ListView中,如果我们没有为每个item指定Key,Flutter将无法区分不同的item,并且每次构建和更新都会重新创建所有的item,这会降低性能。

  3. 保留状态
    当我们使用StatefulWidget时,Key还可以帮助我们保留widget的状态。当我们在widget树中添加、移除或更新一个StatefulWidget时,Flutter需要确定哪些State对象需要被保留,以便维护widget的状态。如果我们没有为StatefulWidget指定Key,则Flutter将无法确定哪些State对象需要被保留,从而可能导致widget状态的丢失。

下面举用法例子

当我们使用StatefulWidget时,如果没有为widget指定Key,则当widget树中的某个节点发生变化时,Flutter会重新创建所有的State对象,这可能导致widget的状态丢失。

以下是一个简单的示例,演示了如何使用Key来保留StatefulWidget的状态。假设我们有一个数字计数器,每次点击按钮时,计数器的值都会加1。我们可以使用StatefulWidget来实现这个计数器,如下所示:

class CounterWidget extends StatefulWidget {
  
  _CounterWidgetState createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $_count'),
        ElevatedButton(
          onPressed: _increment,
          child: Text('Increment'),
        ),
      ],
    );
  }
}

在上述代码中,我们定义了一个CounterWidget类,它继承自StatefulWidget,用于显示一个数字计数器。CounterWidget的状态由一个_CountWidgetState类来管理,它包含一个整数_count,用于保存计数器的值。每次点击按钮时,_count都会加1,这个操作是在_increment函数中执行的。

如果我们在widget树中多次使用CounterWidget,那么每个CounterWidget都将拥有自己的_CountWidgetState对象,并且每次点击按钮时,所有的_CountWidgetState对象都会被重新创建。为了避免这个问题,我们可以为每个CounterWidget指定一个Key,如下所示:

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

  
  _CounterWidgetState createState() => _CounterWidgetState();
}

然后,在widget树中使用CounterWidget时,我们可以为每个CounterWidget指定一个不同的Key,例如:

CounterWidget(key: ValueKey(1)),
CounterWidget(key: ValueKey(2)),
CounterWidget(key: ValueKey(3)),

通过为每个CounterWidget指定一个不同的Key,Flutter将会正确地保留每个CounterWidget的状态,并且在点击按钮时只会重新构建对应的_CountWidgetState对象。这可以提高应用程序的性能,并确保计数器的状态正确显示。

如果我们没有为每个CounterWidget指定一个不同的Key,那么每次点击按钮时,所有的_CountWidgetState对象都会被重新创建,导致计数器的状态丢失。下面是一个没有使用Key的示例代码:

class CounterWidget extends StatefulWidget {
  
  _CounterWidgetState createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
  int _count = 0;

  void _increment() {
    setState(() {
      _count++;
    });
  }

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $_count'),
        ElevatedButton(
          onPressed: _increment,
          child: Text('Increment'),
        ),
      ],
    );
  }
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              CounterWidget(),
              CounterWidget(),
              CounterWidget(),
            ],
          ),
        ),
      ),
    );
  }
}

在上述代码中,我们定义了一个CounterWidget类,用于显示一个数字计数器。在MyApp类中,我们使用CounterWidget三次,将三个计数器显示在屏幕中。

如果我们在应用程序中运行上述代码,并点击任意一个计数器的按钮,所有的计数器都会被重新构建,导致计数器的状态丢失。这是因为所有的CounterWidget都没有指定Key,因此Flutter无法区分它们之间的差异,从而会将它们视为同一个widget,并且每次点击按钮时都会重新构建所有的CounterWidget。

总之,通过为每个widget指定Key,我们可以帮助Flutter正确地区分不同的widget,从而保留widget的状态,并提高应用程序的性能和响应性能。在使用StatefulWidget时,特别需要注意使用Key来保留widget的状态,避免状态丢失的问题。

Key有哪几种

ValueKey

ValueKey是最常见的Key类型之一,它基于一个给定的值创建一个Key对象。当我们使用ValueKey时,如果两个widget具有相同的值,则它们将被视为同一个widget,从而可以避免widget的重复构建。

例如,我们可以使用ValueKey来为一个ListView的item指定Key,如下所示:

ListView.builder(
  itemCount: 10,
  itemBuilder: (BuildContext context, int index) {
    return ListTile(
      key: ValueKey(index),
      title: Text('Item $index'),
    );
  },
)

在上述代码中,我们为ListView的每个item都指定了一个ValueKey,基于它们的索引值来创建Key对象。这样,当ListView中的item发生变化时,Flutter将能够正确地区分不同的item,并且避免不必要的重复构建。

ObjectKey

ObjectKey是另一种常见的Key类型,它基于一个给定的对象创建一个Key对象。当我们使用ObjectKey时,如果两个widget具有相同的对象,则它们将被视为同一个widget,从而可以避免widget的重复构建。

例如,我们可以使用ObjectKey来为一个含有一组数据的widget指定Key,如下所示:

MyData data = ...;

return MyWidget(
  key: ObjectKey(data),
  data: data,
);

在上述代码中,我们为MyWidget指定了一个ObjectKey,基于它所包含的数据对象来创建Key对象。这样,当MyWidget中的数据对象发生变化时,Flutter将能够正确地区分不同的MyWidget,并且避免不必要的重复构建。

UniqueKey

UniqueKey是一种特殊的Key类型,它基于一个随机数创建一个唯一的Key对象。当我们使用UniqueKey时,每个widget都将具有一个唯一的Key,从而可以避免重复构建和状态丢失等问题。

例如,我们可以使用UniqueKey来为一个StatefulWidget指定Key,如下所示:

return MyStatefulWidget(
  key: UniqueKey(),
);

在上述代码中,我们为MyStatefulWidget指定了一个UniqueKey,以确保每次创建时都具有一个唯一的Key。这可以帮助我们避免状态丢失等问题,并提高应用程序的性能和响应性能。

GlobalKey

与其他Key类型不同,GlobalKey可以跨widget树访问widget,它可以用于在widget树之外引用widget的状态或方法。

具体来说,我们可以使用GlobalKey来获取widget的状态或方法,然后在widget树之外调用它们。这在某些情况下非常有用,例如当我们需要从widget树之外修改widget的状态或执行一些操作时。

以下是一个使用GlobalKey的示例代码,它演示了如何在widget树之外引用widget的状态并进行修改:

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

  
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  int _count = 0;

  void increment() {
    setState(() {
      _count++;
    });
  }

  
  Widget build(BuildContext context) {
    return Text('Count: $_count');
  }
}

class MyApp extends StatelessWidget {
  final GlobalKey<_MyWidgetState> _widgetKey = GlobalKey<_MyWidgetState>();

  void incrementCount() {
    _widgetKey.currentState?.increment();
  }

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              MyWidget(key: _widgetKey),
              ElevatedButton(
                onPressed: incrementCount,
                child: Text('Increment'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

在上述代码中,我们定义了一个MyWidget类,用于显示一个计数器。计数器的值保存在一个私有变量_count中,我们定义了一个increment方法来增加计数器的值,并使用setState来通知Flutter更新widget的状态。

在MyApp类中,我们创建了一个GlobalKey<_MyWidgetState>对象,用于引用MyWidget的状态。我们定义了一个incrementCount方法,在点击按钮时调用这个方法,从而调用MyWidget的increment方法,并更新计数器的值。

通过使用GlobalKey,我们可以从widget树之外修改MyWidget的状态,并确保计数器的值正确更新。这在一些复杂的应用程序中非常有用,例如当我们需要在不同的widget之间共享数据或状态时。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Flutter获取Widget位置可以使用`GlobalKey`和`BuildContext`。 1. 使用`GlobalKey` 使用`GlobalKey`可以获取到一个Widget在屏幕上的位置。首先需要在需要获取位置的Widget上定义一个`GlobalKey`,然后通过`GlobalKey`获取到该Widget的位置信息。 ```dart class MyWidget extends StatelessWidget { final GlobalKey _globalKey = GlobalKey(); @override Widget build(BuildContext context) { return Container( key: _globalKey, child: Text('Hello, World!'), ); } void getPosition() { RenderBox box = _globalKey.currentContext.findRenderObject() as RenderBox; Offset position = box.localToGlobal(Offset.zero); print('x: ${position.dx}, y: ${position.dy}'); } } ``` 在上面的代码,我们首先定义了一个`GlobalKey`,然后将其赋值给一个`Container`的`key`属性。在`getPosition`方法,我们通过`_globalKey.currentContext.findRenderObject()`获取到该Widget的渲染对象,然后使用`localToGlobal`方法将该对象的位置转换为全局位置。 2. 使用`BuildContext` 如果您只需要获取Widget在屏幕上的位置,而不是具体的坐标位置,您可以使用`BuildContext`。您可以使用`MediaQuery`获取到屏幕的大小,然后使用`BuildContext`获取到Widget的位置信息。 ```dart class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Builder( builder: (context) { Size screenSize = MediaQuery.of(context).size; final RenderBox renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; final topLeftPosition = renderBox.localToGlobal(Offset.zero); final bottomRightPosition = renderBox.localToGlobal(Offset(size.width, size.height)); return Container( color: Colors.blue, ); }, ); } } ``` 在上面的代码,我们在`Builder`使用`BuildContext`获取到Widget的渲染对象,并使用`localToGlobal`方法获取到该对象的位置信息。最后,我们使用`MediaQuery`获取到屏幕的大小信息,并使用该信息计算出Widget的左上角和右下角的位置信息。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值