Flutter for OpenHarmony 实战:实现滑动删除和左滑操作菜单
Flutter是Google开发的开源UI工具包,支持用一套代码构建iOS、Android、Web、Windows、macOS和Linux六大平台应用,实现"一次编写,多处运行"。OpenHarmony是由开放原子开源基金会运营的分布式操作系统,为全场景智能设备提供统一底座,具有多设备支持、模块化设计、分布式能力和开源开放等特性。Flutter for OpenHarmony技术方案使开发者能够:
前言
Flutter是Google开发的开源UI工具包,支持用一套代码构建iOS、Android、Web、Windows、macOS和Linux六大平台应用,实现"一次编写,多处运行"。
OpenHarmony是由开放原子开源基金会运营的分布式操作系统,为全场景智能设备提供统一底座,具有多设备支持、模块化设计、分布式能力和开源开放等特性。
Flutter for OpenHarmony技术方案使开发者能够:
- 复用Flutter现有代码(Skia渲染引擎、热重载、丰富组件库)
- 快速构建符合OpenHarmony规范的UI
- 降低多端开发成本
- 利用Dart生态插件资源加速生态建设
本文详细解析了一个完整的 Flutter 左右滑动操作应用的开发过程。这个应用展示了如何实现两种常见的滑动交互:左滑显示操作菜单和右滑删除,这两种交互在现代移动应用中非常常见。包含手势识别、动画控制、操作菜单、删除确认对话框、撤销功能等核心特性。使用 RepaintBoundary 优化性能,交互流畅自然。
先看效果
Flutte实现的 web端实时预览 完整效果

在鸿蒙 真机模拟器上成功运行后的效果

