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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 12:39:52