Flutter for OpenHarmony音乐播放器App实战10:歌单列表实现

歌单列表页面展示各种分类的歌单,用户可以通过分类筛选找到感兴趣的歌单。我们使用分类标签栏配合网格布局实现这个页面。本篇将详细介绍如何实现一个功能完善的歌单列表页面。

功能分析

歌单列表页面需要实现以下功能:分类标签栏(全部、流行、摇滚、民谣等)、歌单网格展示、点击歌单进入详情页、分类切换时更新歌单列表。这个页面是用户发现新歌单的重要入口,设计上需要让用户能快速找到感兴趣的分类。

核心技术点

本篇涉及的核心技术包括:横向滚动的分类标签栏、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

相关推荐
Highcharts.js2 小时前
如何使用Highcharts Flutter的官方使用文档
javascript·flutter·开发文档·highcharts
一起养小猫2 小时前
Flutter for OpenHarmony 实战:华容道游戏完整开发指南
flutter·游戏·harmonyos
ujainu11 小时前
Flutter + OpenHarmony 游戏开发进阶:轨迹拖尾特效——透明度渐变与轨迹数组管理
flutter·游戏·openharmony
一起养小猫11 小时前
Flutter for OpenHarmony 实战:记账应用数据统计与可视化
开发语言·jvm·数据库·flutter·信息可视化·harmonyos
2501_9445255412 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 支出分析页面
android·开发语言·前端·javascript·flutter
微祎_15 小时前
Flutter for OpenHarmony:构建一个 Flutter 躲避障碍游戏,深入解析帧同步、动态难度与归一化坐标系统
flutter·游戏
一起养小猫16 小时前
Flutter for OpenHarmony 实战:番茄钟应用完整开发指南
开发语言·jvm·数据库·flutter·信息可视化·harmonyos
一起养小猫16 小时前
Flutter for OpenHarmony 实战:数据持久化方案深度解析
网络·jvm·数据库·flutter·游戏·harmonyos
雨季66617 小时前
Flutter 三端应用实战:OpenHarmony “微光笔记”——在灵感消逝前,为思想点一盏灯
开发语言·javascript·flutter·ui·dart