基础入门 Flutter for OpenHarmony:ReorderableListView 可排序列表详解
在移动应用开发中,拖拽排序是一种常见的交互模式。Flutter 提供了 ReorderableListView 组件,专门用于实现这种可拖拽排序的列表功能。child: Text('整个区域可拖拽'),),ReorderableListView 是 Flutter 中用于实现拖拽排序列表的组件,适合需要用户自定义排序的场景。ReorderableListView 的基本用法和核心概念onReord

欢迎加入开源鸿蒙跨平台社区: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 需要设置 children 和 onReorder 参数:
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:每个列表项必须有唯一的 keytrailing:通常添加拖拽手柄图标
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(构建器构造)
使用 itemBuilder 和 itemCount 参数构建列表项:
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 设置头部和尾部
通过 header 和 footer 参数添加头部和尾部:
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,
});
}
参考资料
更多推荐


所有评论(0)