歌单列表页面展示各种分类的歌单,用户可以通过分类筛选找到感兴趣的歌单。我们使用分类标签栏配合网格布局实现这个页面。本篇将详细介绍如何实现一个功能完善的歌单列表页面。
功能分析
歌单列表页面需要实现以下功能:分类标签栏(全部、流行、摇滚、民谣等)、歌单网格展示、点击歌单进入详情页、分类切换时更新歌单列表。这个页面是用户发现新歌单的重要入口,设计上需要让用户能快速找到感兴趣的分类。
核心技术点
本篇涉及的核心技术包括:横向滚动的分类标签栏、GridView网格布局、状态管理(选中的分类)、页面导航传参、数据驱动的UI构建方式。
对应代码文件
lib/pages/playlist/playlist_list_page.dart
完整代码实现
import'package:flutter/material.dart';import'package:get/get.dart';import'playlist_detail_page.dart';这段代码导入了Flutter核心库、GetX状态管理库以及歌单详情页面。GetX提供了简洁的路由导航API,方便我们进行页面跳转和参数传递。
classPlaylistListPageextendsStatefulWidget{constPlaylistListPage({super.key});@overrideState<PlaylistListPage>createState()=>_PlaylistListPageState();}PlaylistListPage继承StatefulWidget,因为需要管理选中的分类状态。当用户点击不同分类时,页面需要更新显示对应分类的歌单列表。
class_PlaylistListPageStateextendsState<PlaylistListPage>{// 当前选中的分类String_selectedCategory='全部';// 分类列表finalList<String>_categories=['全部','流行','摇滚','民谣','电子','古典','爵士',];_selectedCategory存储当前选中的分类,默认是"全部"。_categories是分类列表,包含了常见的音乐风格分类。实际项目中这个列表可能从服务器获取。
@overrideWidgetbuild(BuildContextcontext){returnScaffold(appBar:AppBar(title:constText('歌单广场'),elevation:0,),body:Column(children:[// 分类标签栏_buildCategoryBar(),// 歌单网格Expanded(child:_buildPlaylistGrid(),),],),);}build方法构建页面UI。Scaffold提供基础页面结构,AppBar显示"歌单广场"标题。页面使用Column垂直排列分类标签栏和歌单网格,Expanded让网格占据剩余空间。
/// 构建分类标签栏Widget_buildCategoryBar(){returnSizedBox(height:50,child:ListView.builder(scrollDirection:Axis.horizontal,padding:constEdgeInsets.symmetric(horizontal:16),itemCount:_categories.length,itemBuilder:(context,index){finalcategory=_categories[index];finalisSelected=category==_selectedCategory;return_buildCategoryItem(category,isSelected);},),);}分类标签栏固定高度50像素,使用横向滚动的ListView.builder实现。scrollDirection设为Axis.horizontal让列表横向滚动,这样可以支持更多分类而不会占用太多垂直空间。
/// 构建单个分类标签Widget_buildCategoryItem(Stringcategory,bool isSelected){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(0xFF1E1E1E),borderRadius:BorderRadius.circular(20),),child:Text(category,style:TextStyle(color:isSelected?Colors.white:Colors.grey,fontSize:14,fontWeight:isSelected?FontWeight.w500:FontWeight.normal,),),),);}GestureDetector处理点击事件,点击时调用setState更新选中的分类。Container使用条件表达式设置背景色,选中状态为粉色主题色,未选中为深灰色。BorderRadius.circular(20)让标签呈胶囊形状。
/// 构建歌单网格Widget_buildPlaylistGrid(){returnGridView.builder(padding:constEdgeInsets.all(16),gridDelegate:constSliverGridDelegateWithFixedCrossAxisCount(crossAxisCount:2,childAspectRatio:0.85,crossAxisSpacing:12,mainAxisSpacing:12,),itemCount:20,itemBuilder:(context,index){return_buildPlaylistItem(index);},);}GridView.builder用于构建网格布局,采用懒加载方式只构建可见区域的子项。gridDelegate配置网格为2列,宽高比0.85,间距12像素。itemCount设为20表示显示20个歌单。
/// 构建单个歌单项Widget_buildPlaylistItem(int index){returnGestureDetector(onTap:(){Get.to(()=>PlaylistDetailPage(id:index));},child:Column(crossAxisAlignment:CrossAxisAlignment.start,children:[// 歌单封面Expanded(child:_buildPlaylistCover(index),),constSizedBox(height:8),// 歌单标题_buildPlaylistTitle(index),],),);}GestureDetector处理点击事件,通过Get.to导航到歌单详情页并传递歌单ID。Column垂直排列封面和标题,crossAxisAlignment设为start让内容左对齐。
/// 构建歌单封面Widget_buildPlaylistCover(int index){returnContainer(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),),],),child:Stack(children:[// 封面图标constCenter(child:Icon(Icons.queue_music,size:50,color:Colors.white70,),),// 播放量Positioned(right:8,top:8,child:Container(padding:constEdgeInsets.symmetric(horizontal:6,vertical:2,),decoration:BoxDecoration(color:Colors.black.withOpacity(0.5),borderRadius:BorderRadius.circular(10),),child:Row(mainAxisSize:MainAxisSize.min,children:[constIcon(Icons.play_arrow,color:Colors.white,size:12,),constSizedBox(width:2),Text('${(index+1)*10}万',style:constTextStyle(color:Colors.white,fontSize:10,),),],),),),],),);}封面使用Container配合BoxDecoration实现圆角背景和阴影效果。Stack叠加封面图标和播放量标签,Positioned将播放量定位在右上角。播放量使用半透明黑色背景,让文字在任何颜色的封面上都清晰可见。
/// 构建歌单标题Widget_buildPlaylistTitle(int index){returnText('$_selectedCategory歌单${index+1}',style:constTextStyle(fontSize:14,fontWeight:FontWeight.w400,),maxLines:2,overflow:TextOverflow.ellipsis,);}}歌单标题中包含当前选中的分类名称,切换分类时标题也会相应变化。maxLines限制最多显示两行,overflow设置溢出时显示省略号,保证界面整洁。
分类标签栏实现详解
分类标签使用横向滚动的ListView.builder实现,这是处理横向列表的标准方式:
// 横向滚动列表的关键配置ListView.builder(scrollDirection:Axis.horizontal,// 设置为横向滚动padding:constEdgeInsets.symmetric(horizontal:16),itemCount:_categories.length,itemBuilder:(context,index){// 构建每个标签},)scrollDirection设为Axis.horizontal是关键配置,让列表从左到右滚动。padding设置水平内边距,让第一个和最后一个标签不会紧贴屏幕边缘。
GridView网格布局说明
GridView.builder是创建网格列表的最佳选择,它采用懒加载方式只构建可见区域的子项:
// 网格布局配置详解GridView.builder(padding:constEdgeInsets.all(16),gridDelegate:constSliverGridDelegateWithFixedCrossAxisCount(crossAxisCount:2,// 每行显示2列childAspectRatio:0.85,// 子项宽高比crossAxisSpacing:12,// 列间距mainAxisSpacing:12,// 行间距),itemCount:20,itemBuilder:(context,index){returnbuildItem(index);},)SliverGridDelegateWithFixedCrossAxisCount定义网格布局规则,crossAxisCount设置列数,childAspectRatio设置宽高比(宽度/高度),小于1表示高度大于宽度。
动态颜色分配
封面使用Colors.primaries数组中的颜色,通过取模运算让每个歌单有不同的颜色:
// 动态颜色分配color:Colors.primaries[index%Colors.primaries.length].withOpacity(0.3)Colors.primaries是Flutter内置的主色调数组,包含红、粉、紫、蓝、青、绿、黄、橙等18种颜色。取模运算确保index超出数组长度时能循环使用颜色。
状态管理说明
页面使用StatefulWidget管理选中的分类状态:
// 状态更新onTap:(){setState((){_selectedCategory=category;});}setState会触发build方法重新执行,UI会根据新的状态重新渲染。分类标签的颜色和歌单标题都会相应更新。
页面导航与传参
点击歌单时使用GetX进行页面导航:
// 页面导航Get.to(()=>PlaylistDetailPage(id:index))Get.to是GetX提供的导航方法,比Navigator.push更简洁。通过构造函数传递歌单ID,详情页可以根据ID加载对应的歌单数据。
小结
本篇实现了音乐播放器的歌单列表页面。通过横向滚动的分类标签栏让用户快速筛选感兴趣的歌单类型,使用GridView网格布局展示歌单。setState管理选中状态,配合条件渲染实现UI的动态更新。封面上叠加播放量标签,让用户快速了解歌单的热度。这种设计模式在很多内容类App中都很常见。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net