RecyclerView默认可以使用ItemTouchHelper实现各种动作,但是如果需要实现类似qq的侧滑显示菜单的效果则需要自定义实现,主要实现方式就是自定义RecyclerView,重写dispatchTouchEvent,onTouchEvent事件。
实现步骤,
1.在dispatchTouchEvent中,
- ACTION_DOWN事件:(1)如果点击的不是已经打开的item则关闭并停止此次事件;(2)如果是点击的是item,则获取item的侧滑菜单的宽度。
- ACTION_MOVE事件:左右滑动距离是否大于上下滑动距离且大于最小滑动距离,是的话则可以进行侧滑并设置一个标识(用于后面的MOVE事件是否进行判断),否则此次事件不进行侧滑,且尝试隐藏已经显示的菜单;
- ACTION_UP事件:重置状态;
2.onTouchEvent中,
- ACTION_MOVE事件:判断滑动的方向,并进行item的滑动,滑动到最小或最大距离时不再滑动。
- ACTION_UP事件:判断当前滑动的距离,大于等于1/2就显示,否则就隐藏,使用Scroller来实现。
处理多点触控,使用mActivePointerId保存按压的id,以最后一个按压点为准。
dispatchTouchEvent和onTouchEvent的关键代码有这些
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (!canSide) {
return super.dispatchTouchEvent(event);
}
int action = event.getActionMasked();
if (isCancelTouch) {
switch (action) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
isCancelTouch = false;
break;
}
return true;
}
switch (action) {
case MotionEvent.ACTION_DOWN:
mActivePointerId = event.getPointerId(0);
mDownX = event.getX();
mDownY = event.getY();
//侧滑
mPointPosition = pointToPosition(mDownX, event.getY());
//LogUtil.i("*******pointToPosition(mDownX, mDownY): " + mPointPosition);
if (mPointPosition != NO_POSITION) {
if (isSideShow && mPointChild != null) {
// 如果已经显示则直接关闭
View view = layoutManager.findViewByPosition(mPointPosition);
if (!mPointChild.equals(view)) {
turnNormal();
isCancelTouch = true;
return true;
}
}
if (positionIsItem()) {
//获取当前的item
View view = layoutManager.findViewByPosition(mPointPosition);
if (view instanceof ViewGroup) {
mPointChild = (ViewGroup) view;
getSideWidth();
} else {
return super.dispatchTouchEvent(event);
}
}
onLocked();
} else {
if (isSideShow) {
turnNormal();
return true;
}
}
break;
case MotionEvent.ACTION_MOVE:
if (positionIsItem()) {
if (!isLockStatus) {
final int activePointerIndex = event.findPointerIndex(mActivePointerId);
final float x = event.getX(activePointerIndex);
final float y = event.getY(activePointerIndex);
float xGap = Math.abs(x - mDownX);
float yGap = Math.abs(y - mDownY);
// 判断是左右还是上下
if (xGap > yGap && xGap > mTouchSlop && positionIsItem()) {
isSide = true;
isLockStatus = true;
} else if (yGap > xGap && yGap > mTouchSlop) {
isSide = false;
isLockStatus = true;
if (isSideShow) {
// 上下滑动时隐藏右侧
turnNormal();
}
}
if (xGap > yGap && xGap > mTouchSlop && !isLockStatus) {
// 非锁定时
onUnLocked();
}
}
}
break;
case MotionEvent.ACTION_UP:
isLockStatus = false;
onUnLocked();
break;
case MotionEvent.ACTION_POINTER_DOWN:
final int index = event.getActionIndex();
mDownX = event.getX(index);
mActivePointerId = event.getPointerId(index);
break;
case MotionEvent.ACTION_POINTER_UP:
final int pointerIndex = event.getActionIndex();
final int pointerId = event.getPointerId(pointerIndex);
if (pointerId == mActivePointerId) {
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
mActivePointerId = event.getPointerId(newPointerIndex);
}
mDownX = event.getX(event.findPointerIndex(mActivePointerId));
break;
default:
isLockStatus = false;
onUnLocked();
isSide = false;
break;
}
return super.dispatchTouchEvent(event);
}
/**
* 获取侧滑菜单的宽度
*/
private void getSideWidth() {
mSideWidth = 0;
View sideView;
int sideWidth;
for (int i = 1, count = mPointChild.getChildCount(); i < count; i++) {
sideView = mPointChild.getChildAt(i);
if (sideView != null && (sideView.getVisibility() == VISIBLE)) {
sideWidth = sideView.getLayoutParams().width;
if (sideWidth <= 0) {
sideWidth = sideView.getWidth();
}
if (sideWidth <= 0) {
sideView.measure(0, 0);
sideWidth = sideView.getMeasuredWidth();
}
mSideWidth += sideWidth;
}
}
}
/**
* 点击位置转为子view的position
* @param x x
* @param y y
* @return position
*/
private int pointToPosition(float x, float y) {
View targetView = findChildViewUnder(x, y);
if (targetView == null) return NO_POSITION;
return getChildAdapterPosition(targetView);
}
/**
* 判断点击的position是否item
*/
private boolean positionIsItem() {
if (getAdapter() == null) return false;
int realPosition = mPointPosition;
int itemCount = getAdapter().getItemCount();
return realPosition >= 0 && realPosition < itemCount;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!canSide) {
return super.onTouchEvent(event);
}
if (isCancelTouch) {
return true;
}
if (isSide && positionIsItem()) {
//事件响应
requestDisallowInterceptTouchEvent(true);
int action = event.getActionMasked();
switch (action) {
case MotionEvent.ACTION_POINTER_DOWN:
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
MotionEvent cancelEvent = MotionEvent.obtain(event);
cancelEvent.setAction(MotionEvent.ACTION_CANCEL |
(event.getActionIndex() << MotionEvent.ACTION_POINTER_INDEX_SHIFT));
onTouchEvent(cancelEvent);
final int activePointerIndex = event.findPointerIndex(mActivePointerId);
final float x = event.getX(activePointerIndex);
int deltaX = (int) (x - mDownX);
mDownX = x;
int scrollX = mPointChild.getScrollX();
// 手指拖动itemView滑动, deltaX大于0向右滑动,小于0向左滑动
if (deltaX > 0) {
if (scrollX > 0) {
// 右侧显示时
if (deltaX < scrollX) {
// 滑动的距离小于显示的距离
mPointChild.scrollBy(-deltaX, 0);
} else {
// 滑动的距离大于显示的距离, 滑动到头
mPointChild.scrollBy(-scrollX, 0);
}
}
} else if (deltaX < 0) {
if (scrollX < mSideWidth) {
//右侧未显示完全
int endWidth = mSideWidth - scrollX;// 剩余显示的距离
if (-deltaX < endWidth) {
// 滑动的距离小于剩余显示的距离
mPointChild.scrollBy(-deltaX, 0);
} else {
// 滑动的距离剩余显示的距离, 滑动到头
mPointChild.scrollBy(endWidth, 0);
}
}
}
//LogUtil.d("onTouchEvent ACTION_MOVE deltaX:" + deltaX + ",scrollX:" + scrollX);
return true;
case MotionEvent.ACTION_UP:
int upScrollX = mPointChild.getScrollX();
//LogUtil.d("onTouchEvent ACTION_UP:" + upScrollX);
if (upScrollX >= mSideWidth / 2) {
// 滑动到显示右侧
scrollShowSide();
} else {
// 滑动到关闭右侧
turnNormal();
}
isSide = false;
//事件响应
requestDisallowInterceptTouchEvent(false);
mActivePointerId = INVALID_POINTER_ID;
break;
}
}
return super.onTouchEvent(event);
}
滑动的代码
@Override
public void computeScroll() {
// 调用startScroll的时候scroller.computeScrollOffset()返回true,
if (scroller.computeScrollOffset() && mPointChild != null) {
// 让ListView item根据当前的滚动偏移量进行滚动
mPointChild.scrollTo(scroller.getCurrX(), scroller.getCurrY());
//LogUtil.d("computeScroll x:" + scroller.getCurrX() + "y:" + scroller.getCurrY());
postInvalidate();
// 滚动动画结束的时候调用回调接口
/*if (scroller.isFinished()) {
//mPointChild.scrollTo(0, 0);
}*/
}
}
/**
* 转换到显示状态
*/
public void scrollShowSide() {
isSideShow = true;
if (mPointChild == null) return;
// 时间
float delta = (float) (Math.abs(mSideWidth - mPointChild.getScrollX())) / (mSideWidth / 2f) * animDuration;
// x偏移量
int offset = mSideWidth - mPointChild.getScrollX();
if (offset == 0) return;
// 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
scroller.startScroll(mPointChild.getScrollX(), 0, offset, 0, (int) Math.abs(delta));
postInvalidate(); // 刷新itemView
}
/**
* 转换为正常隐藏情况
*/
public void turnNormal() {
isSideShow = false;
if (mPointChild == null) return;
// 时间
float delta = (float) mPointChild.getScrollX() / (mSideWidth / 2f) * animDuration;
// x偏移量
int offset = -mPointChild.getScrollX();
if (offset == 0) return;
// 调用startScroll方法来设置一些滚动的参数,我们在computeScroll()方法中调用scrollTo来滚动item
scroller.startScroll(mPointChild.getScrollX(), 0, offset, 0, (int) Math.abs(delta));
postInvalidate(); // 刷新itemView
}
如果RecyclerView在ViewPager中的话需要特殊处理,自定义一个可以设置是否可以滑动的ViewPager,当侧滑时停止viewPager的滑动,主要代码如下
/**
* 锁定时禁止viewpager的滑动
*/
private void onLocked() {
ViewParent parent = getParent();
CustomViewPager pager;
while (parent != null) {
if (parent instanceof CustomViewPager) {
pager = (CustomViewPager) parent;
pager.setTag(R.id.tag_custom_view_page, pager.getScrollable());
pager.setScrollable(false);
}
parent = parent.getParent();
}
}
/**
* 解除锁定时
*/
private void onUnLocked() {
ViewParent parent = getParent();
CustomViewPager pager;
boolean scroll = true;
while (parent != null) {
if (parent instanceof CustomViewPager) {
pager = (CustomViewPager) parent;
if (pager.getTag(R.id.tag_custom_view_page) instanceof Boolean) {
scroll = (boolean) pager.getTag(R.id.tag_custom_view_page);
}
((CustomViewPager) parent).setScrollable(scroll);
}
parent = parent.getParent();
}
}