Flutter实现底部导航栏背景模糊效果的方法
实现Flutter底部导航栏背景模糊效果
我想在Flutter中实现底部导航栏模糊屏幕底部背景的效果(参考示例效果:
),但目前做出的是带有上下厚边框的白色背景导航栏,会遮挡页面内容(当前效果:
)。以下是我的代码:
class _MainPageState extends State<MainPage> { var currentIndex = 0; void onTabTapped(int index) { setState(() { currentIndex = index; HapticFeedback.lightImpact(); }); } final screens = [ const HomePage(), NotificationPage(), MessagePage(), BookmarkPage(), ]; List<IconData> listOfIcons = [ Icons.home_rounded, Icons.notifications, Icons.message_rounded, Icons.library_books, ]; List<String> listOfStrings = [ 'Home', 'T.báo', 'T.Nhắn', 'B.Đăng', ]; @override Widget build(BuildContext context) { double displayWidth = MediaQuery.of(context).size.width; return Scaffold( backgroundColor: Colors.white, body: SafeArea(child: screens[currentIndex]), bottomNavigationBar: Container( margin: EdgeInsets.all(displayWidth * .03), height: displayWidth * .155, decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withOpacity(.1), blurRadius: 30, offset: Offset(0, 10), ), ], borderRadius: BorderRadius.circular(50), ), child: ListView.builder( itemCount: 4, scrollDirection: Axis.horizontal, padding: EdgeInsets.symmetric(horizontal: displayWidth * .02), itemBuilder: (context, index) => InkWell( onTap: () { setState(() { currentIndex = index; HapticFeedback.lightImpact(); }); }, splashColor: Colors.transparent, highlightColor: Colors.transparent, child: Stack( children: [ AnimatedContainer( duration: Duration(seconds: 1), curve: Curves.fastLinearToSlowEaseIn, width: index == currentIndex ? displayWidth * .32 : displayWidth * .18, alignment: Alignment.center, child: AnimatedContainer( duration: Duration(seconds: 1), curve: Curves.fastLinearToSlowEaseIn, height: index == currentIndex ? displayWidth * .12 : 0, width: index == currentIndex ? displayWidth * .32 : 0, decoration: BoxDecoration( color: index == currentIndex ? Color(0xffEC1C24).withOpacity(.2) : Colors.transparent, borderRadius: BorderRadius.circular(50), ), ), ), AnimatedContainer( duration: Duration(seconds: 1), curve: Curves.fastLinearToSlowEaseIn, width: index == currentIndex ? displayWidth * .31 : displayWidth * .18, alignment: Alignment.center, child: Stack( children: [ Row( children: [ AnimatedContainer( duration: Duration(seconds: 1), curve: Curves.fastLinearToSlowEaseIn, width: index == currentIndex ? displayWidth * .13 : 0, ), AnimatedOpacity( opacity: index == currentIndex ? 1 : 0, duration: Duration(seconds: 1), curve: Curves.fastLinearToSlowEaseIn, child: Text( index == currentIndex ? '${listOfStrings[index]}' : '', style: TextStyle( color: Color(0xffEC1C24), fontWeight: FontWeight.w600, fontSize: 15, ), ), ), ], ), Row( children: [ AnimatedContainer( duration: Duration(seconds: 1), curve: Curves.fastLinearToSlowEaseIn, width: index == currentIndex ? displayWidth * .03 : 20, ), Icon( listOfIcons[index], size: displayWidth * .076, color: index == currentIndex ? Color(0xffEC1C24) : Colors.black26, ), ], ), ], ), ), ], ), ), ), ), ); } }
解决方案
要实现背景模糊效果,核心是使用BackdropFilter组件添加高斯模糊,同时调整导航栏背景的透明度,并让页面内容避开导航栏区域:
- 替换导航栏背景为模糊效果:用
Stack包裹原导航栏内容,在底层添加BackdropFilter实现模糊,同时设置半透明背景色 - 避免内容被遮挡:给
SafeArea包裹的页面内容添加底部padding,值等于导航栏的高度(含margin) - 调整阴影与圆角:保留原有的阴影和圆角效果,确保视觉一致性
修改后的完整代码如下:
class _MainPageState extends State<MainPage> { var currentIndex = 0; void onTabTapped(int index) { setState(() { currentIndex = index; HapticFeedback.lightImpact(); }); } final screens = [ const HomePage(), NotificationPage(), MessagePage(), BookmarkPage(), ]; List<IconData> listOfIcons = [ Icons.home_rounded, Icons.notifications, Icons.message_rounded, Icons.library_books, ]; List<String> listOfStrings = [ 'Home', 'T.báo', 'T.Nhắn', 'B.Đăng', ]; @override Widget build(BuildContext context) { double displayWidth = MediaQuery.of(context).size.width; final navBarHeight = displayWidth * .155; final navBarMargin = displayWidth * .03; return Scaffold( backgroundColor: Colors.white, // 给页面内容添加底部padding,避免被导航栏遮挡 body: SafeArea( child: Padding( padding: EdgeInsets.only(bottom: navBarHeight + navBarMargin * 2), child: screens[currentIndex], ), ), bottomNavigationBar: Container( margin: EdgeInsets.all(navBarMargin), height: navBarHeight, child: Stack( children: [ // 背景模糊层 ClipRRect( borderRadius: BorderRadius.circular(50), child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10), child: Container( decoration: BoxDecoration( color: Colors.white.withOpacity(0.7), // 半透明白色背景 borderRadius: BorderRadius.circular(50), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(.1), blurRadius: 30, offset: const Offset(0, 10), ), ], ), ), ), ), // 原导航栏内容 ListView.builder( itemCount: 4, scrollDirection: Axis.horizontal, padding: EdgeInsets.symmetric(horizontal: displayWidth * .02), itemBuilder: (context, index) => InkWell( onTap: () { setState(() { currentIndex = index; HapticFeedback.lightImpact(); }); }, splashColor: Colors.transparent, highlightColor: Colors.transparent, child: Stack( children: [ AnimatedContainer( duration: const Duration(seconds: 1), curve: Curves.fastLinearToSlowEaseIn, width: index == currentIndex ? displayWidth * .32 : displayWidth * .18, alignment: Alignment.center, child: AnimatedContainer( duration: const Duration(seconds: 1), curve: Curves.fastLinearToSlowEaseIn, height: index == currentIndex ? displayWidth * .12 : 0, width: index == currentIndex ? displayWidth * .32 : 0, decoration: BoxDecoration( color: index == currentIndex ? const Color(0xffEC1C24).withOpacity(.2) : Colors.transparent, borderRadius: BorderRadius.circular(50), ), ), ), AnimatedContainer( duration: const Duration(seconds: 1), curve: Curves.fastLinearToSlowEaseIn, width: index == currentIndex ? displayWidth * .31 : displayWidth * .18, alignment: Alignment.center, child: Stack( children: [ Row( children: [ AnimatedContainer( duration: const Duration(seconds: 1), curve: Curves.fastLinearToSlowEaseIn, width: index == currentIndex ? displayWidth * .13 : 0, ), AnimatedOpacity( opacity: index == currentIndex ? 1 : 0, duration: const Duration(seconds: 1), curve: Curves.fastLinearToSlowEaseIn, child: Text( index == currentIndex ? '${listOfStrings[index]}' : '', style: const TextStyle( color: Color(0xffEC1C24), fontWeight: FontWeight.w600, fontSize: 15, ), ), ), ], ), Row( children: [ AnimatedContainer( duration: const Duration(seconds: 1), curve: Curves.fastLinearToSlowEaseIn, width: index == currentIndex ? displayWidth * .03 : 20, ), Icon( listOfIcons[index], size: displayWidth * .076, color: index == currentIndex ? const Color(0xffEC1C24) : Colors.black26, ), ], ), ], ), ), ], ), ), ), ], ), ), ); } }
关键修改说明
- BackdropFilter:通过
ImageFilter.blur设置模糊程度(sigmaX/Y值越大越模糊),配合ClipRRect保证模糊效果只在圆角范围内生效 - 半透明背景:将原白色背景改为
Colors.white.withOpacity(0.7),既保留白色基调又能透出底层页面内容 - 底部padding:计算导航栏总高度(自身高度+上下margin),给页面内容添加对应padding,解决遮挡问题
内容的提问来源于stack exchange,提问作者Trong Nhan Nguyen Le
相关产品推荐
相关产品推荐

