初始化
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
/// 左滑露出操作按钮的列表项包装(如「我的关注」左滑取消关注)。
|
||||
///
|
||||
/// 不用 Dismissible:那个滑到底即执行,取关这种不可撤销的操作误触代价太大,
|
||||
/// 这里做成「左滑露出按钮、点按钮才执行」。
|
||||
class SwipeActionItem extends StatefulWidget {
|
||||
final Widget child;
|
||||
final String actionText;
|
||||
final VoidCallback onAction;
|
||||
final Color actionColor;
|
||||
final double actionWidth;
|
||||
|
||||
/// 滑动层的底色。列表项自身多半是透明的,不垫一层实色,底下的按钮会直接透上来
|
||||
final Color? backgroundColor;
|
||||
|
||||
const SwipeActionItem({
|
||||
super.key,
|
||||
required this.child,
|
||||
required this.actionText,
|
||||
required this.onAction,
|
||||
this.actionColor = const Color(0xffE03017),
|
||||
this.actionWidth = 88,
|
||||
this.backgroundColor,
|
||||
});
|
||||
|
||||
/// 关掉当前展开的那一项(列表滚动时调,避免滑走了还留着一个张开的)
|
||||
static void closeOpened() => _opened?._close();
|
||||
|
||||
@override
|
||||
State<SwipeActionItem> createState() => _SwipeActionItemState();
|
||||
}
|
||||
|
||||
/// 全局只允许一项展开:展开新的会自动收起旧的
|
||||
_SwipeActionItemState? _opened;
|
||||
|
||||
class _SwipeActionItemState extends State<SwipeActionItem> with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _ctr = AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
|
||||
bool get _isOpen => _ctr.value > 0;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (_opened == this) _opened = null;
|
||||
_ctr.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _open() {
|
||||
if (_opened != null && _opened != this) _opened!._close();
|
||||
_opened = this;
|
||||
_ctr.forward();
|
||||
}
|
||||
|
||||
void _close() {
|
||||
if (_opened == this) _opened = null;
|
||||
if (mounted) _ctr.reverse();
|
||||
}
|
||||
|
||||
void _onDragUpdate(DragUpdateDetails d) {
|
||||
// 手指往左走 primaryDelta 为负,换算成 0~1 的展开进度
|
||||
_ctr.value -= (d.primaryDelta ?? 0) / widget.actionWidth;
|
||||
}
|
||||
|
||||
void _onDragEnd(DragEndDetails d) {
|
||||
final v = d.primaryVelocity ?? 0;
|
||||
if (v < -300) return _open(); // 甩一下就展开,不看位置
|
||||
if (v > 300) return _close();
|
||||
_ctr.value > 0.5 ? _open() : _close();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onHorizontalDragUpdate: _onDragUpdate,
|
||||
onHorizontalDragEnd: _onDragEnd,
|
||||
child: AnimatedBuilder(
|
||||
animation: _ctr,
|
||||
builder: (_, child) {
|
||||
return Stack(
|
||||
children: [
|
||||
// 按钮垫在底下,靠内容左移露出来;跟着内容一起裁,避免收起时露边
|
||||
Positioned.fill(child: _actionButton()),
|
||||
Transform.translate(
|
||||
offset: Offset(-widget.actionWidth * _ctr.value, 0),
|
||||
child: ColoredBox(
|
||||
color: widget.backgroundColor ?? Get.theme.scaffoldBackgroundColor,
|
||||
// 展开状态下先吞掉一次点击用于收起,别直接把用户点进详情页
|
||||
child: _isOpen
|
||||
? GestureDetector(
|
||||
onTap: _close,
|
||||
child: AbsorbPointer(child: child),
|
||||
)
|
||||
: child,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _actionButton() {
|
||||
return Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
_close();
|
||||
widget.onAction();
|
||||
},
|
||||
child: Container(
|
||||
width: widget.actionWidth,
|
||||
alignment: Alignment.center,
|
||||
color: widget.actionColor,
|
||||
child: Text(
|
||||
widget.actionText,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.w500),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user