在这里插入图片描述

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

🎯 欢迎来到 Flutter for OpenHarmony 社区!本文将深入讲解 Flutter 中 ReorderableListView 可排序列表组件的使用方法,带你从基础到精通,掌握拖拽排序、列表重排等交互功能。


一、ReorderableListView 组件概述

在移动应用开发中,拖拽排序是一种常见的交互模式。用户可以通过长按并拖动列表项来调整顺序,这种设计常用于任务列表、收藏夹排序、播放列表管理等场景。Flutter 提供了 ReorderableListView 组件,专门用于实现这种可拖拽排序的列表功能。

📋 ReorderableListView 组件特点

特点 说明
拖拽排序 支持长按拖拽调整列表项顺序
动画效果 内置平滑的排序动画
滚动支持 拖拽到边缘时自动滚动
自定义代理 支持自定义拖拽时的显示样式
Material 设计 遵循 Material Design 设计规范

ReorderableListView 与 ListView 的区别

特性 ListView ReorderableListView
排序功能 不支持 支持
拖拽交互 不支持 支持
使用复杂度 较低 较高
适用场景 普通列表展示 需要排序的列表

💡 使用场景:ReorderableListView 适合需要用户自定义排序的场景,如任务优先级调整、播放列表排序、收藏夹管理等。如果只需要展示列表,使用普通的 ListView 即可。


二、ReorderableListView 基础用法

ReorderableListView 的使用需要提供列表项和排序回调。让我们从最基础的用法开始学习。

2.1 最简单的 ReorderableListView

最基础的 ReorderableListView 需要设置 childrenonReorder 参数:

class BasicReorderableListView extends StatefulWidget {
  const BasicReorderableListView({super.key});

  
  State<BasicReorderableListView> createState() => _BasicReorderableListViewState();
}

class _BasicReorderableListViewState extends State<BasicReorderableListView> {
  final List<String> _items = ['项目 1', '项目 2', '项目 3', '项目 4', '项目 5'];

  
  Widget build(BuildContext context) {
    return ReorderableListView(
      onReorder: (oldIndex, newIndex) {
        setState(() {
          if (newIndex > oldIndex) {
            newIndex -= 1;
          }
          final item = _items.removeAt(oldIndex);
          _items.insert(newIndex, item);
        });
      },
      children: _items.map((item) {
        return ListTile(
          key: ValueKey(item),
          title: Text(item),
          trailing: const Icon(Icons.drag_handle),
        );
      }).toList(),
    );
  }
}

代码解析:

  • onReorder:排序回调,当用户完成拖拽排序时触发
  • oldIndex:原位置索引
  • newIndex:目标位置索引
  • key:每个列表项必须有唯一的 key
  • trailing:通常添加拖拽手柄图标

2.2 理解 onReorder 回调

onReorder 回调中的索引处理需要特别注意:

onReorder: (oldIndex, newIndex) {
  setState(() {
    if (newIndex > oldIndex) {
      newIndex -= 1;
    }
    final item = _items.removeAt(oldIndex);
    _items.insert(newIndex, item);
  });
}

索引处理说明:

  • 当向下拖动时(newIndex > oldIndex),newIndex 需要减 1
  • 当向上拖动时(newIndex < oldIndex),newIndex 不需要调整
  • 这是因为 removeAt 之后,后面的元素索引会前移

2.3 完整示例

下面是一个完整的可运行示例,展示了 ReorderableListView 的基础用法:

class ReorderableListViewExample extends StatefulWidget {
  const ReorderableListViewExample({super.key});

  
  State<ReorderableListViewExample> createState() => _ReorderableListViewExampleState();
}

