Flutter---GlobalKey

基本概念:GlobalKey 是 Flutter 中一个特殊的标识符,它可以全局唯一地标识一个 Widget,并允许你在应用的任何地方访问这个 Widget 的状态、位置和大小。类似于GPS。

主要的属性和方法

Dart 复制代码
GlobalKey _key = GlobalKey();

// currentContext - 获取该 Widget 的 BuildContext
BuildContext? context = _key.currentContext;

// currentState - 获取该 Widget 的 State(仅 StatefulWidget)
State? state = _key.currentState;

// currentWidget - 获取该 Widget 实例
Widget? widget = _key.currentWidget;

// findRenderObject() - 获取 RenderObject
RenderObject? renderObject = _key.currentContext?.findRenderObject();

通过一个长按唤醒删除按钮的案例来学习GlobalKey

效果图

流程图

Dart 复制代码
新建(空) → 赋值(绑定Widget) → 挂载(自动关联Element) → 渲染(创建RenderBox) → 获取(位置信息)
Dart 复制代码
代码执行流程

1.初始化时创建GlobalKey
for (var i = 0; i < _items.length; i++) {
  _itemKeys[i.toString()] = GlobalKey();  // 创建20个GlobalKey存起来
}

2.构建时绑定GlobalKey
Container(
  key: _itemKeys[id],  // ← 把GlobalKey绑定到Container上
)

3.长按时通过GlobalKey获取位置
void _showDeleteButton(String id) {
  setState(() => _selectedId = id);  // 1. 高亮当前项
  
  WidgetsBinding.instance.addPostFrameCallback((_) {  // 2. 等布局完成
    final key = _itemKeys[id];  // 3. 取出GlobalKey
    final box = key!.currentContext!.findRenderObject() as RenderBox;  // 4. 找到位置
    final position = box.localToGlobal(Offset.zero);  // 5. 获取坐标
    setState(() => _buttonTop = position.dy - 35);  // 6. 计算按钮位置
  });
}
Dart 复制代码
用户长按第3个列表项
        ↓
触发 onLongPress
        ↓
调用 _showDeleteButton('2')
        ↓
setState → _selectedId = '2' (立即高亮该项)
        ↓
addPostFrameCallback 等待布局完成
        ↓
通过 _itemKeys['2'] 获取 GlobalKey
        ↓
GlobalKey.currentContext 获取 BuildContext
        ↓
findRenderObject() 获取 RenderBox
        ↓
localToGlobal() 获取屏幕位置 (x=16, y=300)
        ↓
计算按钮位置: _buttonTop = 300 - 35 = 265
        ↓
setState → 显示删除按钮
        ↓
用户点击删除按钮
        ↓
调用 _deleteItem('2')
        ↓
_items.removeAt(2) (删除数据)
_itemKeys.remove('2') (清理 GlobalKey)
_selectedId = null (取消高亮)
_buttonTop = null (隐藏按钮)
        ↓
setState → 重建 UI,列表更新

难点

底层数据剖析

Dart 复制代码
─────────────────────────────────────────────────
1. GlobalKey myKey = GlobalKey()
   ↓
   Flutter 内部:创建空壳,_element = null

2. Container(key: myKey)
   ↓
   Flutter 内部:
   ├─ 创建 Element#123 (Element是Flutter中连接Widget(配置信息)和RenderObject(渲染对象)的桥梁)
   ├─ Element#123._widget = Container (将 Container 这个 Widget 配置信息,存储到 Element#123 这个对象的 _widget 变量中)
   ├─ myKey._element = Element#123 (将 Element 对象存储到 GlobalKey 的内部变量中)
   └─ _registry[myKey] = Element#123 (将 GlobalKey 和 Element 的关联关系存储到全局注册表中)

3. Element#123 挂载 RenderObject
   ↓
   Element#123._renderObject = RenderBox#456
   RenderBox#456 包含位置:x=0, y=229, width=300, height=50

4. 用户长按,执行你的代码:
   ↓
   myKey.currentContext  // → Element#123
   ↓
   Element#123.findRenderObject()  // → RenderBox#456
   ↓
   RenderBox#456.localToGlobal()  // → Offset(0, 229)

运行结果

代码实例

Dart 复制代码
import 'package:flutter/material.dart';

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

  @override
  State<DemoPage> createState() => _DemoPageState();
}

class _DemoPageState extends State<DemoPage> {

