Flutter开发:实现带特殊中间按钮的BottomNavigationBar并解决滑动空白页问题
解决Flutter底部导航栏滑动切换时出现空白中间页的问题
问题场景
实现包含三个图标的底部导航栏:
- 左侧图标对应TimelineScreen,右侧图标对应ProfileScreen
- 中间图标点击后通过CupertinoFullscreenDialogTransition打开图片上传对话框
当前功能基本实现,但滑动切换页面时会出现空白的中间页面。
问题原因
PageView中额外添加了空白的中间页面,滑动时会切换到该空白页,而实际业务中中间图标仅触发弹窗,不需要对应页面。
解决方案
调整PageView的页面数量,移除空白中间页,并修正底部导航索引与PageView页面的映射关系:
import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State<MyApp> createState() => _MyAppState(); } class _MyAppState extends State<MyApp> { @override Widget build(BuildContext context) { return const MaterialApp( home: MyHomePage(), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key}); @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _currentIndex = 0; final PageController _pageController = PageController( initialPage: 0, ); void _onTabTapped(int index) { if (index == 0) { setState(() { _currentIndex = index; _pageController.animateToPage( 0, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); }); } else if (index == 2) { setState(() { _currentIndex = index; _pageController.animateToPage( 1, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); }); } else { Navigator.of(context).push( PageRouteBuilder( opaque: false, pageBuilder: (context, _, __) { return const FullDialogPage(); }, ), ); } } @override Widget build(BuildContext context) { return Scaffold( body: PageView( controller: _pageController, onPageChanged: (pageIndex) { setState(() { // 将PageView的索引映射到底部导航的索引 _currentIndex = pageIndex == 0 ? 0 : 2; }); }, children: const [ TimelineScreen(), ProfileScreen(), ], ), bottomNavigationBar: CupertinoTabBar( items: const [ BottomNavigationBarItem( icon: Icon(CupertinoIcons.home), ), BottomNavigationBarItem( icon: Icon(CupertinoIcons.search), ), BottomNavigationBarItem( icon: Icon(CupertinoIcons.person), ), ], currentIndex: _currentIndex, onTap: _onTabTapped, ), ); } } class TimelineScreen extends StatelessWidget { const TimelineScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Timeline'), ), backgroundColor: Colors.white, body: const Center( child: Text('Timeline Screen'), ), ); } } // 弹窗页面 class FullDialogPage extends StatefulWidget { const FullDialogPage({super.key}); @override FullDialogPageState createState() => FullDialogPageState(); } class FullDialogPageState extends State<FullDialogPage> with TickerProviderStateMixin { late AnimationController _primary, _secondary; late Animation<double> _animationPrimary, _animationSecondary; @override void initState() { _primary = AnimationController( vsync: this, duration: const Duration(milliseconds: 600)); _animationPrimary = Tween<double>(begin: 0, end: 1) .animate(CurvedAnimation(parent: _primary, curve: Curves.easeInOut)); _secondary = AnimationController( vsync: this, duration: const Duration(milliseconds: 600)); _animationSecondary = Tween<double>(begin: 0, end: 1) .animate(CurvedAnimation(parent: _secondary, curve: Curves.easeInOut)); _primary.forward(); super.initState(); } @override void dispose() { _primary.dispose(); _secondary.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return CupertinoFullscreenDialogTransition( primaryRouteAnimation: _animationPrimary, secondaryRouteAnimation: _animationSecondary, linearTransition: false, child: Scaffold( appBar: AppBar( backgroundColor: Colors.indigo[900], title: const Text("Upload an image"), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () { _primary.reverse(); Future.delayed(const Duration(milliseconds: 600), () { Navigator.of(context).pop(); }); }, ), ), ), ); } } class ProfileScreen extends StatelessWidget { const ProfileScreen({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Profile'), ), backgroundColor: Colors.white, body: const Center( child: Text('Profile Screen'), ), ); } }
关键修改点
- 移除PageView中的空白中间页面,仅保留
TimelineScreen和ProfileScreen两个页面 - 在
onPageChanged回调中,将PageView的索引(0和1)映射为底部导航对应的索引(0和2),确保底部导航选中状态正确 - 调整
_onTabTapped方法,点击底部导航的0或2时,对应跳转到PageView的0或1页面
内容的提问来源于stack exchange,提问作者Harlan Erskine
相关产品推荐
相关产品推荐