class _ReorderableListViewExampleState extends State<ReorderableListViewExample> {
  final List<Task> _tasks = [
    Task(id: '1', title: '完成项目报告', priority: 1),
    Task(id: '2', title: '回复客户邮件', priority: 2),
    Task(id: '3', title: '准备会议材料', priority: 3),
    Task(id: '4', title: '代码审查', priority: 4),
    Task(id: '5', title: '更新文档', priority: 5),
  ];

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('任务排序'),
        actions: [
          IconButton(
            icon: const Icon(Icons.info_outline),
            onPressed: () {
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('长按并拖动来调整顺序')),
              );
            },
          ),
        ],
      ),
      body: ReorderableListView.builder(
        itemCount: _tasks.length,
        onReorder: (oldIndex, newIndex) {
          setState(() {
            if (newIndex > oldIndex) {
              newIndex -= 1;
            }
            final task = _tasks.removeAt(oldIndex);
            _tasks.insert(newIndex, task);

            for (int i = 0; i < _tasks.length; i++) {
              _tasks[i] = Task(
                id: _tasks[i].id,
                title: _tasks[i].title,
                priority: i + 1,
              );
            }
          });
        },
        itemBuilder: (context, index) {
          final task = _tasks[index];
          return Container(
            key: ValueKey(task.id),
            margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
            decoration: BoxDecoration(
              color: Colors.white,
              borderRadius: BorderRadius.circular(8),
              boxShadow: [
                BoxShadow(
                  color: Colors.grey.withOpacity(0.2),
                  blurRadius: 4,
                  offset: const Offset(0, 2),
                ),
              ],
            ),
            child: ListTile(
              leading: CircleAvatar(
                backgroundColor: Colors.blue,
                child: Text(
                  '${task.priority}',
                  style: const TextStyle(color: Colors.white),
                ),
              ),
              title: Text(task.title),
              trailing: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  IconButton(
                    icon: const Icon(Icons.delete_outline),
                    onPressed: () {
                      setState(() {
                        _tasks.removeAt(index);
                      });
                    },
                  ),
                  const Icon(Icons.drag_handle),
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

class Task {
  final String id;
  final String title;
  final int priority;

  Task({required this.id, required this.title, required this.priority});
}

三、ReorderableListView 构造方法

ReorderableListView 提供了两种构造方法。

3.1 ReorderableListView(直接构造)

使用 children 参数直接提供列表项:

ReorderableListView(
  onReorder: (oldIndex, newIndex) {},
  children: [
    ListTile(key: ValueKey('1'), title: Text('项目 1')),
    ListTile(key: ValueKey('2'), title: Text('项目 2')),
    ListTile(key: ValueKey('3'), title: Text('项目 3')),
  ],
)

3.2 ReorderableListView.builder(构建器构造)

使用 itemBuilderitemCount 参数构建列表项:

ReorderableListView.builder(
  itemCount: _items.length,
  onReorder: (oldIndex, newIndex) {},
  itemBuilder: (context, index) {
    return ListTile(
      key: ValueKey(_items[index].id),
      title: Text(_items[index].title),
    );
  },
)

📊 两种构造方法对比

特性 直接构造 构建器构造
适用场景 少量固定项 大量动态项
性能 较低 较高
灵活性 较低 较高

四、ReorderableListView 样式定制

ReorderableListView 提供了多种样式定制选项。

4.1 设置代理装饰

通过 proxyDecorator 自定义拖拽时的显示样式:

ReorderableListView(
  onReorder: (oldIndex, newIndex) {},
  proxyDecorator: (child, index, animation) {
    return AnimatedBuilder(
      animation: animation,
      builder: (context, child) {
        final animValue = Curves.easeInOut.transform(animation.value);
        final elevation = 1 + animValue * 8;
        final scale = 1 + animValue * 0.05;
        return Transform.scale(
          scale: scale,
          child: Material(
            elevation: elevation,
            color: Colors.blue[100],
            borderRadius: BorderRadius.circular(8),
            child: child,
          ),
        );
      },
      child: child,
    );
  },
  children: [...],
)

4.2 设置头部和尾部

通过 headerfooter 参数添加头部和尾部:

ReorderableListView(
  header: Container(
    padding: const EdgeInsets.all(16),
    color: Colors.blue[100],
    child: const Text('长按拖动排序'),
  ),
  footer: Container(
    padding: const EdgeInsets.all(16),
    color: Colors.grey[100],
    child: const Text('列表结束'),
  ),
  onReorder: (oldIndex, newIndex) {},
  children: [...],
)

4.3 设置滚动方向

通过 scrollDirection 参数设置滚动方向:

ReorderableListView(
  scrollDirection: Axis.horizontal,
  onReorder: (oldIndex, newIndex) {},
  children: [...],
)

📊 ReorderableListView 属性速查表

属性 类型 说明
onReorder void Function(int, int) 排序回调
children List? 列表项(直接构造)
itemBuilder Widget Function(int)? 列表项构建器
itemCount int? 列表项数量
header Widget? 头部组件
footer Widget? 尾部组件
proxyDecorator Widget Function(…)? 拖拽代理装饰
scrollDirection Axis 滚动方向
padding EdgeInsetsGeometry? 内边距
buildDefaultDragHandles bool 是否显示默认拖拽手柄

五、ReorderableListView 实际应用场景

ReorderableListView 在实际开发中有着广泛的应用,让我们通过具体示例来学习。

5.1 任务优先级排序

使用 ReorderableListView 实现任务优先级排序:

class TaskPriorityPage extends StatefulWidget {
  const TaskPriorityPage({super.key});

  
  State<TaskPriorityPage> createState() => _TaskPriorityPageState();
}

class _TaskPriorityPageState extends State<TaskPriorityPage> {
  final List<TaskItem> _tasks = [
    TaskItem(id: '1', title: '紧急任务 A', description: '需要今天完成', color: Colors.red),
    TaskItem(id: '2', title: '重要任务 B', description: '本周内完成', color: Colors.orange),
    TaskItem(id: '3', title: '普通任务 C', description: '下周完成', color: Colors.blue),
    TaskItem(id: '4', title: '低优先级 D', description: '有空再做', color: Colors.grey),
    TaskItem(id: '5', title: '待定任务 E', description: '等待确认', color: Colors.purple),
  ];

  void _onReorder(int oldIndex, int newIndex) {
    setState(() {
      if (newIndex > oldIndex) {
        newIndex -= 1;
      }
      final task = _tasks.removeAt(oldIndex);
      _tasks.insert(newIndex, task);
    });
  }

  void _deleteTask(int index) {
    setState(() {
      _tasks.removeAt(index);
    });
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('任务已删除')),
    );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('任务优先级'),
        actions: [
          IconButton(
            icon: const Icon(Icons.add),
            onPressed: () {
              setState(() {
                _tasks.add(TaskItem(
                  id: DateTime.now().toString(),
                  title: '新任务 ${_tasks.length + 1}',
                  description: '新添加的任务',
                  color: Colors.green,
                ));
              });
            },
          ),
        ],
      ),
      body: Column(
        children: [
          Container(
            padding: const EdgeInsets.all(16),
            color: Colors.grey[100],
            child: const Row(
              children: [
                Icon(Icons.info_outline, size: 16),
                SizedBox(width: 8),
                Text('长按拖动调整优先级,越靠前优先级越高'),
              ],
            ),
          ),
          Expanded(
            child: ReorderableListView.builder(
              itemCount: _tasks.length,
              onReorder: _onReorder,
              proxyDecorator: (child, index, animation) {
                return Material(
                  elevation: 6,
                  color: Colors.transparent,
                  child: child,
                );
              },
              itemBuilder: (context, index) {
                final task = _tasks[index];
                return Dismissible(
                  key: ValueKey(task.id),
                  direction: DismissDirection.endToStart,
                  background: Container(
                    color: Colors.red,
                    alignment: Alignment.centerRight,
                    padding: const EdgeInsets.only(right: 16),
                    child: const Icon(Icons.delete, color: Colors.white),
                  ),
                  onDismissed: (_) => _deleteTask(index),
                  child: Container(
                    key: ValueKey(task.id),
                    margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                    decoration: BoxDecoration(
                      color: Colors.white,
                      borderRadius: BorderRadius.circular(12),
                      border: Border.all(color: task.color.withOpacity(0.3)),
                      boxShadow: [
                        BoxShadow(
                          color: Colors.grey.withOpacity(0.1),
                          blurRadius: 4,
                          offset: const Offset(0, 2),
                        ),
                      ],
                    ),
                    child: ListTile(
                      contentPadding: const EdgeInsets.all(16),
                      leading: Container(
                        width: 40,
                        height: 40,
                        decoration: BoxDecoration(
                          color: task.color.withOpacity(0.2),
                          borderRadius: BorderRadius.circular(8),
                        ),
                        child: Center(
                          child: Text(
                            '${index + 1}',
                            style: TextStyle(
                              color: task.color,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                        ),
                      ),
                      title: Text(
                        task.title,
                        style: const TextStyle(fontWeight: FontWeight.w500),
                      ),
                      subtitle: Text(task.description),
                      trailing: ReorderableDragStartListener(
                        index: index,
                        child: Container(
                          padding: const EdgeInsets.all(8),
                          decoration: BoxDecoration(
                            color: Colors.grey[100],
                            borderRadius: BorderRadius.circular(8),
                          ),
                          child: const Icon(Icons.drag_handle),
                        ),
                      ),
                    ),
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

class TaskItem {
  final String id;
  final String title;
  final String description;
  final Color color;

  TaskItem({
    required this.id,
    required this.title,
    required this.description,
    required this.color,
  });
}

5.2 播放列表排序

使用 ReorderableListView 实现播放列表排序:

class PlaylistPage extends StatefulWidget {
  const PlaylistPage({super.key});

  
  State<PlaylistPage> createState() => _PlaylistPageState();
}

class _PlaylistPageState extends State<PlaylistPage> {
  final List<Song> _playlist = [
    Song(id: '1', title: '歌曲 A', artist: '歌手 1', duration: '3:45'),
    Song(id: '2', title: '歌曲 B', artist: '歌手 2', duration: '4:20'),
    Song(id: '3', title: '歌曲 C', artist: '歌手 3', duration: '3:15'),
    Song(id: '4', title: '歌曲 D', artist: '歌手 4', duration: '5:00'),
    Song(id: '5', title: '歌曲 E', artist: '歌手 5', duration: '4:10'),
  ];

  int? _playingIndex;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('播放列表'),
        actions: [
          IconButton(
            icon: const Icon(Icons.shuffle),
            onPressed: () {
              setState(() {
                _playlist.shuffle();
              });
            },
          ),
        ],
      ),
      body: ReorderableListView.builder(
        itemCount: _playlist.length,
        onReorder: (oldIndex, newIndex) {
          setState(() {
            if (newIndex > oldIndex) {
              newIndex -= 1;
            }
            final song = _playlist.removeAt(oldIndex);
            _playlist.insert(newIndex, song);
          });
        },
        itemBuilder: (context, index) {
          final song = _playlist[index];
          final isPlaying = _playingIndex == index;

          return Container(
            key: ValueKey(song.id),
            decoration: BoxDecoration(
              color: isPlaying ? Colors.blue[50] : null,
            ),
            child: ListTile(
              leading: Container(
                width: 48,
                height: 48,
                decoration: BoxDecoration(
                  color: isPlaying ? Colors.blue : Colors.grey[300],
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Icon(
                  isPlaying ? Icons.pause : Icons.play_arrow,
                  color: isPlaying ? Colors.white : Colors.grey[600],
                ),
              ),
              title: Text(
                song.title,
                style: TextStyle(
                  fontWeight: isPlaying ? FontWeight.bold : FontWeight.normal,
                  color: isPlaying ? Colors.blue : null,
                ),
              ),
              subtitle: Text('${song.artist} · ${song.duration}'),
              trailing: ReorderableDragStartListener(
                index: index,
                child: const Icon(Icons.drag_handle),
              ),
              onTap: () {
                setState(() {
                  _playingIndex = isPlaying ? null : index;
                });
              },
            ),
          );
        },
      ),
    );
  }
}

class Song {
  final String id;
  final String title;
  final String artist;
  final String duration;

  Song({
    required this.id,
    required this.title,
    required this.artist,
    required this.duration,
  });
}

5.3 收藏夹排序

使用 ReorderableListView 实现收藏夹排序:

class FavoritesPage extends StatefulWidget {
  const FavoritesPage({super.key});

  
  State<FavoritesPage> createState() => _FavoritesPageState();
}

class _FavoritesPageState extends State<FavoritesPage> {
  final List<FavoriteItem> _favorites = [
    FavoriteItem(id: '1', name: '收藏夹 A', count: 12, icon: Icons.folder),
    FavoriteItem(id: '2', name: '收藏夹 B', count: 8, icon: Icons.star),
    FavoriteItem(id: '3', name: '收藏夹 C', count: 25, icon: Icons.bookmark),
    FavoriteItem(id: '4', name: '收藏夹 D', count: 5, icon: Icons.favorite),
    FavoriteItem(id: '5', name: '收藏夹 E', count: 18, icon: Icons.flag),
  ];

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('收藏夹管理'),
        actions: [
          IconButton(
            icon: const Icon(Icons.add),
            onPressed: () {
              _showCreateDialog();
            },
          ),
        ],
      ),
      body: ReorderableListView.builder(
        itemCount: _favorites.length,
        onReorder: (oldIndex, newIndex) {
          setState(() {
            if (newIndex > oldIndex) {
              newIndex -= 1;
            }
            final item = _favorites.removeAt(oldIndex);
            _favorites.insert(newIndex, item);
          });
        },
        proxyDecorator: (child, index, animation) {
          return Material(
            elevation: 6,
            borderRadius: BorderRadius.circular(12),
            child: child,
          );
        },
        itemBuilder: (context, index) {
          final item = _favorites[index];
          return Card(
            key: ValueKey(item.id),
            margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
            child: ListTile(
              leading: Container(
                width: 40,
                height: 40,
                decoration: BoxDecoration(
                  color: Colors.blue[100],
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Icon(item.icon, color: Colors.blue),
              ),
              title: Text(item.name),
              subtitle: Text('${item.count} 个项目'),
              trailing: Row(
                mainAxisSize: MainAxisSize.min,
                children: [
                  IconButton(
                    icon: const Icon(Icons.edit_outlined),
                    onPressed: () {
                      _showEditDialog(item, index);
                    },
                  ),
                  ReorderableDragStartListener(
                    index: index,
                    child: const Icon(Icons.drag_handle),
                  ),
                ],
              ),
              onTap: () {
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text('打开 ${item.name}')),
                );
              },
            ),
          );
        },
      ),
    );
  }

  void _showCreateDialog() {
    showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: const Text('新建收藏夹'),
          content: const TextField(
            decoration: InputDecoration(hintText: '收藏夹名称'),
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('取消'),
            ),
            TextButton(
              onPressed: () {
                setState(() {
                  _favorites.add(FavoriteItem(
                    id: DateTime.now().toString(),
                    name: '新收藏夹',
                    count: 0,
                    icon: Icons.folder,
                  ));
                });
                Navigator.pop(context);
              },
              child: const Text('创建'),
            ),
          ],
        );
      },
    );
  }

  void _showEditDialog(FavoriteItem item, int index) {
    showDialog(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: const Text('编辑收藏夹'),
          content: TextField(
            controller: TextEditingController(text: item.name),
            decoration: const InputDecoration(hintText: '收藏夹名称'),
          ),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('取消'),
            ),
            TextButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: const Text('保存'),
            ),
          ],
        );
      },
    );
  }
}

