在音乐播放器应用中,下载管理是一个非常实用的功能。用户可以将喜欢的歌曲下载到本地,方便在没有网络的情况下收听。本篇文章将详细介绍如何实现一个完整的下载管理页面,包括已下载列表、下载中列表以及多选删除等功能。

页面整体结构

下载管理页面采用TabBar切换的方式,将已下载和下载中两个列表分开展示。这种设计让用户可以清晰地区分不同状态的下载任务。

import 'package:flutter/material.dart';
import 'package:get/get.dart';

/// 下载管理页面
/// 管理已下载和正在下载的音乐文件
class DownloadPage extends StatefulWidget {
  const DownloadPage({super.key});

  
  State<DownloadPage> createState() => _DownloadPageState();
}

这里我们创建了一个StatefulWidget,因为下载页面需要管理多个状态,比如Tab切换、多选模式等。使用GetX进行状态管理可以让代码更加简洁。

状态管理与TabController

页面需要管理多个状态变量,包括TabController、多选模式标志以及选中项的集合。

class _DownloadPageState extends State<DownloadPage> with SingleTickerProviderStateMixin {
  // TabController用于控制Tab切换
  late TabController _tabController;
  // 是否处于多选模式
  bool _isMultiSelect = false;
  // 选中的已下载歌曲索引集合
  final Set<int> _selectedDownloaded = {};
  // 选中的下载中歌曲索引集合
  final Set<int> _selectedDownloading = {};

这里使用了SingleTickerProviderStateMixin,这是使用TabController的必要条件。_isMultiSelect用于控制是否显示多选框,两个Set集合分别存储已下载和下载中列表的选中项。

初始化与资源释放

在initState中初始化TabController,在dispose中释放资源,这是Flutter开发中的标准做法。

  
  void initState() {
    super.initState();
    _tabController = TabController(length: 2, vsync: this);
  }

  
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

TabController的length设置为2,对应"已下载"和"下载中"两个Tab。vsync参数传入this,因为我们的State类混入了SingleTickerProviderStateMixin。

AppBar与TabBar设计

页面顶部包含返回按钮、标题、TabBar以及操作按钮。

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: const Icon(Icons.arrow_back),
          onPressed: () => Get.back(),
        ),
        title: const Text('下载管理'),
        bottom: TabBar(
          controller: _tabController,
          labelColor: const Color(0xFFE91E63),
          unselectedLabelColor: Colors.grey,
          indicatorColor: const Color(0xFFE91E63),
          tabs: const [
            Tab(text: '已下载'),
            Tab(text: '下载中'),
          ],
        ),

TabBar的样式设计采用了粉色主题色(0xFFE91E63),选中的Tab文字和指示器都使用这个颜色,未选中的Tab使用灰色,形成明显的视觉对比。

操作按钮区域

AppBar的actions区域放置了多选和删除按钮。

        actions: [
          IconButton(
            icon: Icon(_isMultiSelect ? Icons.close : Icons.checklist),
            onPressed: () {
              setState(() {
                _isMultiSelect = !_isMultiSelect;
                _selectedDownloaded.clear();
                _selectedDownloading.clear();
              });
            },
          ),
          if (_isMultiSelect)
            IconButton(
              icon: const Icon(Icons.delete_outline),
              onPressed: _deleteSelected,
            ),
        ],
      ),

点击多选按钮时,切换_isMultiSelect状态,同时清空之前的选中项。当处于多选模式时,会显示删除按钮。这种条件渲染的方式让界面更加简洁。

删除选中项的逻辑

删除功能需要根据当前所在的Tab来决定删除哪个列表的内容。

  void _deleteSelected() {
    final currentTab = _tabController.index;
    final selectedSet = currentTab == 0 ? _selectedDownloaded : _selectedDownloading;
    
    if (selectedSet.isEmpty) {
      Get.snackbar('提示', '请先选择要删除的歌曲');
      return;
    }
    
    Get.dialog(
      AlertDialog(
        title: const Text('确认删除'),
        content: Text('确定要删除选中的 ${selectedSet.length} 首歌曲吗?'),
        actions: [
          TextButton(
            onPressed: () => Get.back(),
            child: const Text('取消'),
          ),
          TextButton(
            onPressed: () {
              // 执行删除逻辑
              setState(() {
                selectedSet.clear();
                _isMultiSelect = false;
              });
              Get.back();
              Get.snackbar('成功', '删除成功');
            },
            child: const Text('确定', style: TextStyle(color: Color(0xFFE91E63))),
          ),
        ],
      ),
    );
  }

