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