第一次写简书,写的不好或有错误的地方请多多包涵,闲话不多说,先上两张效果图。
Paste_Image.png Paste_Image.png实现此种效果,大致有两种方法,此效果主要是处理滑动事件冲突,第一是重写RecyclerView,重写onInterceptTouchEvent和onTouchEvent。第二种方法时重写RecyclerView的子View,主要也是重写这两个方法,如果是继承ViewGroup,那么要重写onMeasure和onLayout,如果是继承LinearLayout则只要重写事件分发的几个方法就好。这里采用的第二种方法,重写RecyclerView的子View。下面主要说说重写的核心方法:
先看onInterceptTouchEvent方法:
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
boolean consume = false;
int x = (int) e.getX();
int y = (int) e.getY();
switch (e.getAction()){
case MotionEvent.ACTION_DOWN:
//获取手指按下时的坐标
mDownX = (int) e.getX();
mDownY = (int) e.getY();
Log.d(TAG, "mDownX:"+ mDownX);
getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
Log.d(TAG, "onInterceptTouchEvent:ACTION_MOVE");
int deltaX = x - mLastX;
int deltaY = y - mLastY;
if(Math.abs(deltaX) > mTouchSlop || Math.abs(deltaY) > mTouchSlop){
//当y方向滑动距离小于x方向时,拦截事件,交给自己处理
if(Math.abs(deltaX) > Math.abs(deltaY)){
Log.d(TAG, "onInterceptTouchEvent");
//不允许父元素拦截事件
getParent().requestDisallowInterceptTouchEvent(true);
//此时拦截事件,直接调用onTouchEvent,传入onTouchEvent的ACTION_MOVE事件中,不再走onTouchEvent的ACTION_DOWN事件
consume = true;
}else {
getParent().requestDisallowInterceptTouchEvent(false);
}
}
break;
case MotionEvent.ACTION_UP:
Log.d(TAG, "ACTION_UP");
break;
}
mLastX = x;
mLastY = y;
return consume;
}
RecyclerView继承的是ViewGroup,因此默认不拦截任何点击事件,
//当我们重写子View时,必须在合适的时机调用此方法,此处是不允许父元素也就是RecyclerView拦截该事件,此方法时设置一个标记位,具体的可以看源码或者具体了解一下事件分发机制。
getParent().requestDisallowInterceptTouchEvent(true);
onInterceptTouchEvent主要的逻辑是拦截事件,具体一点,当我们在x轴滑动的距离大于y轴滑动的距离时,我们需要子View拦截事件,也就是不允许父元素拦截事件,即
//不允许父元素拦截事件
getParent().requestDisallowInterceptTouchEvent(true);
//此时拦截事件,直接调用onTouchEvent,传入onTouchEvent的ACTION_MOVE事件中,不再走onTouchEvent的ACTION_DOWN事件
consume = true;
当我们完成具体的拦截逻辑后,具体的执行逻辑在onTouchEvent中执行,下面看看代码:
@Override
public boolean onTouchEvent(MotionEvent e) {
switch (e.getAction()){
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
mMoveX = (int) e.getX();
mMoveY = (int) e.getY();
Log.d(TAG, "mMoveX - mDownX:"+ (mMoveX - mDownX));
scrollBy(-(mMoveX - mDownX),0);
int scrollX = getScrollX();
if (scrollX > mItemLength) {
scrollTo(mItemLength, 0);
isOpen = true;
}
if (scrollX < 0) {
scrollTo(0, 0);
isOpen = false;
}
break;
case MotionEvent.ACTION_UP:
//滑动超过一定距离时,自动关闭或开启menu
int upX = (int) getX();
if(Math.abs(getScrollX())>= mMenuWidth && getScrollX() > 0 || Math.abs(getScrollX())< mMenuWidth && getScrollX() < 0){
open();
}else if(Math.abs(getScrollX())>= mMenuWidth && getScrollX() < 0 || Math.abs(getScrollX())< mMenuWidth && getScrollX() > 0){
close();
}
break;
}
mDownX = mMoveX;
mDownY = mMoveY;
return true;
}
具体的执行落在onTouchEvent的ACTION_MOVE中执行,这里的代码也不复杂,最后在ACTION_UP时判断,当滑动超过一定距离时,松开手指可通过Scroller实现平缓滑动的效果。
最后为了实现更好的效果,重写了RecyclerView的onInterceptTouchEvent方法:
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
switch (e.getAction()){
case MotionEvent.ACTION_DOWN:
Log.d(TAG, "onInterceptTouchEvent: down");
int childCount = getChildCount();
for(int i=0;i<childCount;i++){
SlidingView slidingView = (SlidingView) getChildAt(i);
if(slidingView.isOpen){
slidingView.close();
}
}
break;
}
return super.onInterceptTouchEvent(e);
}
具体的效果是,当手指触发ACTION_DOWN事件时,如果有未关闭的子View,那么关闭它。
最后一点要说明的是,在RecyclerView的Adapter中,对于Menu的点击事件采用的是onTouchListener:
((ViewHolder)holder).tvEdit.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if(event.getAction() == MotionEvent.ACTION_DOWN){
int position = holder.getAdapterPosition();
Log.d(TAG, "onTouch: edit:"+position);
if(onClickListenerEditOrDelete != null){
onClickListenerEditOrDelete.OnClickListenerEdit(position);
}
return true;
}
return true;
}
});
因为OnTouchListener的优先级高于OnClickListener,当使用OnClickListener时,有时能触发点击事件有时不能,如有同学知道所以然还想好好请教一番。
具体的实现逻辑就是这样,重点是处理各种事件的滑动冲突,带一点自定义View的基本使用,Scroller和View的滑动机制。demo还有许多不足的地方,还需要多多优化。
网友评论