删除前会弹出确认对话框,避免用户误操作。使用GetX的dialog方法可以方便地显示对话框,snackbar方法用于显示提示信息。

TabBarView内容区域

页面主体使用TabBarView来展示两个列表。

      body: TabBarView(
        controller: _tabController,
        children: [
          _buildDownloadedList(),
          _buildDownloadingList(),
        ],
      ),
    );
  }

TabBarView与TabBar共用同一个controller,这样切换Tab时内容区域会自动同步切换。两个子Widget分别构建已下载列表和下载中列表。

已下载列表构建

已下载列表展示所有已经下载完成的歌曲。

  Widget _buildDownloadedList() {
    return ListView.builder(
      itemCount: 15,
      itemBuilder: (context, index) {
        final isSelected = _selectedDownloaded.contains(index);
        return ListTile(
          leading: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              if (_isMultiSelect)
                Checkbox(
                  value: isSelected,
                  activeColor: const Color(0xFFE91E63),
                  onChanged: (value) {
                    setState(() {
                      if (value == true) {
                        _selectedDownloaded.add(index);
                      } else {
                        _selectedDownloaded.remove(index);
                      }
                    });
                  },
                ),

当处于多选模式时,每个列表项前面会显示一个Checkbox。使用Set来存储选中项的索引,可以方便地进行添加和删除操作。

歌曲封面与信息展示

每首歌曲都有封面图、歌曲名和歌手名。

              Container(
                width: 50,
                height: 50,
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.circular(8),
                  color: Colors.primaries[index % Colors.primaries.length].withOpacity(0.3),
                ),
                child: const Icon(Icons.music_note, color: Colors.white70),
              ),
            ],
          ),
          title: Text('已下载歌曲 ${index + 1}'),
          subtitle: Text('歌手 ${index + 1}'),

封面使用Container配合圆角装饰,颜色根据索引从Colors.primaries中循环取值,这样每首歌曲的封面颜色都不同,增加视觉层次感。

文件大小与更多操作

列表项的尾部显示文件大小和更多操作按钮。

          trailing: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text(
                '${(index + 1) * 3.5} MB',
                style: const TextStyle(color: Colors.grey, fontSize: 12),
              ),
              IconButton(
                icon: const Icon(Icons.more_vert, color: Colors.grey),
                onPressed: () => _showMoreOptions(index),
              ),
            ],
          ),
          onTap: () {
            // 播放歌曲
          },
        );
      },
    );
  }

文件大小使用灰色小字体显示,更多操作按钮点击后会弹出底部菜单。整个ListTile点击后可以播放歌曲。

更多操作菜单

点击更多按钮后显示的底部菜单。

  void _showMoreOptions(int index) {
    Get.bottomSheet(
      Container(
        padding: const EdgeInsets.symmetric(vertical: 20),
        decoration: const BoxDecoration(
          color: Color(0xFF1E1E1E),
          borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
        ),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            ListTile(
              leading: const Icon(Icons.play_arrow, color: Colors.white),
              title: const Text('播放'),
              onTap: () => Get.back(),
            ),
            ListTile(
              leading: const Icon(Icons.playlist_add, color: Colors.white),
              title: const Text('添加到歌单'),
              onTap: () => Get.back(),
            ),

底部菜单使用GetX的bottomSheet方法,容器顶部设置圆角,背景使用深色主题。菜单项包括播放、添加到歌单、分享和删除等常用操作。

删除单曲功能

底部菜单中的删除选项。

            ListTile(
              leading: const Icon(Icons.share, color: Colors.white),
              title: const Text('分享'),
              onTap: () => Get.back(),
            ),
            ListTile(
              leading: const Icon(Icons.delete_outline, color: Colors.red),
              title: const Text('删除', style: TextStyle(color: Colors.red)),
              onTap: () {
                Get.back();
                _confirmDelete(index);
              },
            ),
          ],
        ),
      ),
    );
  }

