期望对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.SimpleOnGestureListener
的onSingleTapUp
响应onItemClick
, onLongPress
响应onItemLongClick
,onDown
里外部决定是否可拖拽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返回true
,LONG_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.OnClickListener
,View.OnLongClickListener
,View.OnTouchListener
,显然,在OnTouchListener
中由于返回了true
, OnLongClickListener
无法被响应。但是这种实现有个不好的点是在执行onBindViewHolder
时不能在对应的View上再设置或者覆盖其它的Listener了,否则操作失效。