news 2026/4/16 8:45:14

Flutter for OpenHarmony音乐播放器App实战18:MV列表实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter for OpenHarmony音乐播放器App实战18:MV列表实现

MV是音乐App中非常受欢迎的内容形式,用户可以通过MV更直观地感受音乐的魅力。今天我们来实现MV列表页面,展示各种MV供用户浏览和选择播放。

功能分析

MV列表页面需要实现以下功能:

  • 网格布局展示MV缩略图
  • 显示MV时长标签
  • 显示MV标题和歌手名称
  • 点击MV进入播放页面

MV列表和歌单列表类似,但MV的缩略图通常是横向的比例,需要调整网格的宽高比来适应视频内容的展示。

对应代码文件

lib/pages/mv/mv_list_page.dart

页面基础结构

首先看页面的基础结构和导入部分。

import'package:flutter/material.dart';import'package:get/get.dart';import'mv_player_page.dart';classMVListPageextendsStatelessWidget{constMVListPage({super.key});

页面导入了Flutter的Material组件库和GetX路由管理库。同时导入了MV播放页面,用于点击MV时进行页面跳转。

MV列表页面使用StatelessWidget,因为列表数据通常是从服务器获取的固定数据,不需要在页面内部管理复杂的状态变化。

Scaffold脚手架

页面使用Scaffold作为基础框架。

@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('MV')),body:GridView.builder(

AppBar设置了简洁的标题"MV",body部分使用GridView.builder来构建网格列表。GridView.builder是一个懒加载的网格组件,只会构建当前可见的项目,对于长列表来说性能更好。

GridView网格配置

GridView的核心配置在gridDelegate参数中。

padding:constEdgeInsets.all(16),gridDelegate:constSliverGridDelegateWithFixedCrossAxisCount(crossAxisCount:2,childAspectRatio:1.2,crossAxisSpacing:12,mainAxisSpacing:12),itemCount:20,

padding设置网格四周的内边距为16像素,让内容不会紧贴屏幕边缘。

SliverGridDelegateWithFixedCrossAxisCount是一个固定列数的网格代理,参数含义如下:

  • crossAxisCount: 2表示每行显示2个MV项目
  • childAspectRatio: 1.2表示子项的宽高比,1.2意味着宽度比高度大,适合展示横向的视频缩略图
  • crossAxisSpacing: 12表示列之间的水平间距
  • mainAxisSpacing: 12表示行之间的垂直间距

itemCount: 20设置列表项的总数,实际项目中这个值应该来自服务器返回的数据长度。

MV项目构建

每个MV项目使用itemBuilder回调来构建。

itemBuilder:(context,index)=>GestureDetector(onTap:()=>Get.to(()=>MVPlayerPage(id:index)),child:Column(crossAxisAlignment:CrossAxisAlignment.start,children:[

GestureDetector包裹整个MV项目,用于处理点击事件。点击时使用GetX的Get.to方法导航到MV播放页面,并传递当前MV的索引作为ID参数。

Column组件垂直排列缩略图和文字信息,crossAxisAlignment: CrossAxisAlignment.start让子组件左对齐。

缩略图区域

缩略图区域使用Expanded占据剩余空间。

Expanded(child:Container(decoration:BoxDecoration(borderRadius:BorderRadius.circular(12),color:Colors.primaries[index%Colors.primaries.length].withOpacity(0.3)),

Expanded让缩略图区域自动填充Column中除了文字之外的所有空间。

Container的decoration设置了圆角和背景色。borderRadius: BorderRadius.circular(12)创建12像素的圆角,让缩略图看起来更柔和。

背景色使用Colors.primaries[index % Colors.primaries.length]从Material的主色列表中循环取色,withOpacity(0.3)设置30%的透明度,让颜色不会太刺眼。这种方式在没有真实图片时可以产生丰富的视觉效果。

Stack叠加布局

缩略图内部使用Stack实现叠加效果。

child:Stack(children:[constCenter(child:Icon(Icons.play_circle_filled,size:50,color:Colors.white70)),

Stack允许子组件叠加在一起,后面的子组件会覆盖前面的。

第一个子组件是居中的播放图标,使用Center组件让图标在缩略图中央显示。Icons.play_circle_filled是一个实心的播放按钮图标,大小设置为50像素。Colors.white70是70%透明度的白色,不会太抢眼但又能清晰可见。

时长标签定位

时长标签使用Positioned精确定位在右下角。

Positioned(right:8,bottom:8,child:Container(padding:constEdgeInsets.symmetric(horizontal:6,vertical:2),decoration:BoxDecoration(color:Colors.black54,borderRadius:BorderRadius.circular(4)),

Positioned组件只能在Stack内部使用,用于精确控制子组件的位置。right: 8表示距离右边8像素,bottom: 8表示距离底部8像素,这样时长标签就会显示在缩略图的右下角。

时长标签的Container设置了水平6像素、垂直2像素的内边距,让文字不会紧贴边框。背景色使用Colors.black54,即54%透明度的黑色,既能遮挡背景又不会太突兀。圆角设置为4像素,让标签看起来更精致。

时长文字

时长标签内显示视频时长。

child:constText('04:32',style:TextStyle(color:Colors.white,fontSize:10)))),],),),),

时长文字使用白色,字号设置为10像素,在小标签中显示清晰又不会占用太多空间。实际项目中,这个时长值应该来自MV的真实数据。

标题和歌手信息

缩略图下方显示MV标题和歌手名称。

constSizedBox(height:8),Text('MV${index+1}',style:constTextStyle(fontSize:14),maxLines:1,overflow:TextOverflow.ellipsis),constText('歌手名称',style:TextStyle(color:Colors.grey,fontSize:12)),],),),),);}}