class FavoriteItem {
  final String id;
  final String name;
  final int count;
  final IconData icon;

  FavoriteItem({
    required this.id,
    required this.name,
    required this.count,
    required this.icon,
  });
}

六、ReorderableDragStartListener 拖拽监听器

ReorderableDragStartListener 用于自定义拖拽触发区域。

6.1 基本用法

ListTile(
  title: Text('项目'),
  trailing: ReorderableDragStartListener(
    index: index,
    child: Icon(Icons.drag_handle),
  ),
)

6.2 自定义拖拽区域

将整个列表项设为可拖拽区域:

ReorderableDragStartListener(
  index: index,
  child: Container(
    padding: EdgeInsets.all(16),
    child: Text('整个区域可拖拽'),
  ),
)

6.3 禁用默认拖拽手柄

通过 buildDefaultDragHandles 禁用默认拖拽手柄:

ReorderableListView.builder(
  buildDefaultDragHandles: false,
  itemCount: _items.length,
  onReorder: (oldIndex, newIndex) {},
  itemBuilder: (context, index) {
    return ListTile(
      key: ValueKey(_items[index].id),
      title: Text(_items[index].title),
      trailing: ReorderableDragStartListener(
        index: index,
        child: const Icon(Icons.drag_handle),
      ),
    );
  },
)

