【Flutter】Key&AnimatedList组件

🔥 本文由 程序喵正在路上 原创,CSDN首发!
💖 系列专栏:Flutter学习
🌠 首发时间:2024年5月28日
🦋 欢迎关注🖱点赞👍收藏🌟留言🐾

Key详解

我们平时一定接触过很多的 Widget,比如 ContainerRowColumn 等,它们在我们绘制界面的过程中发挥着重要的作用。但是不知道你有没有注意到,在几乎每个 Widget 的构造函数中,都有一个共同的参数,它们通常在参数列表的第一个,那就是 Key

Flutter 中,Key 是不能重复使用的,所以 Key 一般用来做唯一标识。组件在更新的时候,其状态的保存主要是通过判断组件的类型或者 key 值是否一致。因此,当各组件的类型不同的时候,类型已经足够用来区分不同的组件了,此时我们可以不必使用 key。但是如果同时存在多个同一类型的控件的时候,此时类型已经无法作为区分的条件了,我们就需要使用到 key

LocalKey和GlobalKey

Flutter key子类包含 LocalKey 和 GlobalKey

  • 局部键(LocalKey):ValueKey、ObjectKey、UniqueKey
    • ValueKey:把一个值作为key
    • UniqueKey:程序会生成唯一的Key,当我们不知道如何指定 ValueKey 的时候就可以使用 UniqueKey
    • ObjectKey:把一个对象实例作为key
  • 全局键(GlobalKey): GlobalKey、GlobalObjectKey
    • GlobalObjectKey:全局 Objec key,和 ObjectKey 有点类似

LocalKey的使用

下面程序的功能是,界面中有 3 个颜色不同的按钮,点击按钮,其上面的数字会增加,点击右下角的浮动按钮,按钮的顺序会被打乱。如果没有给 3 个按钮赋予不同的 key 值,当被打乱后,改变的只有颜色,其上的数字并不会改变。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

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

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<Widget> list = [
    const Box(
      key: ValueKey('666'),
      color: Colors.red,
    ),
    Box(
      key: UniqueKey(),
      color: Colors.blue,
    ),
    const Box(
      key: ObjectKey(Box(color: Colors.blue)),
      color: Colors.yellow,
    ),
  ];

  
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.refresh),
        onPressed: () {
          setState(() {
            list.shuffle(); //shuffle:打乱list元素的顺序
          });
        },
      ),
      appBar: AppBar(
        title: const Text('LocalKey'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: list,
        ),
      ),
    );
  }
}

class Box extends StatefulWidget {
  final Color color;
  const Box({super.key, required this.color});

  
  State<Box> createState() => _BoxState();
}

class _BoxState extends State<Box> {
  int _count = 0;

  
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.all(10),
      height: 100,
      width: 100,
      child: ElevatedButton(
        style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all(widget.color)),
        onPressed: () {
          setState(() {
            _count++;
          });
        },
        child: Text(
          "$_count",
          style: Theme.of(context).textTheme.displayLarge,
        ),
      ),
    );
  }
}

初始:

在这里插入图片描述

随意点击:

在这里插入图片描述

打乱:

在这里插入图片描述

GlobalKey的使用

运行下面这个程序会发现,当我们将手机改为横屏后,按钮的状态恢复到初始状态,这是因为外层组件从 Column 变成了 Row,组件的类型已经改变,状态自然无法再保存。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

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

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<Widget> list = [
    const Box(
      key: ValueKey('666'),
      color: Colors.red,
    ),
    Box(
      key: UniqueKey(),
      color: Colors.blue,
    ),
    const Box(
      key: ObjectKey(Box(color: Colors.blue)),
      color: Colors.yellow,
    ),
  ];

  
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.refresh),
        onPressed: () {
          setState(() {
            list.shuffle(); //shuffle:打乱list元素的顺序
          });
        },
      ),
      appBar: AppBar(
        title: const Text('GlobalKey'),
      ),
      body: Center(
        // //判断屏幕为竖屏还是横屏
        child: MediaQuery.of(context).orientation == Orientation.portrait
            ? Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: list,
              )
            : Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: list,
              ),
      ),
    );
  }
}

class Box extends StatefulWidget {
  final Color color;
  const Box({super.key, required this.color});

  
  State<Box> createState() => _BoxState();
}

class _BoxState extends State<Box> {
  int _count = 0;

  
  Widget build(BuildContext context) {
    return Container(
      margin: const EdgeInsets.all(10),
      height: 100,
      width: 100,
      child: ElevatedButton(
        style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all(widget.color)),
        onPressed: () {
          setState(() {
            _count++;
          });
        },
        child: Text(
          "$_count",
          style: Theme.of(context).textTheme.displayLarge,
        ),
      ),
    );
  }
}

下面使用 GlobalKey 来对其改进,我们定义了 3 个 GlobalKey,然后在 initState 里面对 Box 进行初始化。

在这里插入图片描述

竖屏:

在这里插入图片描述

横屏:

在这里插入图片描述

GlobalKey获取子组件

globalKey.currentState 可以获取子组件的状态,执行子组件的方法,globalKey.currentWidget 可以获i取子组件的属性,_globalKey.currentContext!.findRenderObject() 可以获取渲染的属性。

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomePage(),
    );
  }
}

