歌手列表页面展示所有歌手,用户可以浏览并点击进入歌手详情页。本篇我们使用网格布局来实现这个页面,每个歌手显示圆形头像和名称。这是音乐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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