Flutter全局固定底部导航栏及顶部固定播放器实现问询
Flutter 实现全局固定底部导航栏与上方播放器
核心实现思路
- 借助
Scaffold的bottomNavigationBar属性,将播放器组件与底部导航栏通过Column包裹,实现两者固定在页面底部 - 页面主体内容用
IndexedStack管理,切换页面时可保留原有状态(无需保留状态的话,直接切换Widget即可) - 播放器组件单独抽离,便于维护和全局状态同步
完整代码示例
1. 主应用结构
import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: '全局底部栏示例', theme: ThemeData(primarySwatch: Colors.blue), home: const MainScreen(), ); } } class MainScreen extends StatefulWidget { const MainScreen({super.key}); @override State<MainScreen> createState() => _MainScreenState(); } class _MainScreenState extends State<MainScreen> { // 当前选中页面索引 int _currentIndex = 0; // 播放器播放状态 bool _isPlaying = false; // 页面列表 final List<Widget> _pages = const [ HomePage(), ExplorePage(), LibraryPage(), ]; // 切换页面 void _onTabTapped(int index) { setState(() { _currentIndex = index; }); } // 切换播放/暂停状态 void _togglePlay() { setState(() { _isPlaying = !_isPlaying; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('全局底部栏Demo')), // 页面主体:用IndexedStack保留页面状态 body: IndexedStack( index: _currentIndex, children: _pages, ), // 底部固定区域:播放器 + 导航栏 bottomNavigationBar: Column( mainAxisSize: MainAxisSize.min, children: [ // 自定义播放器组件 PlayerBar( isPlaying: _isPlaying, onTogglePlay: _togglePlay, ), // 底部导航栏 BottomNavigationBar( currentIndex: _currentIndex, onTap: _onTabTapped, items: const [ BottomNavigationBarItem( icon: Icon(Icons.home), label: '首页', ), BottomNavigationBarItem( icon: Icon(Icons.explore), label: '发现', ), BottomNavigationBarItem( icon: Icon(Icons.library_music), label: '我的', ), ], ), ], ), ); } }
2. 播放器组件
class PlayerBar extends StatelessWidget { final bool isPlaying; final VoidCallback onTogglePlay; const PlayerBar({ super.key, required this.isPlaying, required this.onTogglePlay, }); @override Widget build(BuildContext context) { return Container( height: 60, color: Colors.grey[900], padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Row( children: [ // 专辑封面占位 const CircleAvatar( backgroundColor: Colors.blueGrey, child: Icon(Icons.music_note, color: Colors.white), ), const SizedBox(width: 12), // 歌曲信息 const Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '示例歌曲', style: TextStyle(color: Colors.white, fontSize: 14), ), Text( '示例歌手', style: TextStyle(color: Colors.grey, fontSize: 12), ), ], ), ), // 播放控制按钮 IconButton( icon: Icon( isPlaying ? Icons.pause : Icons.play_arrow, color: Colors.white, ), onPressed: onTogglePlay, ), const Icon(Icons.skip_next, color: Colors.white), ], ), ); } }
3. 示例页面
class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return const Center(child: Text('首页内容')); } } class ExplorePage extends StatelessWidget { const ExplorePage({super.key}); @override Widget build(BuildContext context) { return const Center(child: Text('发现页面内容')); } } class LibraryPage extends StatelessWidget { const LibraryPage({super.key}); @override Widget build(BuildContext context) { return const Center(child: Text('我的页面内容')); } }
关键细节说明
- 固定布局逻辑:
Column(mainAxisSize: MainAxisSize.min)确保底部区域仅占用自身高度,不会挤压页面主体内容,Scaffold会自动让body内容避开底部导航栏区域 - 页面状态保留:
IndexedStack可以保留每个页面的状态(比如滚动位置、输入框内容),如果不需要保留状态,直接替换为_pages[_currentIndex]即可 - 全局状态扩展:示例用
setState管理播放状态,实际项目中可结合Provider、Riverpod等状态管理工具,实现播放器状态在所有页面同步 - 适配优化:如果需要适配刘海屏底部安全区域,可在
Column外层包裹SafeArea(bottom: true),或在播放器的padding中加入MediaQuery.of(context).padding.bottom
内容的提问来源于stack exchange,提问作者Digamber negi
相关产品推荐
相关产品推荐

