news 2026/4/24 12:55:28

Flutter for OpenHarmony音乐播放器App实战03:首页实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Flutter for OpenHarmony音乐播放器App实战03:首页实现

首页是用户进入App后看到的第一个内容页面,它需要展示最重要的信息和功能入口。音乐播放器的首页通常包含Banner轮播、快捷入口、推荐歌单、热门歌手、新碟上架等模块。本篇我们来实现一个功能丰富的首页。

功能分析

首页需要实现以下功能:顶部搜索入口、Banner区域展示每日推荐、快捷入口、推荐歌单网格展示、热门歌手横向滚动列表、新碟上架横向滚动列表。

核心技术点

本篇涉及的核心技术包括:SingleChildScrollView实现页面滚动、GridView.builder实现网格布局、ListView.builder实现横向滚动列表、数据驱动的UI构建方式。

对应代码文件

lib/pages/home/home_page.dart

完整代码实现

import'package:flutter/material.dart';import'package:get/get.dart';import'../search/search_page.dart';import'../playlist/playlist_detail_page.dart';import'../artist/artist_detail_page.dart';import'../album/album_detail_page.dart';import'../ranking/ranking_page.dart';import'../daily/daily_recommend_page.dart';classHomePageextendsStatelessWidget{constHomePage({super.key});@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('音乐播放器'),actions:[IconButton(icon:constIcon(Icons.search),onPressed:()=>Get.to(()=>constSearchPage()),),],),

上面这段代码导入了必要的依赖和子页面。HomePage使用StatelessWidget因为不需要管理内部状态。AppBar右侧放置搜索按钮,点击后使用GetX导航到搜索页面。

body:SingleChildScrollView(padding:constEdgeInsets.all(16),child:Column(crossAxisAlignment:CrossAxisAlignment.start,children:[_buildBanner(),constSizedBox(height:24),_buildQuickActions(),constSizedBox(height:24),_buildSection('推荐歌单',_buildPlaylistGrid()),constSizedBox(height:24),_buildSection('热门歌手',_buildArtistList()),constSizedBox(height:24),_buildSection('新碟上架',_buildAlbumList()),constSizedBox(height:100),],),),);}

SingleChildScrollView包裹Column实现整体滚动。padding设置16像素内边距,crossAxisAlignment设为start让内容左对齐。底部留100像素空间避免被迷你播放器遮挡。

Widget_buildBanner(){returnGestureDetector(onTap:()=>Get.to(()=>constDailyRecommendPage()),child:Container(height:160,decoration:BoxDecoration(borderRadius:BorderRadius.circular(16),gradient:constLinearGradient(begin:Alignment.topLeft,end:Alignment.bottomRight,colors:[Color(0xFFE91E63),Color(0xFF9C27B0)],),boxShadow:[BoxShadow(color:constColor(0xFFE91E63).withOpacity(0.3),blurRadius:15,offset:constOffset(0,8),),],),

Banner是首页的视觉焦点,高度160像素,使用16像素圆角。渐变从粉色到紫色与App主题一致,boxShadow添加粉色阴影让Banner有悬浮效果。点击跳转到每日推荐页面。

child:Stack(children:[Positioned(right:20,bottom:20,child:Icon(Icons.headphones,size:100,color:Colors.white.withOpacity(0.2),),),Padding(padding:constEdgeInsets.all(20),child:Column(crossAxisAlignment:CrossAxisAlignment.start,children:[Container(padding:constEdgeInsets.symmetric(horizontal:8,vertical:4),decoration:BoxDecoration(color:Colors.white.withOpacity(0.2),borderRadius:BorderRadius.circular(12),),child:constText('每日推荐',style:TextStyle(color:Colors.white,fontSize:12),),),

Stack叠加背景装饰图标和文字内容。Positioned将耳机图标定位在右下角作为装饰,使用20%透明度不抢夺视觉焦点。标签使用半透明白色背景的胶囊形状。

constSizedBox(height:12),constText('发现你的专属音乐',style:TextStyle(color:Colors.white,fontSize:24,fontWeight:FontWeight.bold,),),constSizedBox(height:8),constText('根据你的口味,每天为你推荐30首歌曲',style:TextStyle(color:Colors.white70,fontSize:14),),],),),],),),);}

主标题使用24像素粗体白色大字,副标题使用白色70%透明度形成主次层次。SizedBox添加固定间距让布局整齐。

