在这里插入图片描述

播放器作为音乐App的核心交互载体,需兼顾视觉体验与功能完整性。本次实现的全屏播放器页面,围绕用户核心操作场景设计,融合旋转动画、多维度控制、弹窗交互等特性,打造沉浸式播放体验。

功能与技术核心解析

核心功能覆盖播放全流程:旋转唱片动画营造听觉-视觉联动感,播放控制满足基础操作,进度条实现精准调节,多类弹窗适配个性化需求。技术层面依托AnimationController实现动画驱动,RotationTransition封装旋转效果,Slider构建进度交互,Get.bottomSheet快速实现弹窗层,整体基于Flutter的Widget组合式开发,适配OpenHarmony生态的交互逻辑。

核心代码实现

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../lyrics/lyrics_page.dart';
import '../comment/comment_page.dart';

首先引入核心依赖包,material.dart提供Flutter基础UI组件(如ScaffoldIconButton等),get.dart是GetX框架核心包,用于路由跳转、弹窗提示等轻量化状态管理与交互;同时引入歌词页、评论页的路由文件,为后续页面跳转做准备,这是跨页面交互的基础配置,确保播放器能快速联动其他功能模块。

class PlayerPage extends StatefulWidget {
  const PlayerPage({super.key});

  
  State<PlayerPage> createState() => _PlayerPageState();
}

定义播放器页面为有状态组件(StatefulWidget),因为页面包含播放状态、动画状态、进度值等动态变化的数据。StatefulWidget的核心是分离组件结构与状态管理,createState方法返回对应的状态类_PlayerPageState,所有动态逻辑均在状态类中实现,这是Flutter处理可变UI的标准范式,确保页面状态变更时能精准重建对应Widget。

class _PlayerPageState extends State<PlayerPage>
    with SingleTickerProviderStateMixin {
  late AnimationController _rotationController;
  bool _isPlaying = false;
  double _progress = 0.3;
  bool _isFavorite = false;
  int _playMode = 0;
    }

状态类混入SingleTickerProviderStateMixin,为动画控制器提供帧回调(vsync),避免动画在页面不可见时浪费资源。声明核心状态变量:_rotationController控制唱片旋转动画,_isPlaying标记播放/暂停状态,_progress存储进度条当前值(初始0.3对应歌曲播放至30%),_isFavorite标记收藏状态,_playMode标识播放模式(0=顺序、1=随机、2=单曲循环),这些变量是驱动页面动态变化的核心数据。

  final List<IconData> _playModeIcons = [
    Icons.repeat,
    Icons.shuffle,
    Icons.repeat_one,
  ];
  final List<String> _playModeLabels = ['顺序播放', '随机播放', '单曲循环'];

定义播放模式对应的图标与文字标签列表,通过索引与_playMode绑定(0对应顺序播放的repeat图标和“顺序播放”文字)。这种数组映射方式简化了模式切换逻辑,无需大量if-else判断,只需通过索引取值,提升代码可读性与维护性,是Flutter中处理多状态UI的常用技巧。

  
  void initState() {
    super.initState();
    _rotationController = AnimationController(
      duration: const Duration(seconds: 20),
      vsync: this,
    );
  }

initState是State类的生命周期方法,仅在组件初始化时执行一次。此处初始化动画控制器_rotationController,设置动画时长为20秒(即唱片20秒旋转一圈,模拟真实黑胶唱片的旋转速度),vsync: this绑定混入的SingleTickerProviderStateMixin,确保动画帧率与屏幕刷新同步,避免卡顿。这是Flutter动画初始化的标准流程,保证动画资源仅初始化一次,优化性能。

  
  void dispose() {
    _rotationController.dispose();
    super.dispose();
  }

dispose是组件销毁时的生命周期方法,用于释放资源避免内存泄漏。此处销毁_rotationController,因为动画控制器持有系统资源,若不手动释放,会导致内存占用持续增加。这是Flutter开发中必须遵守的资源管理规范,尤其是动画、定时器等持有资源的对象,需在组件销毁时及时释放。

  void _togglePlay() {
    setState(() {
      _isPlaying = !_isPlaying;
      if (_isPlaying) {
        _rotationController.repeat();
      } else {
        _rotationController.stop();
      }
    });
  }

