在这里插入图片描述

前言

评论功能是音乐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();

SingleTickerProviderStateMixinTabController 提供动画所需的 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 属性放置 TabBarindicatorColor 设置下划线指示器的颜色为主题粉色。

      body: Column(
        children: [
          Expanded(
            child: TabBarView(
              controller: _tabController,
              children: [
                _buildCommentList(true), 
                _buildCommentList(false)
              ],
            ),
          ),
          _buildCommentInput(),
        ],
      ),
    );
  }

body 使用 Column 布局,TabBarViewExpanded 包裹占据剩余空间,底部输入框固定在最下方。_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

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