📋 目录
项目结构说明
应用入口
演示页面 (SwipeListPage)
SwipeableListItem 组件
SwipeActionMenu 组件
DismissibleListItem 组件
数据模型 (SwipeItemModel)
📁 项目结构说明
文件目录结构
lib/
├── main.dart # 应用入口文件
├── models/ # 数据模型目录
│ └── swipe_item_model.dart # 滑动项数据模型
├── pages/ # 页面目录
│ └── swipe_list_page.dart # 滑动操作列表页面
└── widgets/ # 组件目录
├── swipeable_list_item.dart # 左滑菜单列表项组件
├── swipe_action_menu.dart # 左滑操作菜单组件
└── dismissible_list_item.dart # 右滑删除列表项组件
文件说明
入口文件
lib/main.dart
- 应用入口点,包含
main()函数 - 定义
MyApp类,配置应用主题 - 设置应用标题为"滑动操作演示"
页面文件
lib/pages/swipe_list_page.dart
SwipeListPage类:滑动操作列表页面主类- 使用
TabController管理两个标签页 - 管理左滑菜单和右滑删除两个列表
- 实现删除和撤销功能
- 使用
组件文件
lib/widgets/swipeable_list_item.dart
SwipeableListItem组件:左滑菜单列表项组件- 实现左滑显示操作菜单
- 手势识别和动画控制
- 支持编辑、分享、删除操作
lib/widgets/swipe_action_menu.dart
SwipeAction类:操作菜单项数据模型SwipeActionMenu组件:左滑操作菜单组件- 显示多个操作按钮
- 支持自定义颜色和图标
lib/widgets/dismissible_list_item.dart
DismissibleListItem组件:右滑删除列表项组件- 使用
Dismissible实现滑动删除 - 显示删除确认对话框
- 显示删除背景提示
- 使用
数据模型
lib/models/swipe_item_model.dart
SwipeItem类:滑动项数据模型- 包含商品信息(标题、副标题、分类、价格、状态等)
SwipeItemGenerator类:数据生成器- 生成演示用的商品数据
组件依赖关系
main.dart
└── pages/swipe_list_page.dart (导入演示页面)
├── models/swipe_item_model.dart (导入数据模型)
├── widgets/swipeable_list_item.dart (导入左滑菜单组件)
└── widgets/dismissible_list_item.dart (导入右滑删除组件)
└── models/swipe_item_model.dart (导入数据模型)
└── widgets/swipe_action_menu.dart (导入操作菜单组件)
数据流向
- 数据生成:
SwipeItemGenerator.generateItems()生成商品数据 - 数据加载:
SwipeListPage加载数据到两个列表 - 列表渲染:
ListView.builder遍历数据,创建滑动列表项 - 手势识别:用户滑动时触发手势处理
- 操作反馈:点击操作按钮或确认删除后执行相应操作
- 撤销功能:删除后显示
SnackBar,支持撤销操作
应用入口
1. main() 函数
import 'package:flutter/material.dart';
import 'pages/swipe_list_page.dart';
void main() {
runApp(const MyApp());
}
应用入口,导入滑动列表页面。
2. MyApp 类 - 主题配置
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: '滑动操作演示',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.blue, // 蓝色主题
brightness: Brightness.light, // 浅色模式
),
useMaterial3: true,
),
home: const SwipeListPage(),
);
}
}
配置浅色主题,使用蓝色作为种子颜色。
演示页面 (SwipeListPage)
1. 类定义和状态管理
class SwipeListPage extends StatefulWidget {
const SwipeListPage({super.key});
State<SwipeListPage> createState() => _SwipeListPageState();
}
class _SwipeListPageState extends State<SwipeListPage>
with SingleTickerProviderStateMixin {
late TabController _tabController; // 标签控制器
List<SwipeItem> _swipeableItems = []; // 左滑菜单列表
List<SwipeItem> _dismissibleItems = []; // 右滑删除列表
SwipeItem? _lastDeletedItem; // 最后删除的项(用于撤销)
int? _lastDeletedIndex; // 最后删除的索引
SingleTickerProviderStateMixin 提供 TabController 所需的 vsync。维护两个列表和删除信息,用于撤销功能。
2. 数据加载
void _loadData() {
setState(() {
_swipeableItems = SwipeItemGenerator.generateItems(20);
_dismissibleItems = SwipeItemGenerator.generateItems(20);
});
}
void initState() {
super.initState();
_tabController = TabController(length: 2, vsync: this);
_loadData(); // 初始化时加载数据
}
void dispose() {
_tabController.dispose();
super.dispose();
}
_loadData() 生成并加载数据,initState() 中初始化 TabController 并加载数据。
3. 删除操作
void _deleteSwipeableItem(int index) {
setState(() {
_lastDeletedItem = _swipeableItems[index];
_lastDeletedIndex = index;
_swipeableItems.removeAt(index); // 从列表移除
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('已删除'),
duration: const Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
action: SnackBarAction(
label: '撤销',
textColor: Colors.white,
onPressed: () {
if (_lastDeletedItem != null && _lastDeletedIndex != null) {
setState(() {
_swipeableItems.insert(_lastDeletedIndex!, _lastDeletedItem!); // 恢复
_lastDeletedItem = null;
_lastDeletedIndex = null;
});
}
},
),
),
);
}
void _deleteDismissibleItem(String id) {
final index = _dismissibleItems.indexWhere((item) => item.id == id);
if (index != -1) {
setState(() {
_lastDeletedItem = _dismissibleItems[index];
_lastDeletedIndex = index;
_dismissibleItems.removeAt(index);
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('已删除'),
duration: const Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
action: SnackBarAction(
label: '撤销',
textColor: Colors.white,
onPressed: () {
if (_lastDeletedItem != null && _lastDeletedIndex != null) {
setState(() {
_dismissibleItems.insert(_lastDeletedIndex!, _lastDeletedItem!);
_lastDeletedItem = null;
_lastDeletedIndex = null;
});
}
},
),
),
);
}
}
删除操作保存被删除的项和索引,显示 SnackBar 提示,支持撤销恢复。
4. 页面布局结构
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey.shade50,
appBar: AppBar(
title: const Text(
'滑动操作演示',
style: TextStyle(
fontWeight: FontWeight.bold,
letterSpacing: -0.5,
),
),
centerTitle: true,
elevation: 0,
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Colors.white,
bottom: TabBar(
controller: _tabController,
indicatorColor: Colors.white,
indicatorWeight: 3,
indicatorSize: TabBarIndicatorSize.tab,
labelColor: Colors.white,
unselectedLabelColor: Colors.white70,
labelStyle: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 14,
),
tabs: const [
Tab(
icon: Icon(Icons.swipe_left, size: 20),
text: '左滑菜单',
),
Tab(
icon: Icon(Icons.delete_outline, size: 20),
text: '滑动删除',
),
],
),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
tooltip: '刷新',
onPressed: _loadData,
),
],
),
body: TabBarView(
controller: _tabController,
children: [
_buildSwipeableList(), // 左滑菜单列表
_buildDismissibleList(), // 右滑删除列表
],
),
);
}
页面使用 TabBar 和 TabBarView 实现两个标签页,分别显示左滑菜单和右滑删除列表。
5. 列表构建
Widget _buildSwipeableList() {
if (_swipeableItems.isEmpty) {
return _buildEmptyState('左滑显示操作菜单', Icons.swipe_left);
}
return ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 8),
itemCount: _swipeableItems.length,
cacheExtent: 500, // 预加载范围
itemBuilder: (context, index) {
return SwipeableListItem(
key: ValueKey(_swipeableItems[index].id),
item: _swipeableItems[index],
index: index,
onDelete: () => _deleteSwipeableItem(index),
onEdit: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('编辑: ${_swipeableItems[index].title}'),
duration: const Duration(milliseconds: 1500),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
);
},
onShare: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('分享: ${_swipeableItems[index].title}'),
duration: const Duration(milliseconds: 1500),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
);
},
);
},
);
}
Widget _buildDismissibleList() {
if (_dismissibleItems.isEmpty) {
return _buildEmptyState('向右滑动删除', Icons.swipe);
}
return ListView.builder(
padding: const EdgeInsets.symmetric(vertical: 8),
itemCount: _dismissibleItems.length,
cacheExtent: 500,
itemBuilder: (context, index) {
return DismissibleListItem(
item: _dismissibleItems[index],
index: index,
onDismissed: () => _deleteDismissibleItem(_dismissibleItems[index].id),
);
},
);
}
Widget _buildEmptyState(String message, IconData icon) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.05),
blurRadius: 20,
),
],
),
child: Icon(
icon,
size: 64,
color: Colors.grey.shade400,
),
),
const SizedBox(height: 24),
Text(
'暂无数据',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.grey.shade700,
),
),
const SizedBox(height: 8),
Text(
message,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade500,
),
),
const SizedBox(height: 32),
ElevatedButton.icon(
onPressed: _loadData,
icon: const Icon(Icons.refresh),
label: const Text('重新加载'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
),
],
),
);
}
两个列表使用 ListView.builder 构建,列表为空时显示空状态提示。使用 ValueKey 优化性能。
SwipeableListItem 组件
1. 类定义和动画
class SwipeableListItem extends StatefulWidget {
final SwipeItem item;
final int index;
final VoidCallback? onDelete;
final VoidCallback? onEdit;
final VoidCallback? onShare;
const SwipeableListItem({
super.key,
required this.item,
required this.index,
this.onDelete,
this.onEdit,
this.onShare,
});
State<SwipeableListItem> createState() => _SwipeableListItemState();
}
class _SwipeableListItemState extends State<SwipeableListItem>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _slideAnimation; // 滑动动画
double _dragOffset = 0.0; // 拖拽偏移量
bool _isDragging = false; // 是否正在拖拽
static const double _actionMenuWidth = 240.0; // 操作菜单宽度
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 250), // 250ms 动画
);
_slideAnimation = Tween<double>(
begin: 0.0,
end: -_actionMenuWidth, // 向左滑动,负值
).animate(CurvedAnimation(
parent: _controller,
curve: Curves.easeOut,
));
}
void dispose() {
_controller.dispose();
super.dispose();
}
SingleTickerProviderStateMixin 提供动画控制器所需的 vsync。_slideAnimation 控制内容向左滑动,最大偏移为操作菜单宽度。
2. 手势处理
void _onPanStart(DragStartDetails details) {
setState(() {
_isDragging = true; // 开始拖拽
});
}
void _onPanUpdate(DragUpdateDetails details) {
setState(() {
_dragOffset += details.delta.dx; // 累加水平偏移
// 限制滑动范围:不能超过菜单宽度,不能向右滑动
_dragOffset = math.max(-_actionMenuWidth, math.min(0, _dragOffset));
});
}
void _onPanEnd(DragEndDetails details) {
setState(() {
_isDragging = false; // 结束拖拽
});
final threshold = _actionMenuWidth * 0.4; // 40% 阈值
final velocity = details.velocity.pixelsPerSecond.dx; // 滑动速度
if (_dragOffset.abs() > threshold || velocity < -500) {
// 超过阈值或快速滑动:展开菜单
_controller.forward();
_dragOffset = -_actionMenuWidth;
} else {
// 未超过阈值:收起菜单
_controller.reverse();
_dragOffset = 0;
}
}
手势处理:_onPanStart 标记开始拖拽,_onPanUpdate 更新偏移并限制范围,_onPanEnd 根据阈值和速度决定展开或收起。
3. 菜单控制
void _closeMenu() {
if (_controller.isCompleted || _dragOffset < 0) {
_controller.reverse(); // 收起菜单
setState(() {
_dragOffset = 0;
});
}
}
_closeMenu() 收起菜单,在点击内容或操作按钮后调用。
4. 内容构建
Widget build(BuildContext context) {
return GestureDetector(
onTap: _closeMenu, // 点击收起菜单
onHorizontalDragStart: _onPanStart,
onHorizontalDragUpdate: _onPanUpdate,
onHorizontalDragEnd: _onPanEnd,
child: Stack(
clipBehavior: Clip.none,
children: [
// 背景操作菜单
Positioned.fill(
child: Align(
alignment: Alignment.centerRight,
child: SwipeActionMenu(
actions: [
SwipeAction(
label: '编辑',
icon: Icons.edit_outlined,
color: Colors.blue.shade400,
onTap: () {
_closeMenu();
widget.onEdit?.call();
},
),
SwipeAction(
label: '分享',
icon: Icons.share_outlined,
color: Colors.green.shade400,
onTap: () {
_closeMenu();
widget.onShare?.call();
},
),
SwipeAction(
label: '删除',
icon: Icons.delete_outline,
color: Colors.red.shade400,
onTap: () {
_closeMenu();
widget.onDelete?.call();
},
),
],
),
),
),
// 内容卡片
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
final offset = _isDragging ? _dragOffset : _slideAnimation.value;
return Transform.translate(
offset: Offset(offset, 0), // 水平平移
child: _buildContent(),
);
},
),
],
),
);
}
使用 Stack 布局,背景是操作菜单,前景是内容卡片。GestureDetector 处理手势,AnimatedBuilder 实现滑动动画。拖拽时使用 _dragOffset,动画时使用 _slideAnimation.value。
5. 内容卡片构建
Widget _buildContent() {
return RepaintBoundary(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.06),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
_closeMenu();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('点击了: ${widget.item.title}'),
duration: const Duration(milliseconds: 1500),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
);
},
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
_buildAvatar(), // 头像
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
widget.item.title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
letterSpacing: -0.3,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 8),
_buildStatusChip(), // 状态标签
],
),
const SizedBox(height: 6),
Text(
widget.item.subtitle,
style: TextStyle(
fontSize: 13,
color: Colors.grey.shade600,
letterSpacing: -0.2,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 10),
Row(
children: [
_buildCategoryTag(), // 分类标签
const Spacer(),
Text(
'¥${widget.item.price.toStringAsFixed(2)}',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
letterSpacing: -0.5,
),
),
],
),
],
),
),
const SizedBox(width: 8),
Icon(
Icons.chevron_left,
color: Colors.grey.shade300,
size: 24,
),
],
),
),
),
),
),
);
}
内容卡片使用 RepaintBoundary 优化性能,包含头像、标题、副标题、状态标签、分类标签和价格。
SwipeActionMenu 组件
1. SwipeAction 类
class SwipeAction {
final String label; // 标签文字
final IconData icon; // 图标
final Color color; // 颜色
final VoidCallback onTap; // 点击回调
const SwipeAction({
required this.label,
required this.icon,
required this.color,
required this.onTap,
});
}
操作菜单项数据模型,包含标签、图标、颜色和回调。
2. SwipeActionMenu 组件
class SwipeActionMenu extends StatelessWidget {
final List<SwipeAction> actions;
final double width;
const SwipeActionMenu({
super.key,
required this.actions,
this.width = 80, // 每个按钮宽度
});
Widget build(BuildContext context) {
return Container(
width: width * actions.length, // 总宽度 = 按钮数 × 按钮宽度
decoration: BoxDecoration(
color: Colors.grey.shade900, // 深色背景
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: actions.asMap().entries.map((entry) {
final index = entry.key;
final action = entry.value;
return _buildActionButton(action, index);
}).toList(),
),
);
}
Widget _buildActionButton(SwipeAction action, int index) {
return Expanded(
child: Container(
decoration: BoxDecoration(
border: Border(
left: index > 0
? BorderSide(color: Colors.grey.shade800, width: 0.5)
: BorderSide.none, // 第一个按钮无左边框
),
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: action.onTap,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 44,
height: 44,
decoration: BoxDecoration(
color: action.color.withValues(alpha: 0.15), // 半透明背景
shape: BoxShape.circle,
),
child: Icon(
action.icon,
color: action.color,
size: 22,
),
),
const SizedBox(height: 6),
Text(
action.label,
style: TextStyle(
color: action.color,
fontSize: 11,
fontWeight: FontWeight.w500,
),
),
],
),
),
),
),
),
);
}
}
操作菜单使用 Row 水平排列按钮,每个按钮包含圆形图标和文字标签。使用 InkWell 提供点击反馈。
DismissibleListItem 组件
1. 类定义和布局
class DismissibleListItem extends StatelessWidget {
final SwipeItem item;
final int index;
final VoidCallback onDismissed;
final VoidCallback? onTap;
const DismissibleListItem({
super.key,
required this.item,
required this.index,
required this.onDismissed,
this.onTap,
});
Widget build(BuildContext context) {
return Dismissible(
key: ValueKey(item.id),
direction: DismissDirection.endToStart, // 只能从右向左滑动
background: _buildDismissBackground(), // 删除背景
secondaryBackground: _buildDismissBackground(),
confirmDismiss: (direction) async {
return await _showDeleteConfirmDialog(context); // 显示确认对话框
},
onDismissed: (direction) {
onDismissed(); // 确认后执行删除
},
child: _buildContent(context),
);
}
}
使用 Dismissible 实现滑动删除。direction: endToStart 限制只能从右向左滑动。confirmDismiss 显示确认对话框。
2. 删除确认对话框
Future<bool?> _showDeleteConfirmDialog(BuildContext context) {
return showDialog<bool>(
context: context,
barrierDismissible: true,
builder: (context) => AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
title: Row(
children: [
Icon(
Icons.warning_amber_rounded,
color: Colors.orange.shade400,
size: 28,
),
const SizedBox(width: 12),
const Text(
'确认删除',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
],
),
content: Text(
'确定要删除 "${item.title}" 吗?',
style: TextStyle(fontSize: 15, color: Colors.grey.shade700),
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false), // 取消
style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
child: Text(
'取消',
style: TextStyle(color: Colors.grey.shade600),
),
),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(true), // 确认
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red.shade400,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text('删除'),
),
],
),
);
}
删除确认对话框使用 AlertDialog,包含警告图标、提示文字和取消/确认按钮。返回 true 表示确认删除。
3. 删除背景
Widget _buildDismissBackground() {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Colors.red.shade400,
Colors.red.shade500,
],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
borderRadius: BorderRadius.circular(16),
),
alignment: Alignment.centerRight,
padding: const EdgeInsets.only(right: 24),
child: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.delete_outline,
color: Colors.white,
size: 32,
),
SizedBox(height: 8),
Text(
'删除',
style: TextStyle(
color: Colors.white,
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
],
),
);
}
删除背景使用红色渐变,显示删除图标和文字,提示用户滑动删除操作。
4. 内容构建
Widget _buildContent(BuildContext context) {
return RepaintBoundary(
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.06),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: () {
onTap?.call();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('点击了: ${item.title}'),
duration: const Duration(milliseconds: 1500),
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
);
},
borderRadius: BorderRadius.circular(16),
child: Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
_buildAvatar(context),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
item.title,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
letterSpacing: -0.3,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: 8),
_buildStatusChip(),
],
),
const SizedBox(height: 6),
Text(
item.subtitle,
style: TextStyle(
fontSize: 13,
color: Colors.grey.shade600,
letterSpacing: -0.2,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 10),
Row(
children: [
_buildCategoryTag(context),
const Spacer(),
Text(
'¥${item.price.toStringAsFixed(2)}',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
letterSpacing: -0.5,
),
),
],
),
],
),
),
const SizedBox(width: 8),
Icon(
Icons.swipe_left, // 提示可以滑动
color: Colors.grey.shade300,
size: 24,
),
],
),
),
),
),
),
);
}
内容卡片与左滑菜单类似,但右侧显示滑动图标提示。使用 RepaintBoundary 优化性能。
数据模型 (SwipeItemModel)
1. SwipeItem 类
class SwipeItem {
final String id; // 唯一标识
final String title; // 标题
final String subtitle; // 副标题
final String category; // 分类
final double price; // 价格
final String status; // 状态
final String avatar; // 头像(emoji)
final DateTime createTime; // 创建时间
SwipeItem({
required this.id,
required this.title,
required this.subtitle,
required this.category,
required this.price,
required this.status,
required this.avatar,
required this.createTime,
});
SwipeItem copyWith({
String? id,
String? title,
String? subtitle,
String? category,
double? price,
String? status,
String? avatar,
DateTime? createTime,
}) {
return SwipeItem(
id: id ?? this.id,
title: title ?? this.title,
subtitle: subtitle ?? this.subtitle,
category: category ?? this.category,
price: price ?? this.price,
status: status ?? this.status,
avatar: avatar ?? this.avatar,
createTime: createTime ?? this.createTime,
);
}
}
滑动项数据模型,包含商品信息。copyWith 方法用于创建副本并修改部分属性。
2. SwipeItemGenerator 类
class SwipeItemGenerator {
static List<SwipeItem> generateItems(int count, {int startId = 0}) {
final categories = ['电子产品', '服装', '食品', '图书', '家居', '运动', '美妆', '数码'];
final titles = [
'iPhone 15 Pro Max',
'MacBook Pro M3',
'AirPods Pro',
'iPad Air',
'Apple Watch',
'Nike运动鞋',
'Adidas外套',
'Uniqlo T恤',
'星巴克咖啡',
'肯德基套餐',
];
final subtitles = [
'最新款旗舰手机',
'强大的M3芯片',
'主动降噪耳机',
'轻薄便携平板',
'健康监测手表',
'舒适透气',
'时尚潮流',
'简约百搭',
'香浓醇厚',
'美味可口',
];
final statuses = ['在售', '热销', '新品', '限时', '预售'];
return List.generate(count, (index) {
final id = startId + index;
return SwipeItem(
id: 'item_$id',
title: titles[id % titles.length],
subtitle: subtitles[id % subtitles.length],
category: categories[id % categories.length],
price: 99.99 + (id * 10.5),
status: statuses[id % statuses.length],
avatar: String.fromCharCode(65 + (id % 26)), // A-Z
createTime: DateTime.now().subtract(Duration(days: id % 30)),
);
});
}
}
数据生成器创建演示数据。生成指定数量的商品,使用模运算循环使用标题、分类等数据。
使用示例
在页面中使用滑动操作
class MyPage extends StatefulWidget {
State<MyPage> createState() => _MyPageState();
}
class _MyPageState extends State<MyPage> {
List<SwipeItem> _items = [];
void initState() {
super.initState();
_items = SwipeItemGenerator.generateItems(10);
}
void _deleteItem(int index) {
setState(() {
_items.removeAt(index);
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('滑动操作')),
body: ListView.builder(
itemCount: _items.length,
itemBuilder: (context, index) {
// 左滑菜单
return SwipeableListItem(
key: ValueKey(_items[index].id),
item: _items[index],
index: index,
onDelete: () => _deleteItem(index),
onEdit: () {
print('编辑: ${_items[index].title}');
},
onShare: () {
print('分享: ${_items[index].title}');
},
);
// 或右滑删除
// return DismissibleListItem(
// item: _items[index],
// index: index,
// onDismissed: () => _deleteItem(index),
// );
},
),
);
}
}
使用步骤:
- 准备数据(使用
SwipeItemGenerator或自定义数据) - 使用
ListView.builder遍历数据 - 为每个项创建
SwipeableListItem(左滑菜单)或DismissibleListItem(右滑删除) - 实现相应的回调函数(删除、编辑、分享等)
欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)