Widget_buildQuickActions(){finalactions=[{'icon':Icons.today,'label':'每日推荐','color':constColor(0xFFE91E63),'onTap':()=>Get.to(()=>constDailyRecommendPage()),},{'icon':Icons.leaderboard,'label':'排行榜','color':constColor(0xFF9C27B0),'onTap':()=>Get.to(()=>constRankingPage()),},{'icon':Icons.radio,'label':'私人FM','color':constColor(0xFF2196F3),'onTap':(){},},{'icon':Icons.album,'label':'新碟','color':constColor(0xFF4CAF50),'onTap':(){},},];

快捷入口使用数据驱动方式构建,每个入口包含图标、文字、颜色和点击回调。这种方式让代码更易维护,添加或修改入口只需修改数据即可。

returnRow(mainAxisAlignment:MainAxisAlignment.spaceAround,children:actions.map((action){returnGestureDetector(onTap:action['onTap']asVoidCallback,child:Column(children:[Container(width:56,height:56,decoration:BoxDecoration(color:(action['color']asColor).withOpacity(0.1),borderRadius:BorderRadius.circular(16),),child:Icon(action['icon']asIconData,color:action['color']asColor,size:28,),),constSizedBox(height:8),Text(action['label']asString,style:constTextStyle(fontSize:12),),],),);}).toList(),);}

Row的spaceAround让入口均匀分布。每个入口使用不同颜色增加视觉区分度,图标背景使用10%透明度的对应颜色。

Widget_buildSection(Stringtitle,Widgetchild){returnColumn(crossAxisAlignment:CrossAxisAlignment.start,children:[Row(mainAxisAlignment:MainAxisAlignment.spaceBetween,children:[Text(title,style:constTextStyle(fontSize:18,fontWeight:FontWeight.bold,),),GestureDetector(onTap:(){},child:constRow(children:[Text('更多',style:TextStyle(color:Colors.grey,fontSize:14),),Icon(Icons.chevron_right,color:Colors.grey,size:20),],),),],),constSizedBox(height:12),child,],);}

通用Section组件封装标题行和内容区域,避免重复代码。标题行左侧显示标题,右侧显示"更多"按钮,让页面结构更清晰。

Widget_buildPlaylistGrid(){returnGridView.builder(shrinkWrap:true,physics:constNeverScrollableScrollPhysics(),gridDelegate:constSliverGridDelegateWithFixedCrossAxisCount(crossAxisCount:3,childAspectRatio:0.75,crossAxisSpacing:12,mainAxisSpacing:12,),itemCount:6,itemBuilder:(context,index){returnGestureDetector(onTap:()=>Get.to(()=>PlaylistDetailPage(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),),

GridView.builder的shrinkWrap让高度自适应内容,NeverScrollableScrollPhysics禁用内部滚动。gridDelegate配置3列、0.75宽高比、12像素间距。

child:Stack(children:[constCenter(child:Icon(Icons.queue_music,size:40,color:Colors.white70,),),Positioned(top:4,right:4,child:Container(padding:constEdgeInsets.symmetric(horizontal:6,vertical:2,),decoration:BoxDecoration(color:Colors.black45,borderRadius:BorderRadius.circular(8),),child:Row(mainAxisSize:MainAxisSize.min,children:[constIcon(Icons.play_arrow,size:12,color:Colors.white,),Text('${(index+1)*10}万',style:constTextStyle(color:Colors.white,fontSize:10,),),],),),),],),),),constSizedBox(height:8),Text('推荐歌单${index+1}',style:constTextStyle(fontSize:12),maxLines:2,overflow:TextOverflow.ellipsis,),],),);},);}

每个歌单项包含封面和标题,封面右上角显示播放量。Stack叠加音乐图标和播放量角标,Positioned精确定位角标位置。

Widget_buildArtistList(){returnSizedBox(height:110,child:ListView.builder(scrollDirection:Axis.horizontal,itemCount:10,itemBuilder:(context,index){returnGestureDetector(onTap:()=>Get.to(()=>ArtistDetailPage(id:index)),child:Container(width:80,margin:constEdgeInsets.only(right:16),child:Column(children:[Container(width:64,height:64,decoration:BoxDecoration(shape:BoxShape.circle,color:Colors.primaries[index%Colors.primaries.length].withOpacity(0.3),border:Border.all(color:constColor(0xFFE91E63).withOpacity(0.3),width:2,),),child:constIcon(Icons.person,color:Colors.white70,size:32,),),

SizedBox固定高度110像素,scrollDirection设为Axis.horizontal实现横向滚动。每个歌手项宽80像素,圆形头像使用粉色边框与主题色呼应。

constSizedBox(height:8),Text('歌手${index+1}',style:constTextStyle(fontSize:12),maxLines:1,overflow:TextOverflow.ellipsis,textAlign:TextAlign.center,),Text('${(index+1)*50}首',style:constTextStyle(fontSize:10,color:Colors.grey,),),],),),);},),);}