七、最佳实践

7.1 性能优化

建议 说明
使用 builder 大量数据使用 ReorderableListView.builder
唯一的 key 确保每个列表项有唯一且稳定的 key
避免复杂代理 简化 proxyDecorator 的实现

7.2 交互设计

建议 说明
显示拖拽手柄 提供明显的拖拽手柄图标
提示操作方式 告知用户长按拖动排序
视觉反馈 拖拽时提供视觉反馈

7.3 样式设计

建议 说明
使用卡片样式 为列表项添加卡片样式
自定义代理 拖拽时显示不同的样式
合理间距 为列表项添加适当的间距

八、总结

ReorderableListView 是 Flutter 中用于实现拖拽排序列表的组件,适合需要用户自定义排序的场景。通过本文的学习,你应该已经掌握了:

  • ReorderableListView 的基本用法和核心概念
  • onReorder 回调的索引处理
  • 如何自定义拖拽代理样式
  • ReorderableDragStartListener 的使用
  • 实际应用场景中的最佳实践

在实际开发中,ReorderableListView 常用于任务优先级排序、播放列表管理、收藏夹排序等场景。结合其他组件如 Dismissible,可以实现更丰富的交互功能。


八、完整示例代码

下面是一个完整的可运行示例,展示了 ReorderableListView 的各种用法:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ReorderableListView 示例',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const ReorderableListViewDemoPage(),
    );
  }
}