SizedBox(height: 8)在缩略图和标题之间添加8像素的间距。

MV标题使用14像素的字号,maxLines: 1限制只显示一行,overflow: TextOverflow.ellipsis在文本溢出时显示省略号,避免标题过长破坏布局。

歌手名称使用灰色和12像素的较小字号,作为次要信息展示,与标题形成视觉层次。

完整代码

下面是MV列表页面的完整代码。

import'package:flutter/material.dart';import'package:get/get.dart';import'mv_player_page.dart';classMVListPageextendsStatelessWidget{constMVListPage({super.key});@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('MV')),body:GridView.builder(padding:constEdgeInsets.all(16),gridDelegate:constSliverGridDelegateWithFixedCrossAxisCount(crossAxisCount:2,childAspectRatio:1.2,crossAxisSpacing:12,mainAxisSpacing:12),itemCount:20,itemBuilder:(context,index)=>GestureDetector(onTap:()=>Get.to(()=>MVPlayerPage(id:index)),child:Column(crossAxisAlignment:CrossAxisAlignment.start,children:[Expanded(child:Container(decoration:BoxDecoration(borderRadius:BorderRadius.circular(12),color:Colors.primaries[index%Colors.primaries.length].withOpacity(0.3)),child:Stack(children:[constCenter(child:Icon(Icons.play_circle_filled,size:50,color:Colors.white70)),Positioned(right:8,bottom:8,child:Container(padding:constEdgeInsets.symmetric(horizontal:6,vertical:2),decoration:BoxDecoration(color:Colors.black54,borderRadius:BorderRadius.circular(4)),child:constText('04:32',style:TextStyle(color:Colors.white,fontSize:10)))),],),),),constSizedBox(height:8),Text('MV${index+1}',style:constTextStyle(fontSize:14),maxLines:1,overflow:TextOverflow.ellipsis),constText('歌手名称',style:TextStyle(color:Colors.grey,fontSize:12)),],),),),);}}

功能扩展:分类筛选

实际的MV列表通常需要分类筛选功能,下面是扩展实现。