_togglePlay方法实现播放/暂停的核心逻辑,通过setState更新_isPlaying状态,触发页面重建。播放状态下调用_rotationController.repeat()让动画循环执行(唱片持续旋转),暂停时调用stop()停止动画。setState是Flutter更新状态的核心方式,仅会重建受状态影响的Widget,保证页面更新的性能效率,同时将动画控制与播放状态强绑定,确保视觉与功能同步。

  void _togglePlayMode() {
    setState(() {
      _playMode = (_playMode + 1) % 3;
    });
    Get.snackbar(
      '播放模式',
      _playModeLabels[_playMode],
      snackPosition: SnackPosition.TOP,
      duration: const Duration(seconds: 1),
    );
  }

_togglePlayMode实现播放模式循环切换,通过取余运算(_playMode + 1) % 3让模式在0-2之间循环(顺序→随机→单曲循环→顺序)。切换后调用Get.snackbar弹出提示框,告知用户当前播放模式,提示框位置设为顶部(SnackPosition.TOP),显示1秒后自动消失。GetX的snackbar是轻量化的提示组件,无需手动管理弹窗生命周期,相比原生showSnackBar更简洁,适配移动端用户的交互习惯。

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        decoration: const BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
            colors: [Color(0xFF2A2A2A), Color(0xFF121212)],
          ),
        ),

build方法构建页面核心布局,返回Scaffold作为页面骨架(Flutter页面的基础容器)。body嵌套Container并设置线性渐变背景,从#2A2A2A(深灰)到#121212(近黑)的垂直渐变,营造沉浸式暗黑风格,符合音乐播放器的视觉调性。渐变背景通过LinearGradient实现,beginend指定渐变方向,colors定义渐变色值,是Flutter美化页面背景的常用方式,相比纯色背景更具层次感。

        child: SafeArea(
          child: Column(
            children: [
              _buildAppBar(),
              const Spacer(flex: 1),
              _buildAlbumCover(),
              const Spacer(flex: 1),
              _buildSongInfo(),
              const SizedBox(height: 24),
              _buildProgressBar(),

SafeArea确保页面内容避开设备的状态栏、刘海屏等区域,提升兼容性。子组件为Column垂直布局,按“顶部栏→唱片封面→歌曲信息→进度条”的顺序排列,Spacer(flex: 1)通过弹性空间分配,让唱片封面在垂直方向居中显示,SizedBox固定间距,保证布局的规整性。这种拆分式布局(将不同模块封装为独立方法)是Flutter大型页面的最佳实践,提升代码可读性与复用性。

              const SizedBox(height: 24),
              _buildControls(),
              const SizedBox(height: 16),
              _buildBottomActions(),
              const SizedBox(height: 32),
            ],
          ),
        ),
      ),
    );
  }

延续Column布局,依次添加播放控制按钮区(_buildControls)、底部功能区(_buildBottomActions),并通过SizedBox设置间距。SpacerSizedBox的组合使用,既保证了核心视觉元素(唱片封面)的占比,又让各功能区间距均匀,符合移动端UI设计的“呼吸感”原则。将每个功能模块封装为独立的_buildXXX方法,使build方法结构清晰,便于后续单独修改某一模块而不影响整体布局。

  Widget _buildAppBar() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 8),
      child: Row(
        children: [
          IconButton(
            icon: const Icon(Icons.keyboard_arrow_down, color: Colors.white),
            onPressed: () => Get.back(),
          ),

_buildAppBar封装顶部栏布局,Padding设置水平内边距避免内容贴边,Row实现水平排列。左侧是返回按钮,使用IconButton包裹向下箭头图标,点击时通过Get.back()返回上一页(GetX的路由跳转方法,相比原生Navigator.pop更简洁)。图标颜色设为白色,适配暗黑背景,保证视觉对比度,符合移动端交互设计中“操作按钮清晰可见”的原则。

          Expanded(
            child: Column(
              children: [
                const Text(
                  '正在播放',
                  style: TextStyle(color: Colors.white70, fontSize: 12),
                ),
                const SizedBox(height: 2),
                GestureDetector(
                  onTap: () {},
                  child: const Row(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Text(
                        '来自: 每日推荐',
                        style: TextStyle(color: Colors.white54, fontSize: 11),
                      ),
                      Icon(
                        Icons.chevron_right,
                        color: Colors.white54,
                        size: 14,
                      ),
                    ],
                  ),
                ),
              ],
            ),
          ),

Expanded占据顶部栏剩余空间,内部Column垂直显示“正在播放”提示和歌曲来源。“正在播放”使用浅白色(white70)、小字号,作为辅助文字;歌曲来源“来自: 每日推荐”通过GestureDetector包裹,预留点击事件(可拓展为跳转到歌单页面),右侧箭头图标强化可交互性。文字颜色逐层降低透明度(white54),形成视觉层级,让用户快速聚焦核心信息,这是移动端文字排版的核心技巧。

          IconButton(
            icon: const Icon(Icons.share, color: Colors.white),
            onPressed: () {},
          ),
        ],
      ),
    );
  }

