Flutter进阶:OverlayEntry 插入图层管理器 NOverlayZIndexManager

一、需求来源

最近遇到一个需求:当使用 OverlayEntry 实现 Dialog & Sheet & Drawer & Toast 效果时,无法控制显示 Z 轴上的显示顺序 zIndex. 经过思考初步实现,用 模型封装实现, 模型如下:

dart 复制代码
class NOverlayZIndexItem {
  NOverlayZIndexItem({
    required this.entry,
    required this.zIndex,
    this.key,
  });

  /// 视图
  final OverlayEntry entry;

  /// z 轴层次
  final int zIndex;

  /// 唯一 key
  final String? key;
}

zIndex 决定在z 轴上视图层次,越大的在屏幕最上方(同数值上方叠加)。 key 如果相视图刷新而非新建。

二、使用示例

zIndex = 100 视图在最底下; zIndex = 150 视图在中间; zIndex = 200 视图在最上边。

dart 复制代码
var zIndex = 100;

void showContent({
  required int zIndex,
  Alignment? alignment,
  Color? color,
  Widget? child,
}) {
  final key = "999";

  final offset = NOverlayZIndexManager.instance.items.length * 20.0;
  OverlayEntry? entry;
  entry = OverlayEntry(builder: (_) {
    final message = [zIndex, key].join(", ");
    DLog.d(message);
    return Positioned(
      left: offset,
      right: 0,
      top: 400.0 + offset,
      child: Align(
        alignment: Alignment.center,
        child: GestureDetector(
          onTap: () {
            NOverlayZIndexManager.instance.removeWhere((e) => e.entry == entry);
          },
          child: child ??
              Container(
                width: 100,
                height: 100,
                alignment: alignment ?? Alignment.topLeft,
                decoration: BoxDecoration(
                  color: color ?? ColorEx.random,
                  border: Border.all(color: Colors.blue),
                ),
                child: Text(message),
              ),
        ),
      ),
    );
  });

  NOverlayZIndexManager.instance.show(
    context: context,
    entry: entry,
    zIndex: zIndex,
    // key: key,
  );
}
/// 插入视图1
onOverlayOne() async {
  zIndex = 100;
  displayContent(zIndex: zIndex, alignment: Alignment.bottomCenter, color: Colors.green);
}
/// 插入视图2
onOverlayTwo() async {
  zIndex = 200;
  displayContent(
    zIndex: zIndex,
    color: Colors.yellow,
    child: ElevatedButton(
      onPressed: () {},
      child: Text("ElevatedButton"),
    ),
  );
}
/// 插入视图3
onOverlayThree() async {
  zIndex = 150;
  displayContent(
    zIndex: zIndex,
    color: Colors.red,
    child: ElevatedButton(
      onPressed: () {},
      child: FlutterLogo(),
    ),
  );
}

onClear() async {
  NOverlayZIndexManager.instance.clear();
}

三、源码 NOverlayZIndexManager

dart 复制代码
//
//  NOverlayZIndexManager`.dart
//  projects
//
//  Created by shang on 2026/5/6 11:27.
//  Copyright © 2026/5/6 shang. All rights reserved.
//

import 'package:flutter/widgets.dart';

class NOverlayZIndexItem {
  NOverlayZIndexItem({
    required this.entry,
    required this.zIndex,
    this.key,
  });

  final OverlayEntry entry;
  final int zIndex;
  final String? key;
}

/// 全局 Overlay 层次 ZIndex 管理
class NOverlayZIndexManager {
  NOverlayZIndexManager._();

  static final instance = NOverlayZIndexManager._();

  // final context = AppNavigator.navigatorKey.currentContext!;
  // late final overlayState = Overlay.of(context, rootOverlay: true);

  final List<NOverlayZIndexItem> _items = [];
  List<NOverlayZIndexItem> get items => _items;

  void clear() {
    for (var i = 0; i < items.length; i++) {
      items[i].entry.remove();
    }
    _items.clear();
  }

