事件分发之现学现用

简介

在公司做的第一个需求就是给购物车列表加上侧滑删除(真想吐槽这交互),我说可以引入代码家的库,然后他们说项目里有一个了。打开一看,居然是鲍老师的SwipeMenuListView(已经不维护了),而且引入后就一直没更新,满足不了现在的需求,又有业务冲突,正好那段时间看了开发艺术与探索中的事件分发章节,发现只需要在现有的基础上做一些拦截事件就能满足业务了。

问题与需求

  1. Item的有效滑动区域只有空白区域,也就是说如果item上有别的控件,那么就不能滑动
  2. 如果列表有header,滑动区域会出错
  3. 列表里的滑动删除,只有一个能打开

解决方法

问题1、3的解决思路:

1:引起的原因是在down时,down事件被子view获取了。那么解决方案就出来了,在父布局直接判断是否是在横向移动,并且拦截就可以了。
3:在每次down的时候判断一下当前是否有open状态
在onInterceptTouchEvent中处理判断是否需要拦截

onInterceptTouchEvent 的down事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
mDownX = ev.getX();
mDownY = ev.getY();
boolean handled = super.onInterceptTouchEvent(ev);
mTouchState = TOUCH_STATE_NONE;
mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY());
View view = getChildAt(mTouchPosition - getFirstVisiblePosition());
//只在空的时候赋值 以免每次触摸都赋值,会有多个open状态
if (view instanceof SwipeMenuLayout) {
//如果有打开了 就拦截.
if (mTouchView != null && mTouchView.isOpen() && !inRangeOfView(mTouchView.getMenuView(), ev)) {
return true;
}
mTouchView = (SwipeMenuLayout) view;
mTouchView.setSwipeDirection(mDirection);
}
//如果摸在另外个view
if (mTouchView != null && mTouchView.isOpen() && view != mTouchView) {
handled = true;
}
if (mTouchView != null) {
mTouchView.onSwipe(ev);
}
return handled;
......
}
}

在down的时候判断之前是否有一个swiplayout打开的,如果有,就拦截操作。从而解决了问题3。

这里return true后,会由onTouchEvent的MotionEvent.ACTION_DOWN执行,事件流的传递过程就不在这里赘述了。

onInterceptTouchEvent 的move事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action) {
.......
case MotionEvent.ACTION_MOVE:
float dy = Math.abs((ev.getY() - mDownY));
float dx = Math.abs((ev.getX() - mDownX));
if (Math.abs(dy) > MAX_Y || Math.abs(dx) > MAX_X) {
//每次拦截的down都把触摸状态设置成了TOUCH_STATE_NONE 只有返回true才会走onTouchEvent 所以写在这里就够了
if (mTouchState == TOUCH_STATE_NONE) {
if (Math.abs(dy) > MAX_Y) {
mTouchState = TOUCH_STATE_Y;
} else if (dx > MAX_X) {
mTouchState = TOUCH_STATE_X;
if (mOnSwipeListener != null) {
mOnSwipeListener.onSwipeStart(mTouchPosition);
}
}
}
return true;
}
}
}

就是在移动距离超过一个常量值,就可以判断在移动,并拦截。1的问题也得到了解决。

这里return true后,会由onTouchEvent的MotionEvent.ACTION_MOVE执行

2的问题,只需要在onTouchEvent的move事件里减去header就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (ev.getAction() != MotionEvent.ACTION_DOWN && mTouchView == null)
return super.onTouchEvent(ev);
int action = ev.getAction();
switch (action) {
......
case MotionEvent.ACTION_MOVE:
//有些可能有header,要减去header再判断
mTouchPosition = pointToPosition((int) ev.getX(), (int) ev.getY()) - getHeaderViewsCount();
//如果滑动了一下没完全展现,就收回去,这时候mTouchView已经赋值,再滑动另外一个不可以swip的view
//会导致mTouchView swip 。 所以要用位置判断是否滑动的是一个view
if (!mTouchView.getSwipEnable() || mTouchPosition != mTouchView.getPosition()) {
break;
}
float dy = Math.abs((ev.getY() - mDownY));
float dx = Math.abs((ev.getX() - mDownX));
if (mTouchState == TOUCH_STATE_X) {
if (mTouchView != null) {
mTouchView.onSwipe(ev);
}
getSelector().setState(new int[]{0});
ev.setAction(MotionEvent.ACTION_CANCEL);
super.onTouchEvent(ev);
return true;
} else if (mTouchState == TOUCH_STATE_NONE) {
if (Math.abs(dy) > MAX_Y) {
mTouchState = TOUCH_STATE_Y;
} else if (dx > MAX_X) {
mTouchState = TOUCH_STATE_X;
if (mOnSwipeListener != null) {
mOnSwipeListener.onSwipeStart(mTouchPosition);
}
}
}
break;
.......
return super.onTouchEvent(ev);
}

其实只要理解了事件流的传递过程,那么需要在什么地方做什么事就很清晰,再就是耐心的打log,尝试,就能成功了!

Abner_泥阿布 wechat
欢迎您扫一扫上面的微信公众号,订阅我们的公众号!
或者欢迎加入QQ群:568863373。


如果你觉得这篇文章对你有帮助,请点击下面的分享链接,你还可以选择扫描二维码进行打赏!

我的Github

我的新浪微博