You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter递归使用Navigator.push时如何保留AppBar与底部导航栏

解决Flutter树形ListView导航时底部导航栏消失/缺少Material组件的问题

方案一:在当前页面维护层级栈(推荐)

不用Navigator跳转页面,而是通过维护层级栈动态切换列表数据,底部导航栏会始终保留。

实现代码

class TreePage extends StatefulWidget {
  @override
  State<TreePage> createState() => _TreePageState();
}

class _TreePageState extends State<TreePage> {
  // 解析后的树形数据
  final List<Map<String, dynamic>> _rootData = [
    {
      "label": "All",
      "children": [
        {"label": "test1", "children": [{"label": "test1-1"}]},
        {"label": "test2"},
        {"label": "test3"}
      ]
    }
  ];

  // 层级栈:保存每一层的列表数据
  late List<List<Map<String, dynamic>>> _levelStack;
  // 当前显示的列表数据
  late List<Map<String, dynamic>> _currentList;

  @override
  void initState() {
    super.initState();
    // 初始化:根层级入栈,默认显示根列表
    _levelStack = [_rootData];
    _currentList = _rootData;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(_getCurrentTitle()),
        // 非根层级显示返回按钮
        leading: _levelStack.length > 1
            ? IconButton(
                icon: const Icon(Icons.arrow_back),
                onPressed: () {
                  setState(() {
                    _levelStack.removeLast();
                    _currentList = _levelStack.last;
                  });
                },
              )
            : null,
      ),
      body: ListView.separated(
        shrinkWrap: true,
        itemBuilder: (context, index) {
          final item = _currentList[index];
          final hasChildren = item.containsKey("children") &&
              item["children"] != null &&
              (item["children"] as List).isNotEmpty;

          return ListTile(
            title: Text(item["label"]),
            trailing: hasChildren
                ? const Icon(Icons.arrow_forward_ios, size: 16)
                : null,
            onTap: () {
              if (hasChildren) {
                setState(() {
                  // 子节点列表入栈,更新当前显示数据
                  _levelStack.add(item["children"] as List<Map<String, dynamic>>);
                  _currentList = item["children"] as List<Map<String, dynamic>>;
                });
              } else {
                // 无子节点时的处理逻辑(如跳转详情页)
              }
            },
          );
        },
        separatorBuilder: (_, __) => const Divider(),
        itemCount: _currentList.length,
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: "首页"),
          BottomNavigationBarItem(icon: Icon(Icons.settings), label: "设置"),
        ],
      ),
    );
  }

  // 获取当前层级的标题
  String _getCurrentTitle() {
    if (_levelStack.length == 1) {
      return _currentList[0]["label"];
    } else {
      return _currentList.isNotEmpty ? _currentList[0]["label"] : "";
    }
  }
}

方案二:使用嵌套Navigator保留底部导航栏

如果必须用Navigator.push跳转,可在底部导航栏页面中嵌套一个独立Navigator,跳转时仅更新嵌套区域,底部导航栏保持显示。

实现代码

  1. 底部导航栏主页面:
class HomePage extends StatefulWidget {
  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final GlobalKey<NavigatorState> _nestedNavKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: BottomNavigationBar(
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: "首页"),
          BottomNavigationBarItem(icon: Icon(Icons.settings), label: "设置"),
        ],
      ),
      body: Navigator(
        key: _nestedNavKey,
        initialRoute: "/",
        onGenerateRoute: (settings) {
          if (settings.routeName == "/") {
            return MaterialPageRoute(
              builder: (context) => const TreeSubPage(
                data: [
                  {
                    "label": "All",
                    "children": [
                      {"label": "test1", "children": [{"label": "test1-1"}]},
                      {"label": "test2"},
                      {"label": "test3"}
                    ]
                  }
                ],
                title: "All",
              ),
            );
          }
          final args = settings.arguments as Map<String, dynamic>;
          return PageRouteBuilder(
            pageBuilder: (context, _, __) => TreeSubPage(
              data: args["data"],
              title: args["title"],
            ),
            transitionDuration: Duration.zero,
            reverseTransitionDuration: Duration.zero,
          );
        },
      ),
    );
  }
}
  1. 树形列表子页面:
class TreeSubPage extends StatelessWidget {
  final List<Map<String, dynamic>> data;
  final String title;

  const TreeSubPage({required this.data, required this.title, Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: ListView.separated(
        shrinkWrap: true,
        itemBuilder: (context, index) {
          final item = data[index];
          final hasChildren = item.containsKey("children") &&
              item["children"] != null &&
              (item["children"] as List).isNotEmpty;

          return ListTile(
            title: Text(item["label"]),
            trailing: hasChildren
                ? const Icon(Icons.arrow_forward_ios, size: 16)
                : null,
            onTap: () {
              if (hasChildren) {
                Navigator.push(
                  context,
                  PageRouteBuilder(
                    pageBuilder: (context, _, __) => TreeSubPage(
                      data: item["children"] as List<Map<String, dynamic>>,
                      title: item["label"],
                    ),
                    transitionDuration: Duration.zero,
                    reverseTransitionDuration: Duration.zero,
                  ),
                );
              } else {
                // 无子节点的处理逻辑
              }
            },
          );
        },
        separatorBuilder: (_, __) => const Divider(),
        itemCount: data.length,
      ),
    );
  }
}

问题根源说明

  • 直接Navigator.push新Scaffold会替换整个页面(包括底部导航栏所在的父Scaffold),导致底部导航栏消失。
  • 直接Navigator.push当前类(如CurrentClass)时,若该类未嵌套在Material组件树中,ListTile等组件会因缺少Material上下文报错。

内容的提问来源于stack exchange,提问作者Kate Jo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 01:06:27