  /// 插入(核心)
  NOverlayZIndexItem show({
    required BuildContext context,
    required OverlayEntry entry,
    required int zIndex,
    String? key,
  }) {
    // context ??= AppNavigator.navigatorKey.currentContext!;

    /// ✅ 1. 已存在 → 更新
    if (key != null) {
      final existIndex = _items.indexWhere((e) => e.key == key);
      if (existIndex != -1) {
        final existItem = _items[existIndex];
        existItem.entry.markNeedsBuild();

        // 👉 zIndex 变化才移动
        // if (existItem.zIndex != zIndex) {
        //   updateZIndex(key: key, newZIndex: zIndex);
        // }
        return existItem;
      }
    }

    /// 1️⃣ 先找插入位置(有序)
    int insertIndex = _items.indexWhere((e) => e.zIndex > zIndex);
    if (insertIndex == -1) {
      insertIndex = _items.length;
    }

    /// 2️⃣ 插入到本地列表
    final item = NOverlayZIndexItem(
      entry: entry,
      zIndex: zIndex,
      key: key,
    );

    _items.insert(insertIndex, item);
    return _insertOverlay(context: context, item: item, index: insertIndex);
  }

  NOverlayZIndexItem _insertOverlay({
    required BuildContext context,
    required NOverlayZIndexItem item,
    required int index,
  }) {
    final overlayState = Overlay.of(context, rootOverlay: true);

    NOverlayZIndexItem? below;
    NOverlayZIndexItem? above;

    /// 找"下方"元素(zIndex 更小)
    if (index > 0) {
      below = _items[index - 1];
    }

    /// 找"上方"元素(zIndex 更大)
    if (index < _items.length - 1) {
      above = _items[index + 1];
    }

    if (above != null) {
      overlayState.insert(item.entry, below: above.entry);
      return item;
    }

    /// 优先使用 below(更稳定)
    if (below != null) {
      overlayState.insert(item.entry, above: below.entry);
      return item;
    }

    /// 第一个元素
    overlayState.insert(item.entry);
    return item;
  }

  /// 删除
  void removeWhere(bool Function(NOverlayZIndexItem e) test, [int start = 0]) {
    final index = _items.indexWhere(test, start);
    if (index == -1) {
      return;
    }

    final item = _items.removeAt(index);
    item.entry.remove();
  }

  /// 根据 key 删除
  void removeByKey(String key) {
    final targets = _items.where((e) => e.key == key).toList();
    for (final item in targets) {
      item.entry.remove();
      _items.remove(item);
    }
  }

  /// 更新 UI(不动层级)
  void markNeedsBuild(String key) {
    final item = _items.where((e) => e.key == key).firstOrNull;
    item?.entry.markNeedsBuild();
  }

  /// 修改 zIndex(关键:移动位置)
  void updateZIndex({required BuildContext context, required String key, required int newZIndex}) {
    final overlayState = Overlay.of(context, rootOverlay: true);

    final index = _items.indexWhere((e) => e.key == key);
    if (index == -1) {
      return;
    }

    /// 重新计算位置
    var insertIndex = _items.indexWhere((e) => e.zIndex > newZIndex);
    if (insertIndex == -1) {
      insertIndex = _items.length;
    }

    final item = _items.removeAt(index);
    final itemNew = NOverlayZIndexItem(entry: item.entry, zIndex: newZIndex, key: key);
    _items.insert(insertIndex, itemNew);

    /// ⚠️ 关键:用 rearrange,而不是 insert
    overlayState.rearrange(_items.map((e) => e.entry));
  }
}

总结

核心是基于 OverlayState 方法封装:

dart 复制代码
void insert(OverlayEntry entry, { OverlayEntry? below, OverlayEntry? above }) 

void rearrange(Iterable<OverlayEntry> newEntries, { OverlayEntry? below, OverlayEntry? above }) 

n_overlay_zindex_manager

相关推荐
放下华子我只抽RuiKe51 小时前
React 从入门到生产(四):自定义 Hook
前端·javascript·人工智能·深度学习·react.js·自然语言处理·前端框架
IT_陈寒3 小时前
Redis缓存击穿把我整不会了,原来还有这手操作
前端·人工智能·后端
idcu3 小时前
深入 Lyt.js 组件系统:L2 渲染引擎层的核心
前端·typescript
这是程序猿3 小时前
Spring Boot自动配置详解
java·大数据·前端
文心快码BaiduComate3 小时前
干货|Comate Harness Engineering工程实践指南
前端·后端·程序员
还有多久拿退休金3 小时前
一张栈的图,治好你面试答不出 script 阻塞的病
前端·javascript
光辉GuangHui3 小时前
Agent Skill 也需要测试:如何搭建 Skill 评估框架
前端·后端·llm
To_OC3 小时前
我终于搞懂 Claude Code 核心逻辑!90%的人都用错了模式
前端·ai编程
蓝宝石的傻话4 小时前
Headless浏览器的隐形陷阱:为什么你的AI自动化工具抓不到页面早期错误?
前端