classMVListPageextendsStatefulWidget{constMVListPage({super.key});@overrideState<MVListPage>createState()=>_MVListPageState();}class_MVListPageStateextendsState<MVListPage>{String_selectedCategory='全部';finalList<String>_categories=['全部','官方版','现场版','舞蹈版','剧情版'];

要实现分类筛选,需要将页面改为StatefulWidget,添加选中分类的状态变量和分类列表。

分类标签栏

分类标签栏使用水平滚动的ListView。

Widget_buildCategoryBar(){returnSizedBox(height:50,child:ListView.builder(scrollDirection:Axis.horizontal,padding:constEdgeInsets.symmetric(horizontal:16,vertical:8),itemCount:_categories.length,itemBuilder:(context,index){finalcategory=_categories[index];finalisSelected=category==_selectedCategory;returnGestureDetector(onTap:()=>setState(()=>_selectedCategory=category),child:Container(margin:constEdgeInsets.only(right:12),padding:constEdgeInsets.symmetric(horizontal:16),alignment:Alignment.center,decoration:BoxDecoration(color:isSelected?constColor(0xFFE91E63):constColor(0xFF2A2A2A),borderRadius:BorderRadius.circular(20),),child:Text(category,style:TextStyle(color:isSelected?Colors.white:Colors.grey,fontSize:14,),),),);},),);}

scrollDirection: Axis.horizontal让ListView水平滚动。每个分类标签使用圆角容器,选中状态使用主题色背景,未选中使用深灰色背景。点击标签时调用setState更新选中状态。

下拉刷新

可以添加下拉刷新功能来更新MV列表。

Future<void>_refreshMVList()async{awaitFuture.delayed(constDuration(seconds:1));setState((){// 刷新数据});}Widget_buildMVGrid(){returnRefreshIndicator(onRefresh:_refreshMVList,color:constColor(0xFFE91E63),child:GridView.builder(padding:constEdgeInsets.all(16),gridDelegate:constSliverGridDelegateWithFixedCrossAxisCount(crossAxisCount:2,childAspectRatio:1.2,crossAxisSpacing:12,mainAxisSpacing:12,),itemCount:20,itemBuilder:(context,index)=>_buildMVItem(index),),);}

RefreshIndicator包裹GridView实现下拉刷新,onRefresh回调返回一个Future,刷新指示器会在Future完成后自动隐藏。color参数设置刷新指示器的颜色。

加载更多

滚动到底部时加载更多数据。

finalScrollController_scrollController=ScrollController();bool _isLoadingMore=false;@overridevoidinitState(){super.initState();_scrollController.addListener(_onScroll);}void_onScroll(){if(_scrollController.position.pixels>=_scrollController.position.maxScrollExtent-200){_loadMore();}}Future<void>_loadMore()async{if(_isLoadingMore)return;setState(()=>_isLoadingMore=true);awaitFuture.delayed(constDuration(seconds:1));setState(()=>_isLoadingMore=false);}

通过监听ScrollController的滚动位置,当距离底部不足200像素时触发加载更多。使用_isLoadingMore标志防止重复加载。

空状态处理

当没有MV数据时显示空状态。

Widget_buildEmptyState(){returnCenter(child:Column(mainAxisAlignment:MainAxisAlignment.center,children:[Icon(Icons.videocam_off,size:64,color:Colors.grey[600]),constSizedBox(height:16),Text('暂无MV',style:TextStyle(color:Colors.grey[600],fontSize:16),),constSizedBox(height:8),Text('换个分类试试吧',style:TextStyle(color:Colors.grey[700],fontSize:14),),],),);}

空状态页面显示一个图标和提示文字,引导用户进行下一步操作。

技术要点总结

MV列表页面虽然代码量不大,但涉及到几个重要的Flutter布局技术:

GridView.builder适合展示大量同类型数据,通过gridDelegate可以灵活配置网格的列数、间距和子项比例。

Stack和Positioned组合可以实现复杂的叠加布局,常用于在图片上添加标签、按钮等元素。

文本溢出处理是列表页面的常见需求,maxLines和overflow属性可以优雅地处理长文本。

GetX的路由管理让页面跳转变得简单,通过构造函数传参可以方便地在页面间传递数据。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/15 6:28:13

全面评测2026年免费降低AI率工具,那款工具降AI率最有效?

写论文的时候&#xff0c;不少人会用AI工具辅助。效率是高了&#xff0c;但新问题也来了&#xff1a;AI率过高。很多学校、期刊现在都用检测系统&#xff0c;一旦标记出“AI痕迹”&#xff0c;论文就可能被退回。 所以&#xff0c;怎么降低AI率&#xff0c;成了不少同学的必修…

作者头像 李华
网站建设 2026/4/8 11:17:39

openclaw(大龙虾)+飞书保姆级windows安装教程

短短的几天时间内&#xff0c;一直改名&#xff0c;可见对程序员来说&#xff0c;代码不难写&#xff0c;最难的是不知道如何称呼心爱的他Clawdbot -> Moltbot -> OpenClaw 你觉得应该起个什么名字&#x1f449;1.安装openclaw&#x1f463;1.1安装node和gitnode安装http…

作者头像 李华
网站建设 2026/4/14 8:38:51

加入大厂,却落入了边缘业务:这是职业选择的必然代价吗?

来源&#xff1a;豹变 作者&#xff0f;陈晓妍 过去十多年里&#xff0c;互联网大厂一直是这个时代里最醒目的快车。 人口红利下的互联网公司飞速发展&#xff0c;日活月活攀升、GMV不断增大、营收翻番。到2021年3月1日&#xff0c;腾讯、阿里巴巴、美团、拼多多、京东、快手、…

作者头像 李华
网站建设 2026/4/8 4:37:11

【CTF必备】TOP10常用神器盘点,让你“白嫖”所有下载渠道

CTF比赛必备常用工具 一、什么是CTF二、比赛中工具的重要性三、常用MISC&#xff08;杂项&#xff09;工具 1. Audacity &#xff08;提取莫斯密码辅助工具&#xff09;2. stegsolve &#xff08;图片隐写分析工具&#xff09;3. QR_Research &#xff08;二维码工具&#xff0…

作者头像 李华
网站建设 2026/4/15 20:45:00

一篇讲透计算机类18个本科专业:从代码到硬件,保姆级全景解析

一次搞懂计算机类18个本科专业方向的区别&#xff0c;谁是“宝藏”谁是“天坑”&#xff1f; 随着科技进步的日新月异&#xff0c;互联网在日常生活中所扮演的角色越来越重要。数字化、信息化的时代里&#xff0c;计算机类专业无疑是当今高考最热门且最具发展前景的志愿填报方…

作者头像 李华