顶部栏右侧是分享按钮,IconButton包裹分享图标,点击事件暂留空(可拓展为调用系统分享接口)。图标颜色与返回按钮一致,保证视觉统一性。整个顶部栏采用“返回+中间信息+分享”的经典布局,符合用户的操作习惯,同时通过文字透明度、图标大小的细节把控,提升整体精致度。

  Widget _buildAlbumCover() {
    return RotationTransition(
      turns: _rotationController,
      child: Container(
        width: 280,
        height: 280,
        decoration: BoxDecoration(
          shape: BoxShape.circle,
          boxShadow: [
            BoxShadow(
              color: const Color(0xFFE91E63).withOpacity(0.3),
              blurRadius: 30,
              spreadRadius: 10,
            ),
          ],
        ),

_buildAlbumCover实现核心的旋转唱片封面,RotationTransition是Flutter的动画封装组件,turns绑定_rotationController,将动画控制器的数值转换为旋转角度。外层Container设置280x280的圆形尺寸,boxShadow添加粉色(#E91E63)半透明阴影,blurRadius(模糊半径)和spreadRadius(扩散半径)营造发光效果,模拟黑胶唱片的质感,提升视觉层次感。

        child: Stack(
          alignment: Alignment.center,
          children: [
            Container(
              width: 280,
              height: 280,
              decoration: const BoxDecoration(
                shape: BoxShape.circle,
                color: Color(0xFF2A2A2A),
              ),
            ),

Stack层叠布局实现唱片的多层视觉效果,alignment: Alignment.center让所有子组件居中对齐。最底层是280x280的深灰色圆形,模拟黑胶唱片的外圈,颜色与页面背景渐变的中间色一致,保证视觉融合度。BoxDecorationshape: BoxShape.circle将矩形容器转为圆形,是Flutter实现圆形UI的核心方式,无需额外绘制。

            Container(
              width: 200,
              height: 200,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                gradient: const LinearGradient(
                  begin: Alignment.topLeft,
                  end: Alignment.bottomRight,
                  colors: [Color(0xFFE91E63), Color(0xFF9C27B0)],
                ),
                boxShadow: [
                  BoxShadow(
                    color: Colors.black.withOpacity(0.3),
                    blurRadius: 10,
                  ),
                ],
              ),
              child: const Icon(
                Icons.music_note,
                size: 80,
                color: Colors.white70,
              ),
            ),

中间层是200x200的圆形封面,采用粉紫渐变(#E91E63#9C27B0)模拟唱片标签,boxShadow添加黑色半透明阴影增强立体感。内部嵌套音乐图标(Icons.music_note),作为封面占位符(实际项目中可替换为歌曲封面图片),图标尺寸80、浅白色,保证在渐变背景上清晰可见。渐变+阴影的组合让封面层次丰富,避免单调。

            Container(
              width: 40,
              height: 40,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                color: const Color(0xFF1E1E1E),
                border: Border.all(color: Colors.white24, width: 2),
              ),
            ),
          ],
        ),
      ),
    );
  }

最上层是40x40的圆形中心轴,深黑色(#1E1E1E)搭配白色细边框,模拟黑胶唱片的中心孔,让整个唱片视觉更完整。Border.all设置边框样式,宽度2px、透明度24%的白色,既突出中心轴,又不抢视觉焦点。整个Stack三层结构从外到内尺寸递减,符合真实唱片的视觉比例,旋转动画叠加后,模拟黑胶唱片播放的真实效果,提升用户沉浸感。

核心设计思路

1.将页面拆分为顶部栏、唱片封面、歌曲信息、进度条、控制区等独立模块,每个模块封装为_buildXXX方法,降低代码耦合度;
2.通过AnimationController将播放状态与唱片旋转动画强绑定,保证视觉与功能同步,符合用户直觉;
3.暗黑渐变背景+粉色高亮元素,搭配阴影、渐变、圆形等视觉元素,营造专业的音乐播放氛围;
4.基于GetX实现弹窗、路由跳转,替代原生复杂的状态管理,提升开发效率与运行性能。

小结

本篇我们实现了一个功能完整的播放器页面,包含旋转唱片封面、歌曲信息、进度条、播放控制、播放列表弹窗等功能。通过AnimationController和RotationTransition实现唱片旋转效果,通过Get.bottomSheet实现各种弹窗。整个页面视觉效果出色,交互流畅,是音乐App的核心体验。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