删除选项使用红色图标和文字,提醒用户这是一个危险操作。点击后会先关闭底部菜单,然后弹出确认对话框。

单曲删除确认

删除单首歌曲的确认逻辑。

  void _confirmDelete(int index) {
    Get.dialog(
      AlertDialog(
        title: const Text('确认删除'),
        content: const Text('确定要删除这首歌曲吗?删除后将无法恢复。'),
        actions: [
          TextButton(
            onPressed: () => Get.back(),
            child: const Text('取消'),
          ),
          TextButton(
            onPressed: () {
              Get.back();
              Get.snackbar('成功', '删除成功');
            },
            child: const Text('删除', style: TextStyle(color: Colors.red)),
          ),
        ],
      ),
    );
  }

确认对话框明确告知用户删除后无法恢复,删除按钮使用红色文字。这种二次确认的设计可以有效防止误操作。

下载中列表构建

下载中列表展示正在下载的歌曲及其进度。

  Widget _buildDownloadingList() {
    return ListView.builder(
      itemCount: 5,
      itemBuilder: (context, index) {
        final progress = (index + 1) * 0.2;
        return ListTile(
          leading: Container(
            width: 50,
            height: 50,
            decoration: BoxDecoration(
              borderRadius: BorderRadius.circular(8),
              color: Colors.primaries[index % Colors.primaries.length].withOpacity(0.3),
            ),
            child: const Icon(Icons.music_note, color: Colors.white70),
          ),
          title: Text('下载中歌曲 ${index + 1}'),

下载中列表的封面样式与已下载列表保持一致,progress变量模拟不同的下载进度。

下载进度条

使用LinearProgressIndicator显示下载进度。

          subtitle: LinearProgressIndicator(
            value: progress,
            backgroundColor: Colors.grey.withOpacity(0.3),
            valueColor: const AlwaysStoppedAnimation<Color>(Color(0xFFE91E63)),
          ),
          trailing: Text(
            '${(progress * 100).toInt()}%',
            style: const TextStyle(color: Colors.grey, fontSize: 12),
          ),
        );
      },
    );
  }

进度条使用主题色填充,背景使用半透明灰色。尾部显示百分比数字,让用户清楚地知道下载进度。

空状态处理

当列表为空时,需要显示友好的提示。

  Widget _buildEmptyState(String message) {
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(
            Icons.download_outlined,
            size: 80,
            color: Colors.grey.withOpacity(0.5),
          ),
          const SizedBox(height: 16),
          Text(
            message,
            style: TextStyle(
              color: Colors.grey.withOpacity(0.8),
              fontSize: 16,
            ),
          ),
        ],
      ),
    );
  }

空状态页面使用大图标配合文字说明,居中显示。这种设计比单纯显示空白页面更加友好。

存储空间显示

在页面底部可以添加存储空间使用情况的显示。

  Widget _buildStorageInfo() {
    return Container(
      padding: const EdgeInsets.all(16),
      color: const Color(0xFF1E1E1E),
      child: Row(
        children: [
          const Icon(Icons.storage, color: Colors.grey),
          const SizedBox(width: 12),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text('存储空间', style: TextStyle(fontSize: 14)),
                const SizedBox(height: 4),
                LinearProgressIndicator(
                  value: 0.35,
                  backgroundColor: Colors.grey.withOpacity(0.3),
                  valueColor: const AlwaysStoppedAnimation<Color>(Color(0xFFE91E63)),
                ),
              ],
            ),
          ),
          const SizedBox(width: 12),
          const Text('3.5GB / 10GB', style: TextStyle(color: Colors.grey, fontSize: 12)),
        ],
      ),
    );
  }

存储信息栏显示已用空间和总空间,配合进度条直观展示使用比例。这个功能可以帮助用户了解设备存储状况。

总结

下载管理页面的实现涵盖了多个Flutter开发中的常用技术点:TabBar与TabBarView的配合使用、多选模式的状态管理、列表项的自定义布局、底部菜单和对话框的使用等。通过合理的UI设计和交互逻辑,为用户提供了便捷的下载管理体验。在实际项目中,还需要结合后端接口实现真正的下载功能和文件管理。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