  //变量
  final Map<String, GlobalKey> _itemKeys = {}; //存储每个列表的GlobalKey
  String? _selectedId; //当前被长按的项的ID
  double? _buttonTop; //删除按钮距离屏幕顶部的距离

  //数据
  List<String> _items = List.generate(20, (i) => '任务 ${i + 1}');

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

    //为每个列表项创建唯一的GlobalKey
    for (var i = 0; i < _items.length; i++) {
      _itemKeys[i.toString()] = GlobalKey();
    }
  }


  //==========================显示删除列表=====================
  void _showDeleteButton(String id) {

    //高亮被选中的项
    setState(() => _selectedId = id);
    print("当前被选中的项的下标为:$id");

    //等待布局完成后再计算位置
    WidgetsBinding.instance.addPostFrameCallback((_) { //addPostFrameCallback是一个"在帧绘制之后执行"的回调函数

      //获取该子项的GlobalKey
      final key = _itemKeys[id];
      print("该子项的GlobalKey为$key");

      //检查Key是否有效
      if (key?.currentContext != null) {
        print("该子项的key为${key!.currentContext}");

        //获取RenderBox(包含位置和大小信息)
        final box = key!.currentContext!.findRenderObject() as RenderBox;
        print("获取到的RenderBox信息为:$box");

        //获取该Widget在屏幕上的位置
        final position = box.localToGlobal(Offset.zero);

        //计算删除按钮的位置
        setState(
                () => _buttonTop = position.dy - 35 //这里可以根据自己的需要调试按钮位置
        );
        print("该子项删除按钮的子项为:$_buttonTop");
      }
    });
  }


  //删除逻辑
  void _deleteItem(String id) {
    setState(() {
      _items.removeAt(int.parse(id)); //从数据源删除
      _itemKeys.remove(id);//清理对应的GlobalKey
      _selectedId = null;//清除选中状态
      _buttonTop = null;//隐藏删除按钮
    });
  }

  void _cancelDelete() {
    setState(() {
      _selectedId = null;
      _buttonTop = null;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('长按删除示例')),
      body: Stack(
        children: [
          ListView.builder(
            itemCount: _items.length,
            itemBuilder: (ctx, i) {

              final id = i.toString();

              return GestureDetector(
                onTap: _cancelDelete, //点击其他地方取消 ???

                child: Container(
                  key: _itemKeys[id], //绑定GlobalKey,赋给 Widget 的 key 属性
                  color: _selectedId == id ? Colors.blue.withOpacity(0.1) : null,//高亮被选中的项
                  child: ListTile(
                    title: Text(_items[i]),
                    onLongPress: () => _showDeleteButton(id), //长按触发删除按钮
                  ),
                ),
              );

            },
          ),

          //悬浮按钮
          if (_selectedId != null && _buttonTop != null)
            Positioned(
              left: 50,
              right: 50,
              top: _buttonTop!,
              child: ElevatedButton(
                onPressed: () => _deleteItem(_selectedId!),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.red,
                  foregroundColor: Colors.white,
                ),
                child: Text('删除'),
              ),
            ),
        ],
      ),
    );
  }
}
相关推荐
西西学代码2 小时前
Flutter---StatefulBuilder
flutter
●VON3 小时前
AtomGit Flutter鸿蒙客户端:鸿蒙平台集成
flutter·华为·跨平台·harmonyos·鸿蒙
●VON4 小时前
AtomGit Flutter鸿蒙客户端:共享组件
java·flutter·华为·harmonyos·鸿蒙
●VON4 小时前
AtomGit Flutter鸿蒙客户端:本地存储
flutter·华为·跨平台·harmonyos·鸿蒙
●VON5 小时前
AtomGit Flutter鸿蒙客户端:Provider状态管理
flutter·华为·跨平台·harmonyos·鸿蒙
MemoriKu5 小时前
Flutter 相册 APP 视频模态稳定化实战:从视频抽帧、Embedding 元数据到 Android 真机启动修复
android·开发语言·前端·flutter·架构·音视频·embedding
nice先生的狂想曲5 小时前
flutter页面滚动TabBar+TabBarView
flutter·客户端
nice先生的狂想曲5 小时前
flutter的freezed
flutter·客户端
恋猫de小郭6 小时前
flutter_agent_lens 用 MCP 服务,将 Flutter DevTools 暴露给 AI
android·前端·flutter