前言
侧滑删除控件常用于ListView或者RecyclerView中,侧滑删除当前条目;很早就有自定义侧滑删除控件了,只是之前直接拿来别人的直接来使用,没有太注意其中的具体的实现细节;通过深入理解View的事件传递及分发机制之后,自己动手加深相关理解,特意自己来实现相关的细节;
知识准备:
View的scrollBy()、scrollTo()方法:
scrollBy() 是让View相对于当前的位置滚动某段距离,而scrollTo() 则是让View相对于初始的位置滚动某段距离。不管是scrollTo()还是scrollBy()方法,滚动的都是该View内部的内容;滚动效果是跳跃式的,没有任何平滑滚动的效果。
OverScroller或者Scroller类:用来收集生成滚动动画所需的数据;例如,响应于拖动手势。用于改变View的滑动距离;computeScrollOffset()方法:返回true,表示动画还没有完成,可以获取最新的位置;false表示动画已经完成了。类似于属性动画,可以用来实现回弹,滑动等效果。
- 初始化 scroller = new OverScroller(getContext());
- 调用scroller.startScroll();
- 重写computeScroll()方法,调用scroller.computeScrollOffset()判断动画是否完成;
具体实现过程
继承LinearLayout类
public class SideSlipDeleteView extends LinearLayout {
private void initView() {
setOrientation(HORIZONTAL);
scroller = new OverScroller(getContext());
//系统规定的滑动的最小距离;
touchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
}
}
重写onInterceptTouchEvent()方法:在左滑和右滑的时候拦截事件;其他时候不拦截事件;
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = ev.getX();
lastY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
//disX>0 右滑;disX<0 左滑;
float disX = ev.getX() - lastX;
float disY = ev.getY() - lastY;
//判断是X轴滑动还是Y轴滑动;X轴滑动就拦截;
//Logger.e("disX::" + disX + ",,disY::" + disY + ",,dix::" + (disX - disY) + ",,,getScrollX()::" + getScrollX());
//左滑
if (Math.abs(disX) - Math.abs(disY) > touchSlop && disX < 0 && getScrollX() < getRightWidth()) {
return true;
}
//右滑
if (Math.abs(disX) - Math.abs(disY) > touchSlop && disX > 0 && getScrollX() > 0) {
//Logger.e("onInterceptTouchEvent action::" + ev.getAction());
return true;
}
break;
}
return super.onInterceptTouchEvent(ev);
}
onTouchEvent():如果事件传递到SideSlipDeleteView;onTouchEvent()返回true,表示消费该事件;否则的话ListView的onTouchEvent()方法会返回True,move和up就不会再往SideSlipDeleteView继续传递了。
但是SideSlipDeleteView的onTouchEvent()返回true,ListView的条目点击事件会被拦截掉;并且点击子View及自身的setClickListener()的onClick()也会被拦截掉,需要在UP中调用performClick()方法;
//onTouchEvent 返回true 表示消费该事件;
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = ev.getX();
lastY = ev.getY();
lastDownX = lastX;
if (listener != null) {
listener.slipDeleteMove(this);
}
break;
case MotionEvent.ACTION_MOVE:
//disX>0 右滑;disX<0 左滑;
float disX = ev.getX() - lastX;
float disY = ev.getY() - lastY;
lastX = ev.getX();
lastY = ev.getY();
//左滑
if (Math.abs(disX) - Math.abs(disY) > touchSlop && disX < 0 && getScrollX() < getRightWidth()) {
if (getScrollX() + (-disX) > getRightWidth()) {//临界值
//正值向左移动,负值向右移动
scrollBy(getRightWidth() - getScrollX(), 0);
} else {
scrollBy(-(int) disX, 0);
}
}
//右滑
if (Math.abs(disX) - Math.abs(disY) > touchSlop && disX > 0 && getScrollX() > 0) {
if (getScrollX() - disX < 0) {
scrollBy(getScrollX(), 0);
} else {
scrollBy(-(int) disX, 0);
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
float upX = ev.getX();
//左滑
if (upX - lastDownX < 0) {
slipLeft();
}
//右滑
if (upX - lastDownX > 0) {
slipIn();
}
//避免滑动事件和点击事件冲突;
if (Math.abs(upX - lastDownX) < touchSlop && Math.abs(upX - lastDownX) < touchSlop) {
//执行setOnClickListener()方法;
Logger.e("调用performClick()");
performClick();
}
break;
}
return true;
}
自定义监听事件:确保只有一个Item
public interface OnSlipDeleteStateChangeListener {
//用于完全显示删除
void slipDeleteOpen(SideSlipDeleteView sideSlipDeleteView);
//用于完全隐藏
void slipDeleteClose(SideSlipDeleteView sideSlipDeleteView);
//用在onTouchEvent的down中;
void slipDeleteMove(SideSlipDeleteView sideSlipDeleteView);
}
OnSlipDeleteStateChangeListener listener;
public void setSlipDeleteStateChangeListener(OnSlipDeleteStateChangeListener listener) {
this.listener = listener;
}
在Adapter中设置监听
viewHolder.sideSlipDeleteView.setSlipDeleteStateChangeListener(new SideSlipDeleteView.OnSlipDeleteStateChangeListener() {
@Override
public void slipDeleteOpen(SideSlipDeleteView sideSlipDeleteView) {
mSideSlipDeleteView = sideSlipDeleteView;
}
@Override
public void slipDeleteClose(SideSlipDeleteView sideSlipDeleteView) {
if (mSideSlipDeleteView == sideSlipDeleteView) {
mSideSlipDeleteView = null;
}
}
@Override
public void slipDeleteMove(SideSlipDeleteView sideSlipDeleteView) {
if (mSideSlipDeleteView != null && mSideSlipDeleteView != sideSlipDeleteView) {
mSideSlipDeleteView.closeSlipDelete();
}
}
});
实现过程问题总结
-
问题一:侧滑删除 放在ListView中,为什么只接受了Down,Move和UP 都没有接收到??
因为AbsTouchEvent的onTouchEvent返回true,如果down事件不被SlipDeleteView消费,就会被AbsListView的OnTouchEvent()消费,Move,Up都会直接分发到AbsListView;不会再往传递了;所有SlipDeleteView的onTouchEvent()返回true;
-
问题二:但是如果在SlipDeleteView的OnTouchEvent()直接返回true,ListView的条目点击事件就会被没有了;该如何解决???
只能用SlipDeleteView的点击事件替换ListView的条目点击事件;
-
问题三:SlipDeleteView的点击事件冲突??
那就在Up中调用performClick(),会调用setOnClickListener()监听;
-
问题四:如何只滑出一个Item??
move 将上一个已经滑出的Item时,关闭;Open的时候,记录当前的SlipDeleteView;关闭的时候,将其制null;
自定义侧滑删除控件可以单独使用或者放在ListView,RecyclerView中使用;以上就是具体的实现过程,如有问题,请多指教,谢谢!!