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

相关推荐
前端snow8 小时前
ai agent --- postgreSQL 关系型数据库
前端
卷无止境9 小时前
ECharts:把数据变成故事的可视化利器
前端
Eric_见嘉9 小时前
在职前端 Skill 和 MCP 分享
前端·后端·agent
liangshanbo12159 小时前
面试题:如何优化 Webpack 的打包速度?
前端·webpack·node.js
wengad10 小时前
从 wangEditor 迁到 wangEditor-next
前端·javascript·vue.js
行者-全栈开发11 小时前
【前端/JS框架】CVE-2026-3854:GitHub Enterprise Server X-Stat注入XSS漏洞深度剖析与紧急修复指南
前端·javascript·github
计算机魔术师12 小时前
AMD砸82亿买下李飞飞,这次不卖芯片卖什么?
前端
hzxpaipai12 小时前
杭州企业官网怎么建设?从策划到上线的5步建站方法
运维·服务器·前端·网络
怕浪猫12 小时前
2026年金九银十,我面了22个前端
前端·javascript·面试
IT_陈寒12 小时前
Vue 这个响应式陷阱,我的头发都掉没了
前端·人工智能·后端