有一段时间没写文章了,第一是工作比较忙,第二是私事也比较多毕竟再过2年就而立之年了,然后去年主要是做RN+Android +技术调研。今年开始重心放在Flutter多端实现上,希望能做好吧。
一个可以做滑动删除等常用交互的一个控件(有时候写写改源码 有些时候读别人实现 有时候自己搭轮子 一直在做 总希望大家在需要的时候能有用吧)
一个比较纯粹的项目,没广告,也没乱七八糟的依赖,不会对使用者的依赖链路造成困扰。
LeftScroll(
buttonWidth: 80,
child: Container(
height: 60,
color: Colors.white,
alignment: Alignment.center,
child: Text(' Try Scroll Left'),
),
buttons: <Widget>[
LeftScrollItem(
text: 'delete',
color: Colors.red,
onTap: () {
print('delete');
},
),
LeftScrollItem(
text: 'Edit',
color: Colors.orange,
onTap: () {
print('edit');
},
),
],
onTap: () {
print('tap row');
},
)
使用起来也很简单 一个buttons属性关联着几个LeftScrollItem,这个自定义控件可以实现我们滑动后出发的逻辑
疑问1:普通按钮行不行?
疑问2: 层级是如何实现的?
class LeftScrollListItem {
final String key;
final Widget child;
final List<Widget> buttons;
final Function onTap;
LeftScrollListItem({
@required this.key,
@required this.child,
@required this.buttons,
@required this.onTap,
});
}
LeftScrollListItem 的定义其实有点出乎意料,他不是一个Widget对象,也没做啥特殊的处理,更像是结构化子控件的工具。
200多行 我们一段段来分析他
构造函数必传子控件,处理按钮,和一堆回调,比较重要和需要注意的是closeOnPop
这个属性,这是失去焦点或者其他交互关闭滑动的一个判断flag,1.20后才支持。
LeftScroll({
this.key,
@required this.child,
@required this.buttons,
this.onSlideStarted,
this.onSlideCompleted,
this.onSlideCanceled,
this.onTap,
this.buttonWidth: 80.0,
this.onScroll,
this.closeOnPop: true,
}) : super(key: key);
@override
void initState() {
super.initState();
maxDragDistance = widget.buttonWidth * widget.buttons.length;//根据宽度计算滑动距离
//处理水平拖拽事件
gestures[HorizontalDragGestureRecognizer] =
GestureRecognizerFactoryWithHandlers<HorizontalDragGestureRecognizer>(
() => HorizontalDragGestureRecognizer(debugOwner: this),
(HorizontalDragGestureRecognizer instance) {
instance
..onDown = onHorizontalDragDown //开始拖拽触发
..onUpdate = onHorizontalDragUpdate//更新进度
..onEnd = onHorizontalDragEnd; //拖住结束触发
},
);
//处理点击事件
gestures[TapGestureRecognizer] =
GestureRecognizerFactoryWithHandlers<TapGestureRecognizer>(
() => TapGestureRecognizer(debugOwner: this),
(TapGestureRecognizer instance) {
instance..onTap = widget.onTap;//回传给外部传入的点击事件
},
);
//根据动画进度刷新状态
animationController = AnimationController(
lowerBound: -maxDragDistance,
upperBound: 0,
vsync: this,
duration: Duration(milliseconds: 300))
..addListener(() {
translateX = animationController.value;
setState(() {});
});
}
@override
Widget build(BuildContext context) {
......
return widget.closeOnPop
? WillPopScope(
child: body,
onWillPop: () async {
if (translateX != 0) {
close();
return false;
}
return true;
})
: body;
}
挂载的是一个接受异步方法的onWillPop
当动画结束后,才会返回ture,使逻辑有效
几个逻辑方法
void onHorizontalDragDown(DragDownDetails details) {
//水平滑动开始回调外部传入的方法
if (widget.onSlideStarted != null) widget.onSlideStarted.call();
}
void onHorizontalDragUpdate(DragUpdateDetails details) {
translateX = (translateX + details.delta.dx).clamp(-maxDragDistance, 0.0);
if (widget.onScroll != null) {
//回传进度
widget.onScroll.call(
translateX / maxDragDistance * -1,
);
}
setState(() {});
}
void onHorizontalDragEnd(DragEndDetails details) {
animationController.value = translateX;
//收回来
if (details.velocity.pixelsPerSecond.dx > 200) {
close();
} else if (details.velocity.pixelsPerSecond.dx < -200) {
//滑动距离超过200(拉过阈值),张开
open();
} else {
//超过一半就开
if (translateX.abs() > maxDragDistance / 2) {
open();
} else {
close();
}
}
}
//张开按钮宽度
void open() {
if (translateX != -maxDragDistance)
animationController.animateTo(-maxDragDistance).then((_) {
if (widget.onSlideCompleted != null) widget.onSlideCompleted.call();
});
}
//闭合到0
void close() {
if (translateX != 0)
animationController.animateTo(0).then((_) {
if (widget.onSlideCanceled != null) widget.onSlideCanceled.call();
});
}
源码地址:https://github.com/mjl0602/left-scroll-actions