class ReorderableListViewDemoPage extends StatefulWidget {
  const ReorderableListViewDemoPage({super.key});

  
  State<ReorderableListViewDemoPage> createState() => _ReorderableListViewDemoPageState();
}

class _ReorderableListViewDemoPageState extends State<ReorderableListViewDemoPage> {
  int _selectedIndex = 0;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('ReorderableListView 示例'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
      ),
      drawer: Drawer(
        child: ListView(
          children: [
            const DrawerHeader(
              decoration: BoxDecoration(color: Colors.blue),
              child: Text(
                'ReorderableListView 示例',
                style: TextStyle(color: Colors.white, fontSize: 24),
              ),
            ),
            ListTile(
              leading: const Icon(Icons.list),
              title: const Text('基础排序'),
              selected: _selectedIndex == 0,
              onTap: () {
                setState(() => _selectedIndex = 0);
                Navigator.pop(context);
              },
            ),
            ListTile(
              leading: const Icon(Icons.check_box),
              title: const Text('任务列表'),
              selected: _selectedIndex == 1,
              onTap: () {
                setState(() => _selectedIndex = 1);
                Navigator.pop(context);
              },
            ),
            ListTile(
              leading: const Icon(Icons.playlist_play),
              title: const Text('播放列表'),
              selected: _selectedIndex == 2,
              onTap: () {
                setState(() => _selectedIndex = 2);
                Navigator.pop(context);
              },
            ),
            ListTile(
              leading: const Icon(Icons.star),
              title: const Text('收藏夹管理'),
              selected: _selectedIndex == 3,
              onTap: () {
                setState(() => _selectedIndex = 3);
                Navigator.pop(context);
              },
            ),
            ListTile(
              leading: const Icon(Icons.brush),
              title: const Text('自定义样式'),
              selected: _selectedIndex == 4,
              onTap: () {
                setState(() => _selectedIndex = 4);
                Navigator.pop(context);
              },
            ),
          ],
        ),
      ),
      body: _buildPage(),
    );
  }

  Widget _buildPage() {
    switch (_selectedIndex) {
      case 0:
        return const BasicReorderablePage();
      case 1:
        return const TaskListPage();
      case 2:
        return const PlaylistPage();
      case 3:
        return const FavoriteManagePage();
      case 4:
        return const CustomStylePage();
      default:
        return const BasicReorderablePage();
    }
  }
}