歌手名和歌曲数量显示在头像下方,maxLines和overflow确保文字过长时显示省略号。歌曲数量使用灰色小字作为辅助信息。

Widget_buildAlbumList(){returnSizedBox(height:180,child:ListView.builder(scrollDirection:Axis.horizontal,itemCount:10,itemBuilder:(context,index){returnGestureDetector(onTap:()=>Get.to(()=>AlbumDetailPage(id:index)),child:Container(width:130,margin:constEdgeInsets.only(right:12),child:Column(crossAxisAlignment:CrossAxisAlignment.start,children:[Container(height:130,decoration:BoxDecoration(borderRadius:BorderRadius.circular(12),color:Colors.primaries[index%Colors.primaries.length].withOpacity(0.3),boxShadow:[BoxShadow(color:Colors.black.withOpacity(0.1),blurRadius:8,offset:constOffset(0,4),),],),

新碟列表高度180像素,每个专辑项宽130像素。封面区域高130像素,添加阴影增加立体感。

child:Stack(children:[constCenter(child:Icon(Icons.album,size:50,color:Colors.white70,),),Positioned(bottom:8,right:8,child:Container(width:32,height:32,decoration:constBoxDecoration(shape:BoxShape.circle,color:Color(0xFFE91E63),),child:constIcon(Icons.play_arrow,color:Colors.white,size:20,),),),],),),constSizedBox(height:8),Text('专辑${index+1}',style:constTextStyle(fontSize:13),maxLines:1,overflow:TextOverflow.ellipsis,),Text('歌手${index+1}',style:constTextStyle(fontSize:11,color:Colors.grey,),maxLines:1,overflow:TextOverflow.ellipsis,),],),),);},),);}}

封面右下角有粉色圆形播放按钮,是整个专辑项的视觉焦点。下方显示专辑名和歌手名,歌手名使用灰色小字作为辅助信息。

GridView与ListView对比

GridView适合展示网格布局的内容,如歌单封面、图片墙等。ListView适合展示列表形式的内容,设置scrollDirection为Axis.horizontal可实现横向滚动。两者都支持builder模式实现懒加载,只构建可见区域的项目,提升性能。

数据驱动UI的优势

使用List或Map存储数据,通过map方法遍历生成Widget,这种方式让代码更易维护。添加、删除或修改内容只需修改数据源,不需要改动UI构建逻辑。这也是Flutter推荐的声明式UI编程方式。

小结

本篇实现了音乐播放器的首页,通过SingleChildScrollView实现页面滚动,使用GridView和ListView分别展示网格和列表内容。抽取通用的Section组件减少重复代码,使用数据驱动的方式构建快捷入口。首页包含Banner、快捷入口、推荐歌单、热门歌手、新碟上架等模块,为用户提供丰富的内容入口。

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

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

计算机Net毕设实战-基于net的线上阳光好书系统Asp.net电子书城系统设计与实现【完整源码+LW+部署说明+演示视频,全bao一条龙等】

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

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

stm32毕业论文(毕设)本科生任务书大全

文章目录 1前言2 STM32 毕设课题3 如何选题3.1 不要给自己挖坑3.2 难度把控3.3 如何命名题目 4 最后 1前言 🥇 更新单片机嵌入式选题后,不少学弟学妹催学长更新STM32和C51选题系列,感谢大家的认可,来啦! 以下是学长亲…

作者头像 李华
网站建设 2026/4/22 17:48:43

数字图像处理篇---Canny算子

我用最通俗易懂的方式解释Canny算子——它是边缘检测领域的“瑞士军刀”,智能且精准。一句话核心思想Canny算子 “聪明的侦探破案”它不是简单地找边缘,而是像侦探一样:收集证据、排除干扰、去伪存真、合理推理,最终找出“真正的…

作者头像 李华
网站建设 2026/4/19 11:48:23

DevOps体系详解01-核心概念与价值

一、DevOps是什么 1.1 定义 DevOps Development(开发) Operations(运维) DevOps是一套方法论、文化理念和工具实践的集合,旨在打破开发团队和运维团队之间的壁垒,通过自动化、协作和持续改进&#xff…

作者头像 李华
网站建设 2026/4/19 12:44:48

mycat报错:63529

今天玩mycat 1.6.x 版本的时候在navicat执行建表语句报错 63529 - line 1, column 875, nearby [ON] has error: Syntax error 63529 - line 1, column 957, nearby [ENGINE] has error: Syntax error 该说不说,mycat 风评确实挺差的,能不用还是别用&…

作者头像 李华