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('删除'),
              ),
            ),
        ],
      ),
    );
  }
}
相关推荐
恋猫de小郭10 小时前
Amper 正式转正 Kotlin Toolchain ,Gradle 未来何去何从
android·前端·flutter
张风捷特烈11 小时前
Flutter 类库大揭秘#02 | path_provider 各平台实现
前端·flutter
TT_Close1 天前
别劝退了!5秒搞定 Flutter 鸿蒙 FVM 起跑线
flutter·harmonyos·visual studio code
你听得到111 天前
用户说 App 卡,但说不清在哪?我把 Flutter 监控 SDK 升级成了链路观测工作台
前端·flutter·性能优化
stringwu3 天前
Flutter 开发必备:MVI 架构的高效实现指南
前端·flutter
程序员老刘4 天前
Flutter版本选择指南:3.44系列继续观望 | 2026年6月
flutter·ai编程·客户端
用户965597361905 天前
Provider vs Bloc vs GetX vs Riverpod:Flutter 状态管理方案怎么选?
flutter
恋猫de小郭5 天前
Flutter Patchwork,不用 Fork 改依赖包源码的第三方工具
android·前端·flutter
程序员老刘6 天前
跑分第一的编程大模型,我为啥不用?
flutter·ai编程·vibecoding
恋猫de小郭6 天前
苹果 AirPods 协议,Android 也可以使用完整版 AirPods 能力
android·前端·flutter