class BasicReorderablePage extends StatefulWidget {
  const BasicReorderablePage({super.key});

  
  State<BasicReorderablePage> createState() => _BasicReorderablePageState();
}

class _BasicReorderablePageState extends State<BasicReorderablePage> {
  final List<String> _items = List.generate(10, (index) => '项目 ${index + 1}');

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          padding: const EdgeInsets.all(16),
          color: Colors.grey[100],
          child: const Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Icon(Icons.info_outline, size: 16),
              SizedBox(width: 8),
              Text('长按拖动项目进行排序'),
            ],
          ),
        ),
        Expanded(
          child: ReorderableListView.builder(
            itemCount: _items.length,
            onReorder: (oldIndex, newIndex) {
              setState(() {
                if (newIndex > oldIndex) {
                  newIndex -= 1;
                }
                final item = _items.removeAt(oldIndex);
                _items.insert(newIndex, item);
              });
            },
            itemBuilder: (context, index) {
              return ListTile(
                key: ValueKey(_items[index]),
                title: Text(_items[index]),
                leading: CircleAvatar(
                  child: Text('${index + 1}'),
                ),
                trailing: const Icon(Icons.drag_handle),
              );
            },
          ),
        ),
      ],
    );
  }
}

class TaskListPage extends StatefulWidget {
  const TaskListPage({super.key});

  
  State<TaskListPage> createState() => _TaskListPageState();
}

class _TaskListPageState extends State<TaskListPage> {
  final List<Task> _tasks = [
    Task(id: '1', title: '完成项目报告', priority: 1, isCompleted: false),
    Task(id: '2', title: '团队会议', priority: 2, isCompleted: true),
    Task(id: '3', title: '代码审查', priority: 3, isCompleted: false),
    Task(id: '4', title: '更新文档', priority: 2, isCompleted: false),
    Task(id: '5', title: '客户演示', priority: 1, isCompleted: false),
    Task(id: '6', title: '学习新技术', priority: 3, isCompleted: false),
  ];

  Color _getPriorityColor(int priority) {
    switch (priority) {
      case 1:
        return Colors.red;
      case 2:
        return Colors.orange;
      case 3:
        return Colors.green;
      default:
        return Colors.grey;
    }
  }

  String _getPriorityLabel(int priority) {
    switch (priority) {
      case 1:
        return '高';
      case 2:
        return '中';
      case 3:
        return '低';
      default:
        return '';
    }
  }

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          padding: const EdgeInsets.all(16),
          color: Colors.grey[100],
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text('共 ${_tasks.length} 个任务'),
              TextButton.icon(
                onPressed: () {
                  setState(() {
                    _tasks.sort((a, b) => a.priority.compareTo(b.priority));
                  });
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('已按优先级排序')),
                  );
                },
                icon: const Icon(Icons.sort),
                label: const Text('按优先级排序'),
              ),
            ],
          ),
        ),
        Expanded(
          child: ReorderableListView.builder(
            itemCount: _tasks.length,
            onReorder: (oldIndex, newIndex) {
              setState(() {
                if (newIndex > oldIndex) {
                  newIndex -= 1;
                }
                final task = _tasks.removeAt(oldIndex);
                _tasks.insert(newIndex, task);
              });
            },
            itemBuilder: (context, index) {
              final task = _tasks[index];
              return Dismissible(
                key: ValueKey(task.id),
                direction: DismissDirection.endToStart,
                background: Container(
                  color: Colors.red,
                  alignment: Alignment.centerRight,
                  padding: const EdgeInsets.only(right: 16),
                  child: const Icon(Icons.delete, color: Colors.white),
                ),
                onDismissed: (direction) {
                  setState(() {
                    _tasks.removeAt(index);
                  });
                },
                child: Card(
                  key: ValueKey(task.id),
                  margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                  child: ListTile(
                    leading: Container(
                      width: 8,
                      height: 40,
                      decoration: BoxDecoration(
                        color: _getPriorityColor(task.priority),
                        borderRadius: BorderRadius.circular(4),
                      ),
                    ),
                    title: Text(
                      task.title,
                      style: TextStyle(
                        decoration: task.isCompleted ? TextDecoration.lineThrough : null,
                      ),
                    ),
                    subtitle: Text('优先级: ${_getPriorityLabel(task.priority)}'),
                    trailing: Row(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Checkbox(
                          value: task.isCompleted,
                          onChanged: (value) {
                            setState(() {
                              task.isCompleted = value ?? false;
                            });
                          },
                        ),
                        ReorderableDragStartListener(
                          index: index,
                          child: const Icon(Icons.drag_handle),
                        ),
                      ],
                    ),
                  ),
                ),
              );
            },
          ),
        ),
      ],
    );
  }
}

