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

Flutter:实现Scaffold主体内局部导航,保留顶部及底部导航栏

如何在Flutter Scaffold主体内实现局部导航(保留顶部AppBar和底部导航栏)

需求:点击“+”按钮时,替换Scaffold的当前主体内容,同时保持顶部AppBar和底部导航栏不变。

原有代码(main.dart):

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: const Home(),
    theme: ThemeData(
      colorScheme: ColorScheme.fromSeed(
        seedColor: Colors.yellow,
      ),
    ),
  ));
}


class Home extends StatefulWidget {
  const Home({super.key});

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  int _selectedIndex = 0;
  final List<Widget> _screens = <Widget>[
    HomeScreen(),
    StatsScreen()
  ];

  void _onItemTap(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: NavigationBar(
        labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected,
        selectedIndex: _selectedIndex,
        destinations: const <Widget>[
          NavigationDestination(
              icon: Icon(Icons.home),
              label: 'Home'
          ),
          NavigationDestination(
            icon: Icon(Icons.trending_up),
            label: 'Statistics',
          ),
        ],
        onDestinationSelected: _onItemTap,
      ),
      body: _screens.elementAt(_selectedIndex),
    );
  }
}


class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home'),
        actions: [
          IconButton(
            onPressed: () {
              Navigator.push(context, MaterialPageRoute(builder: (context) => SettingsScreen()));
            },
            icon: const Icon(Icons.settings),
          ),
        ],
      ),
      body: Column(
        children: [
          Expanded(child: Container(color: Colors.yellow)),
          Container(
              padding: EdgeInsets.all(8),
              alignment: Alignment.bottomRight,
              child: FloatingActionButton(
                  onPressed: () {}, //need to push a new body here
                  child: Icon(Icons.add)))
        ],
      ),
    );
  }
}

// 补充缺失的页面类
class StatsScreen extends StatelessWidget {
  const StatsScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Statistics')),
      body: Center(child: Text('Statistics Screen')),
    );
  }
}

class SettingsScreen extends StatelessWidget {
  const SettingsScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Settings')),
      body: Center(child: Text('Settings Screen')),
    );
  }
}

class AddNewScreen extends StatelessWidget {
  const AddNewScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Add New')),
      body: Center(child: Text('Add New Screen')),
    );
  }
}

参考图:
参考图

遇到的问题:使用默认Navigator会在当前Scaffold上方叠加新页面,导致顶部AppBar和底部导航栏被覆盖;看过类似问题但答案不清晰,没有完整代码示例。


解决方案:使用自定义局部Navigator

核心思路是给需要局部导航的区域单独创建一个Navigator组件,用GlobalKey管理其导航栈,这样导航操作只会影响该区域,不会覆盖外层的AppBar和底部导航栏。

修改后的完整代码:

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: const Home(),
    theme: ThemeData(
      colorScheme: ColorScheme.fromSeed(
        seedColor: Colors.yellow,
      ),
    ),
  ));
}

class Home extends StatefulWidget {
  const Home({super.key});

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  int _selectedIndex = 0;
  // 给每个底部导航页面维护独立的局部导航key
  final _homeNavigatorKey = GlobalKey<NavigatorState>();
  final _statsNavigatorKey = GlobalKey<NavigatorState>();

  void _onItemTap(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 把AppBar提到外层,确保所有底部导航页面共享统一的顶部栏(可根据选中页切换标题)
      appBar: AppBar(
        title: Text(_selectedIndex == 0 ? 'Home' : 'Statistics'),
        actions: [
          IconButton(
            onPressed: () {
              // 根据当前选中页面,调用对应的局部Navigator打开设置页
              if (_selectedIndex == 0) {
                _homeNavigatorKey.currentState?.push(
                  MaterialPageRoute(builder: (context) => const SettingsScreen()),
                );
              } else {
                _statsNavigatorKey.currentState?.push(
                  MaterialPageRoute(builder: (context) => const SettingsScreen()),
                );
              }
            },
            icon: const Icon(Icons.settings),
          ),
        ],
      ),
      bottomNavigationBar: NavigationBar(
        labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected,
        selectedIndex: _selectedIndex,
        destinations: const <Widget>[
          NavigationDestination(
            icon: Icon(Icons.home),
            label: 'Home',
          ),
          NavigationDestination(
            icon: Icon(Icons.trending_up),
            label: 'Statistics',
          ),
        ],
        onDestinationSelected: _onItemTap,
      ),
      body: IndexedStack(
        index: _selectedIndex,
        children: [
          // Home页面的局部导航容器
          Navigator(
            key: _homeNavigatorKey,
            onGenerateRoute: (settings) {
              return MaterialPageRoute(
                builder: (context) => const HomeContent(),
              );
            },
          ),
          // Statistics页面的局部导航容器
          Navigator(
            key: _statsNavigatorKey,
            onGenerateRoute: (settings) {
              return MaterialPageRoute(
                builder: (context) => const StatsContent(),
              );
            },
          ),
        ],
      ),
    );
  }
}

// Home页面的初始内容(不再嵌套Scaffold)
class HomeContent extends StatelessWidget {
  const HomeContent({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Expanded(child: Container(color: Colors.yellow)),
        Container(
          padding: const EdgeInsets.all(8),
          alignment: Alignment.bottomRight,
          child: FloatingActionButton(
            onPressed: () {
              // 使用当前上下文的局部Navigator打开新页面
              Navigator.push(
                context,
                MaterialPageRoute(builder: (context) => const AddNewScreen()),
              );
            },
            child: const Icon(Icons.add),
          ),
        ),
      ],
    );
  }
}

// Statistics页面的初始内容
class StatsContent extends StatelessWidget {
  const StatsContent({super.key});

  @override
  Widget build(BuildContext context) {
    return const Center(child: Text('Statistics Screen'));
  }
}

// 设置页面(局部导航打开)
class SettingsScreen extends StatelessWidget {
  const SettingsScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Settings')),
      body: const Center(child: Text('Settings Screen')),
    );
  }
}

// 点击+按钮打开的新页面
class AddNewScreen extends StatelessWidget {
  const AddNewScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Add New')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('这是点击+按钮打开的新页面'),
            ElevatedButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('返回'),
            ),
          ],
        ),
      ),
    );
  }
}

关键改动说明:

  • 移除嵌套Scaffold:删掉原来HomeScreen和StatsScreen里的Scaffold,将AppBar移到外层的Home Scaffold中,确保底部导航栏和顶部栏始终可见。
  • 创建局部Navigator:给每个底部导航页面分配GlobalKey<NavigatorState>,用IndexedStack包裹两个Navigator组件,每个Navigator对应独立的页面导航栈。
  • 局部导航操作:点击+按钮或设置按钮时,调用对应页面的局部Navigator来push新页面,仅替换当前主体内容,外层栏位保持不变。
  • IndexedStack的作用:切换底部导航时保留未选中页面的状态,避免重新初始化。

效果验证:

  • 点击Home页面的+按钮,主体区域会打开AddNewScreen,顶部AppBar和底部导航栏依然存在。
  • 点击返回按钮,回到HomeContent页面。
  • 切换底部导航到Statistics再切回Home,页面状态会被保留。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 05:24:55