GestureDetector无法取消长按

期望对RecyclerView中的item可以做单击, 长按, 和拖拽操作, 利用OnItemTouchListener可以达到一个在外部总控的目的。一般的对Item的操作都是在Item内部定义一个OnClickListener,如果Item的点击之类的操作与外部无关并且Item内部状态各不相同, 那这样做是最好的,但如果需要和外部关联并且这些操作不会对个别的Item另作处理,这样做就有些不足,通常这样的操作是各个Item持有同一个操作实例, 这就是所谓“外部总控”。

比如:对一个Item长按,item自己状态变成selected或者checked, 同时外部界面变成编辑界面,Item的操作各不相同,但变成编译界面不区别具体某个Item,这样一个操作不仅涉及Item内部状态数据的变化,也涉及外部操作响应。OnItemTouchListener的好处就是能够统一在recyclerView层进行Touch事件的操作而同时不影响Item内部设置的各个Touch事件。
但是,现实却是另一回事,实践了一下OnItemTouchListener却没有达到预想的目的。

一般的,利用OnItemTouchListener这个接口需要再用到GestureDetector这个对象。
声明了对一个Item的操作接口

1
2
3
4
5
6
7
public interface OnItemClickListener {
void onItemClick(RecyclerView.ViewHolder holder);

void onItemLongClick(RecyclerView.ViewHolder holder);

boolean startDragging(RecyclerView.ViewHolder holder, MotionEvent e);
}

GestureDetector.SimpleOnGestureListeneronSingleTapUp响应onItemClick, onLongPress响应onItemLongClickonDown里外部决定是否可拖拽startDragging

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
private static class RecyclerItemTouchHandler implements RecyclerView.OnItemTouchListener {
private final OnItemClickListener mListener;
private final GestureDetectorCompat mGestureDetector;

public RecyclerItemTouchHandler(final RecyclerView rv, OnItemClickListener listener) {
mListener = listener;
GestureDetector.OnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent e) {
android.util.Log.d("wesley", "RecyclerItemTouchHandler.onSingleTapUp");
View child = rv.findChildViewUnder(e.getX(), e.getY());
if (child != null) {
mListener.onItemClick(rv.getChildViewHolder(child));
}
return super.onSingleTapUp(e);
}

@Override
public void onLongPress(MotionEvent e) {
View child = rv.findChildViewUnder(e.getX(), e.getY());
if (child != null) {
mListener.onItemLongClick(rv.getChildViewHolder(child));
}
android.util.Log.d("wesley", "RecyclerItemTouchHandler.onLongPress");
}

@Override
public boolean onDown(MotionEvent e) {
View child = rv.findChildViewUnder(e.getX(), e.getY());
boolean handled = child != null && mListener.startDragging(rv.getChildViewHolder(child), e);
if (handled) {
mGestureDetector.setIsLongpressEnabled(false);
android.util.Log.d("wesley", "RecyclerItemTouchHandler.onDown");
}
return handled || super.onDown(e);
}
};
mGestureDetector = new GestureDetectorCompat(rv.getContext().getApplicationContext(),
gestureListener);
}

@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
return mGestureDetector.onTouchEvent(e);
}

@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
android.util.Log.d("wesley", "RecyclerItemTouchHandler.onTouchEvent");
mGestureDetector.onTouchEvent(e);
}

@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}

public static boolean hitTest(View view, float x, float y) {
int location[] = new int[2];
view.getLocationOnScreen(location);
int viewX = location[0];
int viewY = location[1];
return (x > viewX && x < (viewX + view.getWidth())) &&
(y > viewY && y < (viewY + view.getHeight()));
}
}

startDragging的实现中利用ItemTouchHelper的来做具体操作:

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
view.addOnItemTouchListener(new RecyclerItemTouchHandler(view, new RecyclerItemTouchHandler.OnItemClickListener() {
@Override
public void onItemClick(RecyclerView.ViewHolder holder) {
if (mPresenter.isEdit()) {
int pos = holder.getAdapterPosition();
boolean selected = !mPresenter.isSelected(pos);
mPresenter.selectProvider(pos, selected);
}
}

@Override
public void onItemLongClick(RecyclerView.ViewHolder holder) {
if (mPresenter.setEdit(true) &&
mPresenter.selectProvider(holder.getAdapterPosition(), true)) {

}
}

@Override
public boolean startDragging(RecyclerView.ViewHolder holder, MotionEvent e) {
View drag = holder.itemView.findViewById(R.id.drag_icon);
boolean handled = drag != null && RecyclerItemTouchHandler.hitTest(drag,
e.getRawX(), e.getRawY());
if (handled) {
mTouchHelper.startDrag(holder);
}
return handled;
}
}));

这一切是那么的完美,然而实现的效果却是在拖拽的过程中响应了onLongPress的事件!费了半天劲找原因,看了下GestureDetector源码,原来是因为在onDown中并没有取消LONG_PRESS的消息:

1
2
3
4
5
6
7
8
9
10
case MotionEvent.ACTION_DOWN:
...
if (mIsLongpressEnabled) {
mHandler.removeMessages(LONG_PRESS);
mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
+ TAP_TIMEOUT + LONGPRESS_TIMEOUT);
}
mHandler.sendEmptyMessageAtTime(SHOW_PRESS, mCurrentDownEvent.getDownTime() + TAP_TIMEOUT);
handled |= mListener.onDown(ev);
break;

也就是说即使onDown返回trueLONG_PRESS的消息还是发出了。这与我们通常对Touch事件的理解有些不同,为啥要这样写呢?我认为应该改成这样:

1
2
3
4
5
6
7
handled |= mListener.onDown(ev);
if (!handled && mIsLongpressEnabled) {
mHandler.removeMessages(LONG_PRESS);
mHandler.sendEmptyMessageAtTime(LONG_PRESS, mCurrentDownEvent.getDownTime()
+ TAP_TIMEOUT + LONGPRESS_TIMEOUT);
}
break;

但是不管怎样,因为GestureDetector无法取消长按,没法用这种实现了,最后在外部还是用了View.OnClickListenerView.OnLongClickListenerView.OnTouchListener,显然,在OnTouchListener中由于返回了true, OnLongClickListener无法被响应。但是这种实现有个不好的点是在执行onBindViewHolder时不能在对应的View上再设置或者覆盖其它的Listener了,否则操作失效。

dark
sans