class Task {
  final String id;
  final String title;
  final int priority;
  bool isCompleted;

  Task({
    required this.id,
    required this.title,
    required this.priority,
    required this.isCompleted,
  });
}

class PlaylistPage extends StatefulWidget {
  const PlaylistPage({super.key});

  
  State<PlaylistPage> createState() => _PlaylistPageState();
}

class _PlaylistPageState extends State<PlaylistPage> {
  final List<Song> _songs = [
    Song(id: '1', title: '晴天', artist: '周杰伦', duration: '4:29'),
    Song(id: '2', title: '七里香', artist: '周杰伦', duration: '4:59'),
    Song(id: '3', title: '稻香', artist: '周杰伦', duration: '3:43'),
    Song(id: '4', title: '青花瓷', artist: '周杰伦', duration: '3:59'),
    Song(id: '5', title: '告白气球', artist: '周杰伦', duration: '3:36'),
    Song(id: '6', title: '夜曲', artist: '周杰伦', duration: '4:47'),
  ];

  int? _playingIndex;

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          padding: const EdgeInsets.all(16),
          color: Colors.blue[50],
          child: Row(
            children: [
              const Icon(Icons.music_note, color: Colors.blue),
              const SizedBox(width: 8),
              const Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text('我的播放列表', style: TextStyle(fontWeight: FontWeight.bold)),
                    Text('拖动调整播放顺序', style: TextStyle(fontSize: 12, color: Colors.grey)),
                  ],
                ),
              ),
              Text('${_songs.length} 首'),
            ],
          ),
        ),
        Expanded(
          child: ReorderableListView.builder(
            itemCount: _songs.length,
            onReorder: (oldIndex, newIndex) {
              setState(() {
                if (newIndex > oldIndex) {
                  newIndex -= 1;
                }
                final song = _songs.removeAt(oldIndex);
                _songs.insert(newIndex, song);
                if (_playingIndex != null) {
                  if (_playingIndex == oldIndex) {
                    _playingIndex = newIndex;
                  } else if (oldIndex < _playingIndex! && newIndex >= _playingIndex!) {
                    _playingIndex = _playingIndex! - 1;
                  } else if (oldIndex > _playingIndex! && newIndex <= _playingIndex!) {
                    _playingIndex = _playingIndex! + 1;
                  }
                }
              });
            },
            itemBuilder: (context, index) {
              final song = _songs[index];
              final isPlaying = _playingIndex == index;

              return Container(
                key: ValueKey(song.id),
                color: isPlaying ? Colors.blue[50] : null,
                child: ListTile(
                  leading: CircleAvatar(
                    backgroundColor: isPlaying ? Colors.blue : Colors.grey[200],
                    child: isPlaying
                        ? const Icon(Icons.play_arrow, color: Colors.white)
                        : Text('${index + 1}'),
                  ),
                  title: Text(
                    song.title,
                    style: TextStyle(
                      color: isPlaying ? Colors.blue : null,
                      fontWeight: isPlaying ? FontWeight.bold : FontWeight.normal,
                    ),
                  ),
                  subtitle: Text(song.artist),
                  trailing: Row(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      Text(song.duration, style: TextStyle(color: Colors.grey[600])),
                      const SizedBox(width: 8),
                      IconButton(
                        icon: Icon(
                          isPlaying ? Icons.pause : Icons.play_arrow,
                          color: isPlaying ? Colors.blue : Colors.grey,
                        ),
                        onPressed: () {
                          setState(() {
                            _playingIndex = isPlaying ? null : index;
                          });
                        },
                      ),
                      ReorderableDragStartListener(
                        index: index,
                        child: const Icon(Icons.drag_handle),
                      ),
                    ],
                  ),
                ),
              );
            },
          ),
        ),
      ],
    );
  }
}

class Song {
  final String id;
  final String title;
  final String artist;
  final String duration;

  Song({
    required this.id,
    required this.title,
    required this.artist,
    required this.duration,
  });
}

class FavoriteManagePage extends StatefulWidget {
  const FavoriteManagePage({super.key});

  
  State<FavoriteManagePage> createState() => _FavoriteManagePageState();
}

