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

相关推荐
勇往直前plus1 天前
Vue3(篇一) 核心概念——响应式模板语法与组件基础
前端·javascript·vue.js
咩咩啃树皮1 天前
第43篇:Vue3计算属性(computed)完全精讲——缓存机制、依赖计算、业务最优解
前端·vue.js·缓存
徐小夕1 天前
花了一周,3亿tokens,我开源了一款 Word 文档智能审查平台,文稿自动质检+可视化分析,告别低效人工审核
前端·算法·github
kyriewen1 天前
别再乱用useEffect了——你写的10个里有8个不该存在
前端·javascript·react.js
Ivanqhz1 天前
Rust &‘static str浅析
java·前端·javascript·rust
IT_陈寒1 天前
SpringBoot这个分页坑,我踩了三天才爬出来
前端·人工智能·后端
颜酱1 天前
05 | 召回前置准备:根据业务数据库生成各数据库(读取配置阶段)
前端·人工智能·后端
zandy10111 天前
衡石 Agentic BI的ReAct 推理框架在 Agentic BI 中的工程化实践
前端·javascript·react.js
罗超驿1 天前
JavaEE进阶之路:从Web架构原理到HTML标签全解析
前端·html·web·javaee
西安小哥1 天前
从前端到AI工程师:一场跨越鸿沟的真实蜕变之旅
前端