//父Widget
class HomePage extends StatefulWidget {
  const HomePage({super.key});

  
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final GlobalKey _globalKey = GlobalKey();

  
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          //1、获取currentState Widget的属性(记住)
          var boxState = _globalKey.currentState as _BoxState;
          print(boxState._count);
          setState(() {
            boxState._count++; //可以设置获取的属性
          });
          //调用currentState Widget的方法
          boxState.run();

          //2、获取子Widget (了解)
          var boxWidget = _globalKey.currentWidget as Box;
          print(boxWidget
              .color); //值:MaterialColor(primary value: Color(0xfff44336))

          // 3、获取子组件渲染的属性(了解)
          var renderBox =
              _globalKey.currentContext!.findRenderObject() as RenderBox;
          print(renderBox.size); //值:Size(100.0, 100.0)
        },
      ),
      appBar: AppBar(
        title: const Text('Global获取子组件'),
      ),
      body: Center(
        child: Box(key: _globalKey, color: Colors.red),
      ),
    );
  }
}

//子Widget
class Box extends StatefulWidget {
  final Color color;
  const Box({Key? key, required this.color}) : super(key: key);
  
  State<Box> createState() => _BoxState();
}

class _BoxState extends State<Box> {
  int _count = 0;
  void run() {
    print("我是box的run方法");
  }

  
  Widget build(BuildContext context) {
    return SizedBox(
      height: 100,
      width: 100,
      child: ElevatedButton(
        style: ButtonStyle(
            backgroundColor: MaterialStateProperty.all(widget.color)),
        onPressed: () {
          setState(() {
            _count++;
          });
        },
        child: Text(
          "$_count",
          style: Theme.of(context).textTheme.headlineLarge,
        ),
      ),
    );
  }
}

在这里插入图片描述

Widget Tree、Element Tree和RenderObject Tree

Flutter 应用是由是 Widget Tree、Element Tree 和 RenderObject Tree组成的。Widget 可以理解成一个类,Element 可以理解成 Widget 的实例,WidgetElement 的关系可以是一对多,一份配置可以创造多个 Element 实例。

属性描述
WidgetWidget 就是一个类, 是 Element 的配置信息。与 Element 的关系可以是一对多,一份配置可以创造多 个Element 实例
ElementWidget 的实例化,内部持有 Widget 和 RenderObject
RenderObject负责渲染绘制

默认情况下,当 Flutter 同一个 Widget 的大小、顺序变化的时候,FLutter 不会改变 Widgetstate

AnimatedList组件

AnimatedList介绍

AnimatedListListView 的功能大体相似,不同的是, AnimatedList 可以在列表中插入或删除节点时执行一个动画,在需要添加或删除列表项的场景中会提高用户体验。

AnimatedList 是一个 StatefulWidget,它对应的 State 类型为 AnimatedListState,添加和删除元素的方法位于 AnimatedListState 中:

void insertItem(int index, { Duration duration = _kDuration });
void removeItem(int index, AnimatedListRemovedItemBuilder builder, { Duration duration = _kDuration }) ;

AnimatedList 常见属性:

属性描述
keyglobalKey final globalKey = GlobalKey();
initialItemCount子元素数量
itemBuilder方法 (BuildContext context, int index, Animation animation) {}

关于GlobalKey

  • 每个 Widget 都对应一个 Element ,我们可以直接对 Widget 进行操作,但是无法直接操作 Widget 对应的 Element 。而 GlobalKey 就是那把直接访问 Element 的钥匙。通过 GlobalKey 可以获取到 Widget 对应的 Element

AnimatedList实现动态列表

import 'dart:async';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

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

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final _globalKey = GlobalKey<AnimatedListState>();
  bool flag = true;
  List<String> list = ["第一条", "第二条"];

  Widget _buildItem(index) {
    return ListTile(
      key: ValueKey(index),
      title: Text(list[index]),
      trailing: IconButton(
        icon: const Icon(Icons.delete),
        onPressed: () {
          //执行删除
          _deleteItem(index);
        },
      ),
    );
  }

  _deleteItem(index) {
    if (flag == true) {
      flag = false;
      //执行删除
      _globalKey.currentState!.removeItem(index, (context, animation) {
        //animation的值是从1到0
        var removeItem = _buildItem(index);

        list.removeAt(index); //数组中删除数据
        return ScaleTransition(
          // opacity: animation,
          scale: animation,
          child: removeItem, //删除的时候执行动画的元素
        );
      });
      //解决快速删除的bug
      Timer.periodic(const Duration(milliseconds: 500), (timer) {
        flag = true;
        timer.cancel();
      });
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('AnimatedList动态列表'),
      ),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.add),
        onPressed: () {
          list.add("我是新增的数据");
          _globalKey.currentState!.insertItem(list.length - 1);
        },
      ),
      body: AnimatedList(
          key: _globalKey,
          initialItemCount: list.length,
          itemBuilder: ((context, index, animation) {
            //animation的值是从0到1
            return FadeTransition(
              opacity: animation,
              child: _buildItem(index),
            );
          })),
    );
  }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序喵正在路上

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值