Flutter for OpenHarmony音乐播放器App实战:主框架与底部导航实现
本文介绍了个人中心页面的实现方案,主要包含三个模块:用户信息区、VIP推广卡片和功能菜单列表。用户信息区显示头像和登录状态,点击跳转登录页;VIP卡片采用金色渐变设计吸引用户开通会员;功能菜单包含主题设置、定时关闭等选项,点击跳转对应页面。页面使用Flutter框架构建,采用圆角卡片和粉色主题色统一视觉风格,通过GetX库实现页面导航。整体布局清晰,功能模块划分明确,提供了完整的用户个人管理功能。

主框架是整个App的骨架,它决定了App的基本结构和导航方式。音乐播放器通常采用底部导航栏的设计,用户可以在首页、发现、音乐库、我的四个主要模块之间切换。同时,底部还需要一个迷你播放器,让用户在浏览其他页面时也能控制音乐播放。本篇我们来实现这个主框架页面。
功能分析
主框架页面需要实现以下功能:底部导航栏包含4个Tab、点击Tab切换对应页面、页面切换时保持状态不丢失、底部导航栏上方悬浮迷你播放器、迷你播放器显示当前播放歌曲信息、播放控制按钮。
核心技术点
本篇涉及的核心技术包括:IndexedStack实现Tab页面切换并保持状态、BottomNavigationBar底部导航栏、Stack和Positioned实现悬浮迷你播放器、GetX响应式状态管理。
对应代码文件
lib/pages/main_page.dart
完整代码实现
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'home/home_page.dart';
import 'discover/discover_page.dart';
import 'music/my_music_page.dart';
import 'profile/profile_page.dart';
import 'player/player_page.dart';
class MainPage extends StatefulWidget {
const MainPage({super.key});
State<MainPage> createState() => _MainPageState();
}
上面这段代码导入了必要的依赖包和子页面。MainPage使用StatefulWidget是因为需要管理Tab索引和播放状态。GetX用于响应式状态管理和路由导航。
class _MainPageState extends State<MainPage> {
int _currentIndex = 0;
final RxBool _isPlaying = false.obs;
final RxString _currentSong = '未播放'.obs;
final RxString _currentArtist = ''.obs;
final List<Widget> _pages = [
const HomePage(),
const DiscoverPage(),
const MyMusicPage(),
const ProfilePage(),
];
final List<BottomNavigationBarItem> _navItems = const [
BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
BottomNavigationBarItem(icon: Icon(Icons.explore), label: '发现'),
BottomNavigationBarItem(icon: Icon(Icons.library_music), label: '音乐库'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'),
];
_currentIndex记录当前选中的Tab索引。RxBool和RxString是GetX的响应式变量,.obs后缀将普通变量转换为可观察对象。_pages列表存放四个子页面实例,_navItems定义底部导航栏的图标和文字。
void initState() {
super.initState();
_currentSong.value = '当前播放歌曲';
_currentArtist.value = '歌手名称';
}
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
IndexedStack(index: _currentIndex, children: _pages),
Positioned(
left: 0,
right: 0,
bottom: 0,
child: _buildMiniPlayer(context),
),
],
),
bottomNavigationBar: _buildBottomNavBar(),
);
}
initState中初始化歌曲信息。build方法返回Scaffold脚手架,body使用Stack叠加IndexedStack和迷你播放器。IndexedStack会同时持有所有子页面但只显示当前索引对应的页面,这样切换Tab时页面状态不会丢失。
Widget _buildBottomNavBar() {
return BottomNavigationBar(
currentIndex: _currentIndex,
onTap: (index) => setState(() => _currentIndex = index),
type: BottomNavigationBarType.fixed,
items: _navItems,
);
}
Widget _buildMiniPlayer(BuildContext context) {
return GestureDetector(
onTap: () => Get.to(() => const PlayerPage()),
onVerticalDragEnd: (details) {
if (details.primaryVelocity! < 0) {
Get.to(() => const PlayerPage());
}
},
BottomNavigationBar的currentIndex指定当前选中项,onTap处理点击切换Tab。type设为fixed让所有Tab等宽显示。迷你播放器用GestureDetector包裹,支持点击和上滑手势打开全屏播放器,primaryVelocity为负表示向上滑动。
child: Container(
margin: const EdgeInsets.fromLTRB(8, 0, 8, 60),
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Theme.of(context).cardColor,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 10,
offset: const Offset(0, -2),
),
],
),
child: Row(
children: [
_buildAlbumCover(),
const SizedBox(width: 12),
_buildSongInfo(),
_buildControlButtons(),
],
),
),
);
}
Container的margin底部设为60为导航栏留空间。decoration添加卡片背景色、圆角和阴影让迷你播放器悬浮显示。Row水平排列专辑封面、歌曲信息和控制按钮三个区域。
Widget _buildAlbumCover() {
return Container(
width: 48,
height: 48,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
gradient: const LinearGradient(
colors: [Color(0xFFE91E63), Color(0xFF9C27B0)],
),
),
child: const Icon(Icons.music_note, color: Colors.white),
);
}
Widget _buildSongInfo() {
return Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Obx(() => Text(
_currentSong.value,
style: const TextStyle(fontWeight: FontWeight.w500),
maxLines: 1,
overflow: TextOverflow.ellipsis,
)),
专辑封面使用48x48尺寸,渐变背景与品牌色保持一致。歌曲信息区域使用Expanded占据剩余空间,Obx包裹Text组件监听响应式变量变化自动更新UI,maxLines和overflow确保文字过长时显示省略号。
const SizedBox(height: 2),
Obx(() => Text(
_currentArtist.value,
style: const TextStyle(color: Colors.grey, fontSize: 12),
maxLines: 1,
overflow: TextOverflow.ellipsis,
)),
],
),
);
}
Widget _buildControlButtons() {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.skip_previous),
onPressed: _playPrevious,
iconSize: 28,
),
歌手名使用灰色小字显示在歌曲名下方。控制按钮区域使用Row水平排列,mainAxisSize设为min让Row只占必要宽度。上一首按钮使用skip_previous图标,iconSize设为28。
Obx(() => IconButton(
icon: Icon(
_isPlaying.value ? Icons.pause_circle_filled : Icons.play_circle_filled,
size: 40,
color: const Color(0xFFE91E63),
),
onPressed: _togglePlay,
)),
IconButton(
icon: const Icon(Icons.skip_next),
onPressed: _playNext,
iconSize: 28,
),
],
);
}
播放按钮使用Obx监听_isPlaying状态,根据播放状态切换暂停或播放图标。播放按钮使用较大尺寸40和粉色突出显示,是整个控制区的视觉焦点。下一首按钮与上一首按钮样式一致。
void _togglePlay() {
_isPlaying.toggle();
}
void _playPrevious() {
Get.snackbar(
'提示',
'播放上一首',
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 1),
);
}
void _playNext() {
Get.snackbar(
'提示',
'播放下一首',
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 1),
);
}
}
_togglePlay使用GetX的toggle方法切换播放状态。_playPrevious和_playNext目前只显示提示,实际项目中需要调用播放器服务切换歌曲。Get.snackbar是GetX提供的便捷提示方法。
IndexedStack与PageView对比
IndexedStack会同时构建所有子页面,但只显示当前选中的那个。优点是切换Tab时页面状态不丢失,缺点是首次加载时会构建所有页面。PageView支持滑动切换,但默认只保留当前页面和相邻页面的状态。如果需要保持所有页面状态,需要配合AutomaticKeepAliveClientMixin使用。对于底部导航这种场景,IndexedStack是更简单直接的选择。
GetX响应式变量
GetX的响应式变量通过.obs后缀创建,使用Obx组件包裹需要响应变化的Widget。当响应式变量的值发生变化时,Obx会自动重建其子Widget。这种方式比setState更加精细,只会重建真正需要更新的部分,性能更好。RxBool、RxString、RxInt等是常用的响应式类型。
迷你播放器设计要点
迷你播放器是音乐App的标配组件。设计时需要注意以下几点:位置要固定在底部导航栏上方,不能遮挡导航栏;要支持点击和上滑手势打开全屏播放器;要显示当前播放的歌曲信息;要提供基本的播放控制按钮;样式要与整体App风格协调。
进阶优化建议
在实际项目中,你可能还需要考虑以下几点:
播放状态全局管理:将播放状态提取到GetX Controller中,这样在任何页面都可以方便地访问和修改播放状态,实现真正的全局状态管理。
迷你播放器动画:可以添加进度条动画显示当前播放进度,也可以添加专辑封面旋转动画增加视觉效果,让用户直观感受到音乐正在播放。
手势优化:除了上滑打开全屏播放器,还可以支持左右滑动切换歌曲,提供更便捷的操作方式。
播放队列:点击迷你播放器右侧可以弹出播放队列,方便用户管理播放列表,查看即将播放的歌曲。
小结
本篇实现了音乐播放器的主框架页面,通过IndexedStack实现Tab页面切换并保持状态,通过BottomNavigationBar实现底部导航。迷你播放器悬浮在底部导航栏上方,显示当前播放的歌曲信息和控制按钮,支持点击和上滑手势打开全屏播放器。这个主框架为后续的功能开发奠定了基础,用户可以在四个主要模块之间自由切换,同时随时控制音乐播放。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)