首页是用户进入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