Flutter的ListView左滑出现删除按钮。
如同iOS原生ListView的删除风格,列表项左滑,右侧出现删除按钮。这里用了2个dart文件,实现这个功能。
(1) left_slide_actions.dart文件。
import 'package:flutter/material.dart';
typedef _BaseFunction<T> = void Function(T o);
/// 来源: https://blog.csdn.net/zhuowalun8427/article/details/121285947 。
class LeftSlideActions extends StatefulWidget {
final double actionsWidth;
final List<Widget> actions;
final Widget child;
final Decoration? decoration;
final VoidCallback? actionsWillShow;
final _BaseFunction<VoidCallback>? exportHideActions;
const LeftSlideActions({
Key? key,
required this.actionsWidth,
required this.actions,
required this.child,
this.decoration,
this.actionsWillShow,
this.exportHideActions,
}) : super(key: key);
@override
_LeftSlideActionsState createState() => _LeftSlideActionsState();
}
class _LeftSlideActionsState extends State<LeftSlideActions>
with TickerProviderStateMixin {
double _translateX = 0;
late AnimationController _controller;
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
_controller = AnimationController(
lowerBound: -widget.actionsWidth,
upperBound: 0,
vsync: this,
duration: const Duration(milliseconds: 300),
)..addListener(() {
_translateX = _controller.value;
setState(() {});
});
if (widget.exportHideActions != null) {
widget.exportHideActions!(_hide);
}
}
@override
Widget build(BuildContext context) {
return Container(
decoration: widget.decoration,
clipBehavior: Clip.hardEdge,
child: Stack(
children: [
Positioned.fill(
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: widget.actions,
),
),
GestureDetector(
onHorizontalDragUpdate: (v) {
_onHorizontalDragUpdate(v);
},
onHorizontalDragEnd: (v) {
_onHorizontalDragEnd(v);
},
child: Transform.translate(
offset: Offset(_translateX, 0),
child: Row(
children: [
Expanded(flex: 1, child: widget.child),
],
),
),
),
],
),
);
}
void _onHorizontalDragUpdate(DragUpdateDetails details) {
_translateX =
(_translateX + details.delta.dx).clamp(-widget.actionsWidth, 0.0);
setState(() {});
}
void _onHorizontalDragEnd(DragEndDetails details) {
_controller.value = _translateX;
if (details.velocity.pixelsPerSecond.dx > 200) {
_hide();
} else if (details.velocity.pixelsPerSecond.dx < -200) {
_show();
} else {
if (_translateX.abs() > widget.actionsWidth / 2) {
_show();
} else {
_hide();
}
}
}
void _show() {
if (widget.actionsWillShow != null) {
widget.actionsWillShow!();
}
if (_translateX != -widget.actionsWidth) {
_controller.animateTo(-widget.actionsWidth);
}
}
void _hide() {
if (_translateX != 0) {
_controller.animateTo(0);
}
}
}
(2)main.dart文件。
import 'package:flutter/material.dart';
import 'left_slide_actions.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
static const int _itemNum = 20;
final List<String> _itemTextList = [];
final Map<String, VoidCallback> _mapForHideActions = {};
@override
void initState() {
super.initState();
for (int i = 1; i <= _itemNum; i++) {
_itemTextList.add(i.toString());
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('ListView左滑出现删除按钮'),
//backgroundColor: Colors.pink,
),
body: SafeArea(
child: ListView.separated(
scrollDirection: Axis.vertical,
padding: const EdgeInsets.fromLTRB(12, 20, 12, 30),
itemCount: _itemTextList.length,
itemBuilder: (BuildContext context, int index) {
if (index < _itemTextList.length) {
final String tempStr = _itemTextList[index];
return LeftSlideActions(
key: Key(tempStr),
actionsWidth: 60,
actions: [
_buildDeleteBtn(index),
],
child: _buildListItem(index),
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(5)),
),
actionsWillShow: () {
// 隐藏其他列表项的行为。
for (int i = 0; i < _itemTextList.length; i++) {
if (index == i) {
continue;
}
String tempKey = _itemTextList[i];
VoidCallback? hideActions = _mapForHideActions[tempKey];
if (hideActions != null) {
hideActions();
}
}
},
exportHideActions: (hideActions) {
_mapForHideActions[tempStr] = hideActions;
},
);
}
return const SizedBox.shrink();
},
separatorBuilder: (BuildContext context, int index) {
return const SizedBox(height: 10);
},
// 添加下面这句 内容未充满的时候也可以滚动。
physics: const AlwaysScrollableScrollPhysics(),
// 添加下面这句 是为了GridView的高度自适应, 否则GridView需要包裹在固定宽高的容器中。
//shrinkWrap: true,
),
),
);
}
Widget _buildListItem(final int index) {
return Container(
height: 50,
padding: const EdgeInsets.symmetric(horizontal: 10),
alignment: Alignment.centerLeft,
decoration: const BoxDecoration(
color: Color(0xFFEEEEEE),
boxShadow: [
BoxShadow(
// 阴影颜色。
color: Color(0x66EBEBEB),
// 阴影xy轴偏移量。
offset: Offset(0.0, 0.0),
// 阴影模糊程度。
blurRadius: 6.0,
// 阴影扩散程度。
spreadRadius: 4.0,
),
],
),
child: Text(
('行内容---' + _itemTextList[index]),
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
color: Color(0xFF333333),
height: 1,
),
),
);
}
Widget _buildDeleteBtn(final int index) {
return GestureDetector(
onTap: () {
// 省略: 弹出是否删除的确认对话框。
setState(() {
_itemTextList.removeAt(index);
});
},
child: Container(
width: 60,
color: const Color(0xFFF20101),
alignment: Alignment.center,
child: const Text(
'删除',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.white,
height: 1,
),
),
),
);
}
}