Flutter for OpenHarmony 音乐播放器App实战 - 评论实现

前言
评论功能是音乐App中增强用户互动的重要模块。用户可以在这里分享对歌曲的感受,看看其他人的想法,找到志同道合的乐友。本篇我们来实现一个完整的评论页面,包含精彩评论和最新评论两个Tab、评论列表展示、以及底部评论输入框。
功能设计
评论页面需要实现以下功能:顶部显示评论总数,通过TabBar切换精彩评论和最新评论,每条评论展示用户头像、昵称、评论内容、发布时间,以及点赞、回复、分享三个操作按钮。页面底部固定一个评论输入框,用户可以随时发表自己的看法。
页面框架
评论页面需要管理TabController和输入框控制器,所以使用 StatefulWidget。同时因为要使用 TabController,需要混入 SingleTickerProviderStateMixin:
class CommentPage extends StatefulWidget {
const CommentPage({super.key});
State<CommentPage> createState() => _CommentPageState();
}
class _CommentPageState extends State<CommentPage>
with SingleTickerProviderStateMixin {
late TabController _tabController;
final TextEditingController _commentController = TextEditingController();
SingleTickerProviderStateMixin 为 TabController 提供动画所需的 vsync 参数。_commentController 用于管理评论输入框的内容。
在 initState 中初始化 TabController:
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
}
length: 2 表示有两个Tab页。vsync: this 将当前State作为动画的同步器,这就是为什么需要混入 SingleTickerProviderStateMixin。
别忘了在 dispose 中释放资源:
void dispose() {
_tabController.dispose();
_commentController.dispose();
super.dispose();
}
控制器用完后必须释放,否则会造成内存泄漏。
页面主体结构
页面由AppBar、TabBarView和底部输入框三部分组成:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('评论 (2.3万)'),
bottom: TabBar(
controller: _tabController,
indicatorColor: const Color(0xFFE91E63),
tabs: const [Tab(text: '精彩评论'), Tab(text: '最新评论')],
),
),
AppBar的 title 显示评论总数,让用户一眼就能看到这首歌的热度。bottom 属性放置 TabBar,indicatorColor 设置下划线指示器的颜色为主题粉色。
body: Column(
children: [
Expanded(
child: TabBarView(
controller: _tabController,
children: [
_buildCommentList(true),
_buildCommentList(false)
],
),
),
_buildCommentInput(),
],
),
);
}
body 使用 Column 布局,TabBarView 用 Expanded 包裹占据剩余空间,底部输入框固定在最下方。_buildCommentList 方法接收一个布尔参数,用于区分是精彩评论还是最新评论。
评论列表实现
评论列表是页面的核心部分,我们用一个方法来构建:
Widget _buildCommentList(bool isHot) {
final comments = List.generate(15, (i) => {
'user': '用户${i + 1}',
'content': isHot
? '这首歌太好听了!每次听都会感动到流泪,推荐给所有人!'
: '刚刚听完,很不错的一首歌',
'time': isHot ? '${i + 1}天前' : '${i * 5 + 1}分钟前',
'likes': isHot ? (1000 - i * 50) : (50 - i * 3),
});
这里用 List.generate 生成模拟数据。精彩评论的内容更长、点赞数更多、发布时间更早;最新评论则相反。实际项目中这些数据应该从服务器获取。
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: comments.length,
itemBuilder: (context, index) {
final comment = comments[index];
return Container(
margin: const EdgeInsets.only(bottom: 16),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(
backgroundColor: Colors.primaries[index % Colors.primaries.length],
child: Text(comment['user'].toString()[0],
style: const TextStyle(color: Colors.white))
),
const SizedBox(width: 12),
每条评论使用 Row 布局,左侧是圆形头像,右侧是评论内容。CircleAvatar 的背景色从 Colors.primaries 中循环取值,让每个用户的头像颜色不同。头像中显示用户名的首字母。crossAxisAlignment: CrossAxisAlignment.start 让头像和内容顶部对齐。
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(comment['user'] as String,
style: const TextStyle(
color: Color(0xFFE91E63),
fontWeight: FontWeight.bold
)
),
const Spacer(),
Text(comment['time'] as String,
style: const TextStyle(color: Colors.grey, fontSize: 12)
),
],
),
const SizedBox(height: 8),
Text(comment['content'] as String),
评论内容区域使用 Expanded 占据剩余宽度。第一行是用户名和发布时间,用户名使用粉色粗体突出显示,时间使用灰色小字靠右对齐。Spacer 组件会占据中间所有空白区域,把时间推到最右边。
评论操作按钮
每条评论下方有点赞、回复、分享三个操作按钮:
const SizedBox(height: 8),
Row(
children: [
GestureDetector(
onTap: () {},
child: Row(
children: [
const Icon(Icons.thumb_up_outlined,
size: 16, color: Colors.grey),
const SizedBox(width: 4),
Text('${comment['likes']}',
style: const TextStyle(
color: Colors.grey, fontSize: 12
)
)
]
),
),
点赞按钮使用 GestureDetector 包裹,内部是图标和点赞数的组合。图标使用空心的大拇指 Icons.thumb_up_outlined,大小设为16像素与文字协调。
const SizedBox(width: 24),
GestureDetector(
onTap: () {},
child: const Row(
children: [
Icon(Icons.comment_outlined,
size: 16, color: Colors.grey),
SizedBox(width: 4),
Text('回复',
style: TextStyle(color: Colors.grey, fontSize: 12))
]
)
),
const SizedBox(width: 24),
GestureDetector(
onTap: () {},
child: const Row(
children: [
Icon(Icons.share_outlined,
size: 16, color: Colors.grey),
SizedBox(width: 4),
Text('分享',
style: TextStyle(color: Colors.grey, fontSize: 12))
]
)
),
],
),
],
),
),
],
),
);
},
);
}
回复和分享按钮的结构与点赞类似,三个按钮之间用24像素的间距分隔。所有按钮都使用灰色,保持视觉上的统一和低调。
底部评论输入框
评论输入框固定在页面底部,方便用户随时发表评论:
Widget _buildCommentInput() {
return Container(
padding: const EdgeInsets.all(12),
decoration: const BoxDecoration(
color: Color(0xFF1E1E1E),
border: Border(top: BorderSide(color: Colors.grey, width: 0.2))
),
输入框容器使用深灰色背景,顶部有一条细细的分割线,与上方的评论列表区分开来。Border 只设置 top 属性可以实现单边边框。
child: Row(
children: [
Expanded(
child: TextField(
controller: _commentController,
decoration: InputDecoration(
hintText: '写下你的评论...',
hintStyle: const TextStyle(color: Colors.grey),
filled: true,
fillColor: const Color(0xFF2A2A2A),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
borderSide: BorderSide.none
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8
),
),
),
),
输入框使用 Expanded 占据大部分宽度。borderRadius: BorderRadius.circular(20) 让输入框呈现圆角胶囊形状。contentPadding 设置输入框内部的文字边距,让文字不会紧贴边缘。
const SizedBox(width: 12),
Container(
decoration: const BoxDecoration(
color: Color(0xFFE91E63),
shape: BoxShape.circle
),
child: IconButton(
icon: const Icon(Icons.send, color: Colors.white, size: 20),
onPressed: () {}
),
),
],
),
);
}
发送按钮是一个粉色圆形容器,内部放置白色的发送图标。BoxShape.circle 让容器呈现正圆形。点击按钮后应该获取输入框内容、发送到服务器、然后清空输入框并刷新列表。
页面入口
评论页面的入口在播放器页面,用户点击评论图标即可进入:
IconButton(
icon: const Icon(Icons.comment, color: Colors.white70),
onPressed: () => Get.to(() => const CommentPage())
),
使用对话气泡图标 Icons.comment 作为入口,这是用户熟悉的评论标识。
小结
本篇我们实现了一个功能完整的评论页面,包含TabBar切换、评论列表展示和底部输入框。通过 SingleTickerProviderStateMixin 混入实现了TabController的动画支持。评论列表中每条评论都有头像、昵称、内容、时间和操作按钮,信息展示清晰完整。底部输入框采用圆角设计,发送按钮使用主题色突出显示。在实际项目中,还需要对接后端接口实现评论的发布、点赞、回复等功能,以及分页加载更多评论。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)