class _FavoriteManagePageState extends State<FavoriteManagePage> {
  final List<FavoriteItem> _favorites = [
    FavoriteItem(id: '1', name: '学习资料', count: 25, icon: Icons.school),
    FavoriteItem(id: '2', name: '工作文档', count: 18, icon: Icons.work),
    FavoriteItem(id: '3', name: '生活记录', count: 42, icon: Icons.favorite),
    FavoriteItem(id: '4', name: '旅行照片', count: 156, icon: Icons.photo),
    FavoriteItem(id: '5', name: '音乐收藏', count: 89, icon: Icons.music_note),
  ];

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          padding: const EdgeInsets.all(16),
          color: Colors.grey[100],
          child: Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text('共 ${_favorites.length} 个收藏夹'),
              TextButton.icon(
                onPressed: () {
                  setState(() {
                    _favorites.add(FavoriteItem(
                      id: DateTime.now().toString(),
                      name: '新收藏夹',
                      count: 0,
                      icon: Icons.folder,
                    ));
                  });
                },
                icon: const Icon(Icons.add),
                label: const Text('新建'),
              ),
            ],
          ),
        ),
        Expanded(
          child: ReorderableListView.builder(
            buildDefaultDragHandles: false,
            itemCount: _favorites.length,
            onReorder: (oldIndex, newIndex) {
              setState(() {
                if (newIndex > oldIndex) {
                  newIndex -= 1;
                }
                final item = _favorites.removeAt(oldIndex);
                _favorites.insert(newIndex, item);
              });
            },
            itemBuilder: (context, index) {
              final item = _favorites[index];
              return Card(
                key: ValueKey(item.id),
                margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                child: ListTile(
                  leading: Container(
                    width: 40,
                    height: 40,
                    decoration: BoxDecoration(
                      color: Colors.blue[50],
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: Icon(item.icon, color: Colors.blue),
                  ),
                  title: Text(item.name),
                  subtitle: Text('${item.count} 项'),
                  trailing: Row(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      IconButton(
                        icon: const Icon(Icons.edit, color: Colors.grey),
                        onPressed: () {
                          ScaffoldMessenger.of(context).showSnackBar(
                            SnackBar(content: Text('编辑: ${item.name}')),
                          );
                        },
                      ),
                      ReorderableDragStartListener(
                        index: index,
                        child: Container(
                          padding: const EdgeInsets.all(8),
                          child: const Icon(Icons.drag_handle),
                        ),
                      ),
                    ],
                  ),
                ),
              );
            },
          ),
        ),
      ],
    );
  }
}

class FavoriteItem {
  final String id;
  final String name;
  final int count;
  final IconData icon;

  FavoriteItem({
    required this.id,
    required this.name,
    required this.count,
    required this.icon,
  });
}

class CustomStylePage extends StatefulWidget {
  const CustomStylePage({super.key});

  
  State<CustomStylePage> createState() => _CustomStylePageState();
}

class _CustomStylePageState extends State<CustomStylePage> {
  final List<ColorItem> _items = [
    ColorItem(id: '1', name: '红色', color: Colors.red),
    ColorItem(id: '2', name: '蓝色', color: Colors.blue),
    ColorItem(id: '3', name: '绿色', color: Colors.green),
    ColorItem(id: '4', name: '橙色', color: Colors.orange),
    ColorItem(id: '5', name: '紫色', color: Colors.purple),
    ColorItem(id: '6', name: '青色', color: Colors.teal),
  ];

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          padding: const EdgeInsets.all(16),
          color: Colors.grey[100],
          child: const Text('自定义拖拽代理样式'),
        ),
        Expanded(
          child: ReorderableListView.builder(
            itemCount: _items.length,
            onReorder: (oldIndex, newIndex) {
              setState(() {
                if (newIndex > oldIndex) {
                  newIndex -= 1;
                }
                final item = _items.removeAt(oldIndex);
                _items.insert(newIndex, item);
              });
            },
            proxyDecorator: (child, index, animation) {
              return AnimatedBuilder(
                animation: animation,
                builder: (context, child) {
                  final animValue = Curves.easeInOut.transform(animation.value);
                  final elevation = 1 + animValue * 8;
                  final scale = 1 + animValue * 0.05;
                  return Transform.scale(
                    scale: scale,
                    child: Material(
                      elevation: elevation,
                      color: Colors.transparent,
                      borderRadius: BorderRadius.circular(12),
                      child: child,
                    ),
                  );
                },
                child: child,
              );
            },
            itemBuilder: (context, index) {
              final item = _items[index];
              return Container(
                key: ValueKey(item.id),
                margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                decoration: BoxDecoration(
                  color: item.color.withOpacity(0.2),
                  borderRadius: BorderRadius.circular(12),
                  border: Border.all(color: item.color, width: 2),
                ),
                child: ListTile(
                  leading: Container(
                    width: 32,
                    height: 32,
                    decoration: BoxDecoration(
                      color: item.color,
                      shape: BoxShape.circle,
                    ),
                  ),
                  title: Text(item.name),
                  trailing: const Icon(Icons.drag_handle),
                ),
              );
            },
          ),
        ),
      ],
    );
  }
}

class ColorItem {
  final String id;
  final String name;
  final Color color;

  ColorItem({
    required this.id,
    required this.name,
    required this.color,
  });
}

参考资料

Logo

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

更多推荐