Flutter for OpenHarmony音乐播放器App实战13:歌手列表实现
歌手列表页面展示所有歌手,用户可以浏览并点击进入歌手详情页。本篇我们使用网格布局来实现这个页面,每个歌手显示圆形头像和名称。这是音乐App中常见的歌手展示方式。
歌手列表页面展示所有歌手,用户可以浏览并点击进入歌手详情页。本篇我们使用网格布局来实现这个页面,每个歌手显示圆形头像和名称。这是音乐App中常见的歌手展示方式。
功能分析
歌手列表页面需要实现以下功能:网格布局展示歌手、圆形头像显示、点击歌手进入详情页、歌手分类筛选。这个页面是用户发现新歌手的重要入口,设计上需要让用户能快速浏览和找到感兴趣的歌手。
核心技术点
本篇涉及的核心技术包括:GridView.builder网格布局、CircleAvatar圆形头像、GestureDetector点击事件、GetX路由导航、动态颜色分配、分类筛选功能。
对应代码文件
lib/pages/artist/artist_list_page.dart
完整代码实现
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'artist_detail_page.dart';
这段代码导入了Flutter核心库、GetX状态管理库以及歌手详情页面。歌手列表需要跳转到歌手详情页,因此需要导入详情页的引用。
class ArtistListPage extends StatefulWidget {
const ArtistListPage({super.key});
State<ArtistListPage> createState() => _ArtistListPageState();
}
ArtistListPage继承StatefulWidget,因为我们需要管理歌手分类的选中状态。当用户切换分类时,页面需要更新显示对应分类的歌手。
class _ArtistListPageState extends State<ArtistListPage> {
// 当前选中的分类
String _selectedType = '全部';
// 歌手类型列表
final List<String> _artistTypes = [
'全部',
'华语男',
'华语女',
'欧美男',
'欧美女',
'组合',
'乐队',
];
_selectedType存储当前选中的歌手类型,默认是"全部"。_artistTypes是歌手类型列表,包含了常见的歌手分类方式,方便用户快速筛选。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('歌手'),
elevation: 0,
),
body: Column(
children: [
// 分类筛选栏
_buildFilterBar(),
// 歌手网格
Expanded(
child: _buildArtistGrid(),
),
],
),
);
}
build方法构建页面UI。Scaffold提供基础页面结构,AppBar显示"歌手"标题。页面使用Column垂直排列分类筛选栏和歌手网格,Expanded让网格占据剩余空间。
/// 构建分类筛选栏
Widget _buildFilterBar() {
return SizedBox(
height: 50,
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 16),
itemCount: _artistTypes.length,
itemBuilder: (context, index) {
final type = _artistTypes[index];
final isSelected = type == _selectedType;
return _buildFilterItem(type, isSelected);
},
),
);
}
分类筛选栏固定高度50像素,使用横向滚动的ListView.builder实现。这样可以支持更多分类选项而不会占用太多垂直空间,用户可以左右滑动查看所有分类。
/// 构建单个筛选项
Widget _buildFilterItem(String type, bool isSelected) {
return GestureDetector(
onTap: () {
setState(() {
_selectedType = type;
});
},
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(
type,
style: TextStyle(
color: isSelected ? Colors.white : Colors.grey,
fontSize: 14,
),
),
),
);
}
GestureDetector处理点击事件,点击时调用setState更新选中的分类。Container使用条件表达式设置背景色,选中状态为粉色主题色,未选中为深灰色。圆角设为20像素呈胶囊形状。
/// 构建歌手网格
Widget _buildArtistGrid() {
return GridView.builder(
padding: const EdgeInsets.all(16),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: 0.8,
crossAxisSpacing: 16,
mainAxisSpacing: 16,
),
itemCount: 30,
itemBuilder: (context, index) {
return _buildArtistItem(index);
},
);
}
GridView.builder用于构建网格布局,采用懒加载方式只构建可见区域的子项。gridDelegate配置网格为3列,宽高比0.8,间距16像素。每行显示3个歌手,布局紧凑又不拥挤。
/// 构建单个歌手项
Widget _buildArtistItem(int index) {
return GestureDetector(
onTap: () {
Get.to(() => ArtistDetailPage(id: index));
},
child: Column(
children: [
// 歌手头像
Expanded(
child: _buildArtistAvatar(index),
),
const SizedBox(height: 8),
// 歌手名称
_buildArtistName(index),
],
),
);
}
GestureDetector处理点击事件,通过Get.to导航到歌手详情页并传递歌手ID。Column垂直排列头像和名称,Expanded让头像区域占据剩余空间。
/// 构建歌手头像
Widget _buildArtistAvatar(int index) {
return Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: CircleAvatar(
radius: 45,
backgroundColor: Colors.primaries[index % Colors.primaries.length]
.withOpacity(0.3),
child: const Icon(
Icons.person,
size: 40,
color: Colors.white70,
),
),
);
}
头像使用Container包裹CircleAvatar,添加阴影效果增加层次感。CircleAvatar显示圆形头像,半径45像素。背景色从primaries颜色列表循环取值,让每个歌手有不同的颜色。
/// 构建歌手名称
Widget _buildArtistName(int index) {
return Column(
children: [
Text(
'歌手 ${index + 1}',
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Text(
'${(index + 1) * 5}首歌曲',
style: const TextStyle(
fontSize: 11,
color: Colors.grey,
),
),
],
);
}
}
歌手名称使用14像素字体,fontWeight设为w500稍微加粗。下方显示歌曲数量作为辅助信息,使用灰色小字体。maxLines限制只显示一行,overflow设置溢出时显示省略号。
GridView网格布局详解
GridView.builder是创建网格列表的最佳选择,它采用懒加载方式只构建可见区域的子项:
// 网格布局配置详解
GridView.builder(
padding: const EdgeInsets.all(16),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // 每行显示3列
childAspectRatio: 0.8, // 子项宽高比
crossAxisSpacing: 16, // 列间距
mainAxisSpacing: 16, // 行间距
),
itemCount: 30,
itemBuilder: (context, index) {
return buildItem(index);
},
)
padding设置网格的内边距,让内容不会紧贴屏幕边缘。crossAxisCount设为3表示每行显示3个歌手,这个数量在手机屏幕上显示效果较好。
SliverGridDelegateWithFixedCrossAxisCount说明
这个委托类用于定义网格的布局规则:
// 布局参数说明
const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, // 每行显示的列数
childAspectRatio: 0.8, // 子项的宽高比(宽/高)
crossAxisSpacing: 16, // 列间距
mainAxisSpacing: 16, // 行间距
)
childAspectRatio设置子项的宽高比,0.8表示高度比宽度大一点,适合显示圆形头像加文字。crossAxisSpacing和mainAxisSpacing分别设置列间距和行间距。
CircleAvatar圆形头像组件
CircleAvatar是Flutter提供的圆形头像组件,非常适合显示用户头像:
// CircleAvatar基本用法
CircleAvatar(
radius: 45, // 头像半径
backgroundColor: Colors.blue, // 背景色
backgroundImage: NetworkImage(url), // 网络图片
child: Icon(Icons.person), // 子组件(无图片时显示)
)
radius设置头像半径,backgroundColor设置背景色。实际项目中可以使用backgroundImage加载网络图片,child作为图片加载失败时的占位显示。
动态颜色分配
头像使用Colors.primaries数组中的颜色,通过取模运算让每个歌手有不同的颜色:
// 动态颜色分配
backgroundColor: Colors.primaries[index % Colors.primaries.length]
.withOpacity(0.3)
Colors.primaries是Flutter内置的主色调数组,包含18种颜色。取模运算确保index超出数组长度时能循环使用颜色。withOpacity(0.3)降低透明度让颜色更柔和。
阴影效果实现
为头像添加阴影效果增加层次感:
// 阴影效果
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: CircleAvatar(...),
)
BoxShadow的color设置阴影颜色,blurRadius设置模糊半径,offset设置阴影偏移。向下偏移4像素让阴影看起来像是光从上方照射。
文本溢出处理
歌手名称可能很长,需要处理溢出情况:
// 文本溢出处理
Text(
'歌手名称',
style: const TextStyle(fontSize: 14),
maxLines: 1,
overflow: TextOverflow.ellipsis,
)
maxLines设为1限制只显示一行,overflow设为TextOverflow.ellipsis在文本溢出时显示省略号。这是处理长文本的常用方式,保证界面整洁。
页面导航实现
点击歌手时使用GetX进行页面导航:
// 页面导航
GestureDetector(
onTap: () => Get.to(() => ArtistDetailPage(id: index)),
child: // 歌手项内容
)
Get.to是GetX提供的导航方法,通过构造函数传递歌手ID。详情页可以根据ID加载对应的歌手数据,包括歌手信息、热门歌曲、专辑列表等。
小结
本篇实现了音乐播放器的歌手列表页面。使用GridView.builder实现网格布局,每行显示3个歌手。CircleAvatar用于显示圆形头像,GestureDetector处理点击事件。分类筛选栏让用户可以快速找到感兴趣的歌手类型。这种网格布局在很多App中都会用到,比如通讯录、相册等。通过调整crossAxisCount和childAspectRatio参数,可以轻松适配不同的设计需求。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)