BaseRecyclerViewAdapterHelper开源项目之BaseSectionQuickAdapter 点击事件源码学习

此篇博客代码引用开源项目BaseRecyclerViewAdapterHelper,GitHub地址:

version:2.8.5

  • 更多分享请看:http://www.cherylgood.cn

今天我们主要来分析BaseRecyclerViewAdapterHelper为view提供监听点击事件能力的相关源码。

 public abstract class SimpleClickListener implements RecyclerView.OnItemTouchListener {
private GestureDetectorCompat mGestureDetector;
private RecyclerView recyclerView;
protected BaseQuickAdapter baseQuickAdapter;
public static String TAG = "SimpleClickListener";
private boolean mIsPrepressed = false;
private boolean mIsShowPress = false;
private View mPressedView = null;

@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
    Log.i(TAG,">>>>onInterceptTouchEvent e"+e.getActionMasked());
    if (recyclerView == null) {
        this.recyclerView = rv;
        this.baseQuickAdapter = (BaseQuickAdapter) recyclerView.getAdapter();
        mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(), new ItemTouchHelperGestureListener(recyclerView));
    }else if (recyclerView!=rv){
        this.recyclerView = rv;
        this.baseQuickAdapter = (BaseQuickAdapter) recyclerView.getAdapter();
        mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(), new ItemTouchHelperGestureListener(recyclerView));
    }
    if (!mGestureDetector.onTouchEvent(e) && e.getActionMasked() == MotionEvent.ACTION_UP && mIsShowPress) {
        if (mPressedView!=null){
            BaseViewHolder vh = (BaseViewHolder) recyclerView.getChildViewHolder(mPressedView);
            if (vh == null ||!isHeaderOrFooterView(vh.getItemViewType())) {
                mPressedView.setPressed(false);
            }
        }
        mIsShowPress = false;
        mIsPrepressed = false;
    }
    return false;
}

@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
    Log.i(TAG,">>>>onTouchEvent e"+e.getActionMasked());
    mGestureDetector.onTouchEvent(e);
}

@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    Log.i(TAG,">>>>onRequestDisallowInterceptTouchEvent disallowIntercept"+disallowIntercept);
}

private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {

    private RecyclerView recyclerView;


    ItemTouchHelperGestureListener(RecyclerView recyclerView) {
        this.recyclerView = recyclerView;
    }
    @Override
    public boolean onDown(MotionEvent e) {
        mIsPrepressed = true;
        mPressedView = recyclerView.findChildViewUnder(e.getX(), e.getY());
        super.onDown(e);
        return false;
    }

    @Override
    public void onShowPress(MotionEvent e) {
        Log.i(TAG,">>>>onShowPress e"+e);
        if (mIsPrepressed && mPressedView != null) {
    //    mPressedView.setPressed(true);
            mIsShowPress = true;
        }
        super.onShowPress(e);
    }


    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        Log.i(TAG,">>>>onSingleTapUp e"+e);
        if (mIsPrepressed && mPressedView != null) {
            if (recyclerView.getScrollState()!=RecyclerView.SCROLL_STATE_IDLE){
                return false;
            }
            final View pressedView = mPressedView;
            BaseViewHolder vh = (BaseViewHolder) recyclerView.getChildViewHolder(pressedView);

            if (isHeaderOrFooterPosition(vh.getLayoutPosition())) {
                return false;
            }
            Set<Integer> childClickViewIds = vh.getChildClickViewIds();
            Set<Integer> nestViewIds = vh.getNestViews();
            if (childClickViewIds != null && childClickViewIds.size() > 0) {
                for (Integer childClickViewId : childClickViewIds) {
                    View childView = pressedView.findViewById(childClickViewId);
                    if (childView != null) {
                        if (inRangeOfView(childView, e) && childView.isEnabled()) {

                            if (nestViewIds!=null&&nestViewIds.contains(childClickViewId)){
                                return false;
                            }

                            setPressViewHotSpot(e, childView);
                            childView.setPressed(true);
                            onItemChildClick(baseQuickAdapter, childView, vh.getLayoutPosition() - baseQuickAdapter.getHeaderLayoutCount());
                            resetPressedView(childView);
                            return true;
                        } else {
                            childView.setPressed(false);
                        }
                    }

                }
                setPressViewHotSpot(e,pressedView);
                mPressedView.setPressed(true);
                for (Integer childClickViewId : childClickViewIds) {
                    View childView = pressedView.findViewById(childClickViewId);
                    if (childView!=null){
                        childView.setPressed(false);
                    }
                }
                onItemClick(baseQuickAdapter, pressedView, vh.getLayoutPosition() - baseQuickAdapter.getHeaderLayoutCount());
            } else {
                setPressViewHotSpot(e,pressedView);
                mPressedView.setPressed(true);
                if (childClickViewIds != null && childClickViewIds.size() > 0) {
                    for (Integer childClickViewId : childClickViewIds) {
                        View childView = pressedView.findViewById(childClickViewId);
                        if (childView!=null){
                            childView.setPressed(false);
                        }
                    }
                }

                onItemClick(baseQuickAdapter, pressedView, vh.getLayoutPosition() - baseQuickAdapter.getHeaderLayoutCount());
            }
            resetPressedView(pressedView);

        }
        return true;
    }

    private void resetPressedView(final View pressedView) {
        if (pressedView!=null){
            pressedView.postDelayed(new Runnable() {
                @Override
                public void run() {
                    if (pressedView!=null){
                        pressedView.setPressed(false);
                    }

                }
            }, 100);
        }

        mIsPrepressed = false;
        mPressedView = null;
    }

    @Override
    public void onLongPress(MotionEvent e) {
        Log.i(TAG,">>>>onLongPress e"+e);
        boolean isChildLongClick =false;
        if (recyclerView.getScrollState()!=RecyclerView.SCROLL_STATE_IDLE){
            return ;
        }
        if (mIsPrepressed && mPressedView != null) {
            mPressedView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
            BaseViewHolder vh = (BaseViewHolder) recyclerView.getChildViewHolder(mPressedView);
            if (!isHeaderOrFooterPosition(vh.getLayoutPosition())) {
                Set<Integer> longClickViewIds = vh.getItemChildLongClickViewIds();
                Set<Integer> nestViewIds = vh.getNestViews();
                if (longClickViewIds != null && longClickViewIds.size() > 0) {
                    for (Integer longClickViewId : longClickViewIds) {
                        View childView = mPressedView.findViewById(longClickViewId);
                        if (inRangeOfView(childView, e) && childView.isEnabled()) {
                            if (nestViewIds!=null&&nestViewIds.contains(longClickViewId)){
                                isChildLongClick=true;
                                break;
                            }
                            setPressViewHotSpot(e, childView);
                            onItemChildLongClick(baseQuickAdapter, childView, vh.getLayoutPosition() - baseQuickAdapter.getHeaderLayoutCount());
                            childView.setPressed(true);
                            mIsShowPress = true;
                            isChildLongClick = true;
                            break;
                        }
                    }
                }
                if (!isChildLongClick){

                    onItemLongClick(baseQuickAdapter, mPressedView, vh.getLayoutPosition() - baseQuickAdapter.getHeaderLayoutCount());
                    setPressViewHotSpot(e,mPressedView);
                    mPressedView.setPressed(true);
                    if (longClickViewIds != null) {
                        for (Integer longClickViewId : longClickViewIds) {
                            View childView = mPressedView.findViewById(longClickViewId);
                            childView.setPressed(false);
                        }
                    }
                    mIsShowPress = true;
                }

            }

        }
    }


}

private void setPressViewHotSpot(final MotionEvent e,final  View mPressedView) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        /**
         * when   click   Outside the region  ,mPressedView is null
         */
        if (mPressedView !=null && mPressedView.getBackground() != null) {
            mPressedView.getBackground().setHotspot(e.getRawX(), e.getY()-mPressedView.getY());
        }
    }
}

/**
 * Callback method to be invoked when an item in this AdapterView has
 * been clicked.
 *
 * @param view     The view within the AdapterView that was clicked (this
 *                 will be a view provided by the adapter)
 * @param position The position of the view in the adapter.
 */
public abstract void onItemClick(BaseQuickAdapter adapter, View view, int position);

/**
 * callback method to be invoked when an item in this view has been
 * click and held
 *
 * @param view     The view whihin the AbsListView that was clicked
 * @param position The position of the view int the adapter
 * @return true if the callback consumed the long click ,false otherwise
 */
public abstract void onItemLongClick(BaseQuickAdapter adapter, View view, int position);

public abstract void onItemChildClick(BaseQuickAdapter adapter, View view, int position);

public abstract void onItemChildLongClick(BaseQuickAdapter adapter, View view, int position);

public boolean inRangeOfView(View view, MotionEvent ev) {
    int[] location = new int[2];
    if (view==null||!view.isShown()){
        return false;
    }
    view.getLocationOnScreen(location);
    int x = location[0];
    int y = location[1];
    if (ev.getRawX() < x
            || ev.getRawX() > (x + view.getWidth())
            || ev.getRawY() < y
            || ev.getRawY() > (y + view.getHeight())) {
        return false;
    }
    return true;
}

private boolean isHeaderOrFooterPosition(int position) {
    /**
     *  have a headview and EMPTY_VIEW FOOTER_VIEW LOADING_VIEW
     */
    if (baseQuickAdapter==null){
        if (recyclerView!=null){
            baseQuickAdapter= (BaseQuickAdapter) recyclerView.getAdapter();
        }else {
            return false;
        }
    }
    int type = baseQuickAdapter.getItemViewType(position);
    return (type == EMPTY_VIEW || type == HEADER_VIEW || type == FOOTER_VIEW || type == LOADING_VIEW);
}
private boolean isHeaderOrFooterView(int type) {

    return (type == EMPTY_VIEW || type == HEADER_VIEW || type == FOOTER_VIEW || type == LOADING_VIEW);
}}

用过BVAH框架中的点击事件的小伙伴们应该对以下几个接口类不陌生:

  • OnItemChildClickListener 给childView添加点击事件监听器
  • OnItemChildLongClickListener 给childView添加长点击事件监听器
  • OnItemClickListener 给整个Item添加点击事件监听器
  • OnItemLongClickListener 给整个Item添加长点击事件监听器

里面都提供了响应的回调接口,但是赋予他们能力的其实是SimpleClickListener
这个类。是不是开始开兴趣了?让我们一起来看下这个类的真实面目:

public abstract class SimpleClickListener implements RecyclerView.OnItemTouchListener {....}
  • 首先我们发现SimpleClickListener
    实现了RecyclerView.OnItemTouchListener
    接口OnItemTouchListener接口是recyclerview提供的一个监听item被点击的监听器,源码如下:

    允许应用去拦截处理touch事件,通常实现操作recyclerview 的交互事件时用到
    public static interface OnItemTouchListener {
    在onInterceptTouchEvent方法会在recyclerview的childview 发生touch交互前被调用
      public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e);
      public void onTouchEvent(RecyclerView rv, MotionEvent e);
      public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept); }
    

注:点击事件的实现有多重方式,如:

  • 使用 RecyclerView提供的 addOnItemTouchListener()实现
  • 创建 ItemView时添加点击事件监听
  • 在 ItemView attach RecyclerView时实现

更多分享:http://www.cherylgood.cn

最近还是在做小的项目demo,进度有点缓慢,刚刚缕清思路。recyclerview的整体是撸下来了,包括适配器,学会了一丢丢东西。写写笔记来巩固一下。关于recyclerview的点击事件,网上有很多方法,我当时大概查了一下,有在适配器上添加的,有的改recyclerview的源码。我当时也是翻了很久,感觉还是那个开源项目写的比较好一点,毕竟在GitHub上面是star数目超过9k的。先上代码吧:

类内字段解析

  • private GestureDetectorCompat mGestureDetector;
    手势检测辅助类,相对于GestureDetectorCompat
    有更好的兼容性,且api使用相同。
  • private RecyclerView recyclerView;
    存储recyclerview实例对象,后面在获取adapter和viewholder时用到
  • protected BaseQuickAdapter baseQuickAdapter;
  • private boolean mIsPrepressed = false;控件被按下标标识
  • private boolean mIsShowPress = false; 控件press状态标识
  • private View mPressedView = null; 被点击的控件

接下来我们根据事件的传递机制进行分析,当touch事件发生时:
onInterceptTouchEvent是第一个被调用的。

@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
    Log.i(TAG,">>>>onInterceptTouchEvent e"+e.getActionMasked());
    if (recyclerView == null) {
        this.recyclerView = rv;
        this.baseQuickAdapter = (BaseQuickAdapter) recyclerView.getAdapter();
        mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(), new ItemTouchHelperGestureListener(recyclerView));
    }else if (recyclerView!=rv){
        this.recyclerView = rv;
        this.baseQuickAdapter = (BaseQuickAdapter) recyclerView.getAdapter();
        mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(), new ItemTouchHelperGestureListener(recyclerView));
    }
    if (!mGestureDetector.onTouchEvent(e) && e.getActionMasked() == MotionEvent.ACTION_UP && mIsShowPress) {
        if (mPressedView!=null){
            BaseViewHolder vh = (BaseViewHolder) recyclerView.getChildViewHolder(mPressedView);
            if (vh == null ||!isHeaderOrFooterView(vh.getItemViewType())) {
                mPressedView.setPressed(false);
            }
        }
        mIsShowPress = false;
        mIsPrepressed = false;
    }
    return false;
}

源码中,在onInterceptTouchEvent 方法中主要做了两件事情:

  • 初始化recyclerView、baseQuickAdapter、mGestureDetector实例
  • 如果点击事件最终没被消费掉,则恢复被touch而进入press状态中的控件状态。

然后我们就可以在onTouchEvent
方法中将touch事件交给mGestureDetector进行处理了

  @Override
   public void onTouchEvent(RecyclerView rv, MotionEvent e) {
       Log.i(TAG,">>>>onTouchEvent e"+e.getActionMasked());
       mGestureDetector.onTouchEvent(e);
   }

然后我们看在初始化mGestureDetector时绑定的ItemTouchHelperGestureListener
监听器, ItemTouchHelperGestureListener
监听器继承自SimpleOnGestureListener类,我们进入SimpleOnGestureListener的源码可以看到:

 public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener,
        OnContextClickListener {

其内部其实是实现了在个手势交互监听器,只是方法体什么都没做,这样的好处是,我们继承他,可以不用实现一堆方法,需要用到哪个方法,重写该方法就可以了。

我们这次主要用到了如下几个方法:

  • onDown 按下时触发
  • onShowPress 处于press状态时触发
  • onSingleTapUp 单击事件触发
  • onLongPress 长点击事件press状态触发

可以看到,使用GestureDetectorCompat的好处。

根据一个touch事件的发生过程,down->showpress->up
即压下,显示挤压效果,松开弹起。

 @Override
    public boolean onDown(MotionEvent e) {
        mIsPrepressed = true;
        mPressedView = recyclerView.findChildViewUnder(e.getX(), e.getY());
        super.onDown(e);
        return false;
    }

在down中:

  • 重置mIsPrepressed 为true,当前已被点击,未松开
  • 根据e的x、y,获得被点击的view。

@Override
public void onShowPress(MotionEvent e) {
if (mIsPrepressed && mPressedView != null) {
mIsShowPress = true;
}
super.onShowPress(e);
}
在showpress中:

  • 根据down的结果,设置mIsShowPress字段,确定当前是否出于showpress状态

如果是单机事件则会触发 onSingleTapUp(MotionEvent e)
在从onSIngleTapUp的源码中可以看到:

if (mIsPrepressed && mPressedView != null) {
  • 如果是我们的recyclerview中的view被点击了才会执行后面的代码

    if (recyclerView.getScrollState()!=RecyclerView.SCROLL_STATE_IDLE){
                  return false;
              }
    
  • 如果未处于RecyclerView.SCROLL_STATE_IDLE状态直接返回false,此时用户可能是在滑动,没不是点击操作

    final View pressedView = mPressedView;
              BaseViewHolder vh = (BaseViewHolder) recyclerView.getChildViewHolder(pressedView);
    
              if (isHeaderOrFooterPosition(vh.getLayoutPosition())) {
                  return false;
              }
    
  • 获取viewholder 如果是头部视图或者尾部视图,返回false
    ,我们不需要处理

接下来到了关键代码了:

 Set<Integer> childClickViewIds = vh.getChildClickViewIds();
  • 获取添加了点击事件的view 的ids
    Set<Integer> nestViewIds = vh.getNestViews();

  • 获取内嵌了recyclerview的view容器 的ids
    if (childClickViewIds != null && childClickViewIds.size() > 0) {

  • 如果有view添加了点击事件监听
    for (Integer childClickViewId : childClickViewIds) {

  • 则开始遍历views
    View childView = pressedView.findViewById(childClickViewId);

  • 根据ids获得添加了点击事件的view

       if (inRangeOfView(childView, e) && childView.isEnabled()) {
    
  • 判断view处于点击范围内切isEnable

如果在点击范围内切isEnable = true则执行下面代码

    if (nestViewIds!=null&&nestViewIds.contains(childClickViewId)){
                return false;  }
  • 如果是内嵌recyclerview ,则不处理,交由子recyclerview进行处理。

经过层层过滤之后,剩下的就是当view被点击时需要做的操作了:
如果

  • setPressViewHotSpot(e, childView); 修改childview的状态
  • childView.setPressed(true); 设置其press为true
  • onItemChildClick(baseQuickAdapter, childView,
    vh.getLayoutPosition() –
    baseQuickAdapter.getHeaderLayoutCount());调用onItemChildClick回调事件。
  • resetPressedView(childView); 重置childview的状态
  • return true; 表示该事件已被消费

如果不出于点击范围内或者isEable=false 则 childView.setPressed(false);

上面处理了item下的childview
的点击,如果是item维度的点击,则执行下面代码,流程基本一致,只是回调的是onItemClick方法。

                setPressViewHotSpot(e,pressedView);
                mPressedView.setPressed(true);
                for (Integer childClickViewId : childClickViewIds) {
                    View childView = pressedView.findViewById(childClickViewId);
                    if (childView!=null){
                        childView.setPressed(false);
                    }
                }
                onItemClick(baseQuickAdapter, pressedView, vh.getLayoutPosition() - baseQuickAdapter.getHeaderLayoutCount());
            } 

之前处理的是存在childview
的情况,如果不存在childview,自然点击事件都是item的了,操作跟在存在childview但childview不处于点击范围内或者isEable=false时类似。

else {
                setPressViewHotSpot(e,pressedView);
                mPressedView.setPressed(true);
                if (childClickViewIds != null && childClickViewIds.size() > 0) {
                    for (Integer childClickViewId : childClickViewIds) {
                        View childView = pressedView.findViewById(childClickViewId);
                        if (childView!=null){
                            childView.setPressed(false);
                        }
                    }
                }

                onItemClick(baseQuickAdapter, pressedView, vh.getLayoutPosition() - baseQuickAdapter.getHeaderLayoutCount());
            }
            resetPressedView(pressedView);

长点击的实现类似,这里就不做重复的分析了。如果大家有哪里不懂的可以直接留言
总结:通过GestureDetectorCompat的使用,能很好的简化我们的编码量,在涉及到交互的操作时,都可以使用GestureDetectorCompat来进行辅助。最好的老师就是阅读优秀的源码,欢迎一起学习,共同进步!

  • 之前我们在Scroller的使用详解中,在onMeasure方法中可能你会看到
    childView.setClickable(true);为什么要设置childView为true呢,假如不设置的话,你会发现ACTION_MOVE并没有执行。为什么会出现这样的问题呢?此时我是一脸懵逼的,要想彻底搞明白,对于Android事件分发机制的了解是必不可少的。

  • 首先我们先来一个测试。

public abstract class SimpleClickListener implements RecyclerView.OnItemTouchListener {private GestureDetectorCompat mGestureDetector;private RecyclerView recyclerView;protected BaseQuickAdapter baseQuickAdapter;public static String TAG = "SimpleClickListener";private boolean mIsPrepressed = false;private boolean mIsShowPress = false;private View mPressedView = null;@Overridepublic boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) { Log.i(TAG,">>>>onInterceptTouchEvent e"+e.getActionMasked; if (recyclerView == null) { this.recyclerView = rv; this.baseQuickAdapter = (BaseQuickAdapter) recyclerView.getAdapter(); mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(), new ItemTouchHelperGestureListener(recyclerView)); }else if (recyclerView!=rv){ this.recyclerView = rv; this.baseQuickAdapter = (BaseQuickAdapter) recyclerView.getAdapter(); mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(), new ItemTouchHelperGestureListener(recyclerView)); } if (!mGestureDetector.onTouchEvent && e.getActionMasked() == MotionEvent.ACTION_UP && mIsShowPress) { if (mPressedView!=null){ BaseViewHolder vh = (BaseViewHolder) recyclerView.getChildViewHolder(mPressedView); if (vh == null ||!isHeaderOrFooterView(vh.getItemViewType { mPressedView.setPressed; } } mIsShowPress = false; mIsPrepressed = false; } return false;}@Overridepublic void onTouchEvent(RecyclerView rv, MotionEvent e) { Log.i(TAG,">>>>onTouchEvent e"+e.getActionMasked; mGestureDetector.onTouchEvent;}@Overridepublic void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { Log.i(TAG,">>>>onRequestDisallowInterceptTouchEvent disallowIntercept"+disallowIntercept);}private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener { private RecyclerView recyclerView; ItemTouchHelperGestureListener(RecyclerView recyclerView) { this.recyclerView = recyclerView; } @Override public boolean onDown(MotionEvent e) { mIsPrepressed = true; mPressedView = recyclerView.findChildViewUnder, e.getY; super.onDown; return false; } @Override public void onShowPress(MotionEvent e) { Log.i(TAG,">>>>onShowPress e"+e); if (mIsPrepressed && mPressedView != null) { // mPressedView.setPressed; mIsShowPress = true; } super.onShowPress; } @Override public boolean onSingleTapUp(MotionEvent e) { Log.i(TAG,">>>>onSingleTapUp e"+e); if (mIsPrepressed && mPressedView != null) { if (recyclerView.getScrollState()!=RecyclerView.SCROLL_STATE_IDLE){ return false; } final View pressedView = mPressedView; BaseViewHolder vh = (BaseViewHolder) recyclerView.getChildViewHolder(pressedView); if (isHeaderOrFooterPosition(vh.getLayoutPosition { return false; } Set<Integer> childClickViewIds = vh.getChildClickViewIds(); Set<Integer> nestViewIds = vh.getNestViews(); if (childClickViewIds != null && childClickViewIds.size { for (Integer childClickViewId : childClickViewIds) { View childView = pressedView.findViewById(childClickViewId); if (childView != null) { if (inRangeOfView(childView, e) && childView.isEnabled { if (nestViewIds!=null&&nestViewIds.contains(childClickViewId)){ return false; } setPressViewHotSpot(e, childView); childView.setPressed; onItemChildClick(baseQuickAdapter, childView, vh.getLayoutPosition() - baseQuickAdapter.getHeaderLayoutCount; resetPressedView(childView); return true; } else { childView.setPressed; } } } setPressViewHotSpot(e,pressedView); mPressedView.setPressed; for (Integer childClickViewId : childClickViewIds) { View childView = pressedView.findViewById(childClickViewId); if (childView!=null){ childView.setPressed; } } onItemClick(baseQuickAdapter, pressedView, vh.getLayoutPosition() - baseQuickAdapter.getHeaderLayoutCount; } else { setPressViewHotSpot(e,pressedView); mPressedView.setPressed; if (childClickViewIds != null && childClickViewIds.size { for (Integer childClickViewId : childClickViewIds) { View childView = pressedView.findViewById(childClickViewId); if (childView!=null){ childView.setPressed; } } } onItemClick(baseQuickAdapter, pressedView, vh.getLayoutPosition() - baseQuickAdapter.getHeaderLayoutCount; } resetPressedView(pressedView); } return true; } private void resetPressedView(final View pressedView) { if (pressedView!=null){ pressedView.postDelayed(new Runnable() { @Override public void run() { if (pressedView!=null){ pressedView.setPressed; } } }, 100); } mIsPrepressed = false; mPressedView = null; } @Override public void onLongPress(MotionEvent e) { Log.i(TAG,">>>>onLongPress e"+e); boolean isChildLongClick =false; if (recyclerView.getScrollState()!=RecyclerView.SCROLL_STATE_IDLE){ return ; } if (mIsPrepressed && mPressedView != null) { mPressedView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); BaseViewHolder vh = (BaseViewHolder) recyclerView.getChildViewHolder(mPressedView); if (!isHeaderOrFooterPosition(vh.getLayoutPosition { Set<Integer> longClickViewIds = vh.getItemChildLongClickViewIds(); Set<Integer> nestViewIds = vh.getNestViews(); if (longClickViewIds != null && longClickViewIds.size { for (Integer longClickViewId : longClickViewIds) { View childView = mPressedView.findViewById(longClickViewId); if (inRangeOfView(childView, e) && childView.isEnabled { if (nestViewIds!=null&&nestViewIds.contains(longClickViewId)){ isChildLongClick=true; break; } setPressViewHotSpot(e, childView); onItemChildLongClick(baseQuickAdapter, childView, vh.getLayoutPosition() - baseQuickAdapter.getHeaderLayoutCount; childView.setPressed; mIsShowPress = true; isChildLongClick = true; break; } } } if (!isChildLongClick){ onItemLongClick(baseQuickAdapter, mPressedView, vh.getLayoutPosition() - baseQuickAdapter.getHeaderLayoutCount; setPressViewHotSpot(e,mPressedView); mPressedView.setPressed; if (longClickViewIds != null) { for (Integer longClickViewId : longClickViewIds) { View childView = mPressedView.findViewById(longClickViewId); childView.setPressed; } } mIsShowPress = true; } } } }}private void setPressViewHotSpot(final MotionEvent e,final View mPressedView) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { /** * when click Outside the region ,mPressedView is null */ if (mPressedView !=null && mPressedView.getBackground() != null) { mPressedView.getBackground().setHotspot(e.getRawX(), e.getY()-mPressedView.getY; } }}/** * Callback method to be invoked when an item in this AdapterView has * been clicked. * * @param view The view within the AdapterView that was clicked (this * will be a view provided by the adapter) * @param position The position of the view in the adapter. */public abstract void onItemClick(BaseQuickAdapter adapter, View view, int position);/** * callback method to be invoked when an item in this view has been * click and held * * @param view The view whihin the AbsListView that was clicked * @param position The position of the view int the adapter * @return true if the callback consumed the long click ,false otherwise */public abstract void onItemLongClick(BaseQuickAdapter adapter, View view, int position);public abstract void onItemChildClick(BaseQuickAdapter adapter, View view, int position);public abstract void onItemChildLongClick(BaseQuickAdapter adapter, View view, int position);public boolean inRangeOfView(View view, MotionEvent ev) { int[] location = new int[2]; if (view==null||!view.isShown{ return false; } view.getLocationOnScreen; int x = location[0]; int y = location[1]; if (ev.getRawX() < x || ev.getRawX() > (x + view.getWidth || ev.getRawY() < y || ev.getRawY() > (y + view.getHeight { return false; } return true;}private boolean isHeaderOrFooterPosition(int position) { /** * have a headview and EMPTY_VIEW FOOTER_VIEW LOADING_VIEW */ if (baseQuickAdapter==null){ if (recyclerView!=null){ baseQuickAdapter= (BaseQuickAdapter) recyclerView.getAdapter(); }else { return false; } } int type = baseQuickAdapter.getItemViewType; return (type == EMPTY_VIEW || type == HEADER_VIEW || type == FOOTER_VIEW || type == LOADING_VIEW);}private boolean isHeaderOrFooterView { return (type == EMPTY_VIEW || type == HEADER_VIEW || type == FOOTER_VIEW || type == LOADING_VIEW);}}
![](https://upload-images.jianshu.io/upload_images/1811893-effd5f98d26c7eea.png)

QQ20170418-162600.png



如上图,我们activity里只有一个button  
代码如下:

在这里我大概说一下具体思路啊,首先这个类实现了RecyclerView.OnItemTouchListener,里面最重要的是onInterceptTouchEvent方法,主要用于拦截点击事件,返回的是false,因为我们要把点击事件让onTouchEvent方法处理。同时要初始化recyclerView、baseQuickAdapter、mGestureDetector实例。后边还复写了onTouchEvent方法,可以看出在这个方法里,我们返回的是GestureDetectorCompat的实体类的点击方法。整体上就是利用GestureDetectorCompat手势监听处理点击事件。下面先说一下各个方法的作用


OnDown(MotionEvent e):用户按下屏幕就会触发;onShowPress(MotionEvent e):如果是按下的时间超过瞬间,而且在按下的时候没有松开或者是拖动的,那么onShowPress就会执行,具体这个瞬间是多久,我也不清楚呃……onLongPress(MotionEvent e):长按触摸屏,超过一定时长,就会触发这个事件 触发顺序: onDown->onShowPress->onLongPressonSingleTapUp(MotionEvent e):从名子也可以看出,一次单独的轻击抬起操作,也就是轻击一下屏幕,立刻抬起来,才会有这个触发,当然,如果除了Down以外还有其它操作,那就不再算是Single操作了,所以也就不会触发这个事件触发顺序:点击一下非常快的Touchup:onDown->onSingleTapUp->onSingleTapConfirmed点击一下稍微慢点的Touchup:onDown->onShowPress->onSingleTapUp->onSingleTapConfirmedonFling() :滑屏,用户按下触摸屏、快速移动后松开,由1个MotionEvent ACTION_DOWN, 多个ACTION_MOVE, 1个ACTION_UP触发
package guanaj.com.scrollerdemo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.Scroller;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

private static final String TAG = "MainActivity";
private Scroller scroller;
private LinearLayout llContent;
private Button mButton;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);


    mButton = (Button) findViewById(R.id.m_button);
    mButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.i(TAG,"#onClick 我被点击了");
        }
    });
    mButton.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {

            switch (event.getAction()){
                case MotionEvent.ACTION_DOWN:{
                    Log.i(TAG,"#onTouch ACTION_DOWN ");
                    break;
                }
                case MotionEvent.ACTION_MOVE:{
                    Log.i(TAG,"#onTouch ACTION_MOVE ");
                    break;
                }
                case MotionEvent.ACTION_UP:{
                    Log.i(TAG,"#onTouch ACTION_UP ");
                    break;
                }
                default:{
                    Log.i(TAG,"#onTouch");
                }
            }
            return false;
        }
    });
}

明白了这些方法名就可以继续分析代码了。1.首先来看OnDown方法,这个在用户刚刚按下的时候就会触发,这个方法里可以找到点击的view,另外将控件被按下的标识mIsPrepressed至为true。2.onShowPress方法按下控件超过瞬间被触发,3.接下来看最重要的方法onSingleTapUp这个是轻击事件。先从大概分析,首先排除三种可能性:①当前view是否被点击且点击的view是否为空②点前的点击事件是否是由于recyclerview的滚动而造成的点击效果③当前点击的是否是recyclerview的底部或顶部控件,这要解释一下头部和底部并不是根据position为0来划分的,而是由item的type来决定的,如果想要深入了解这个的话,可以查看这个框架的viewholder。这里解释一下为什么要排除,举个栗子QQ空间,假如是由于recyclerview组成的。最上面的你的背景墙很有可能就是recyclerview的头部,这个点击和下面的item的点击效果是不一样的。以上效果排除以后才能算是真正得用户点击事件。这里面还要针对item的布局做出不一样的效果,①item中有可以点击的子view而且用户点击的也是子view②item当中有点击的子view,但是用户点击范围不在这个子view当中③item当中不包含可以点击的子view,直接出发item的点击来看代码

然后我们点击一下:

04-18 16:23:20.022 30413-30413/guanaj.com.scrollerdemo I/MainActivity:
#onTouch ACTION_DOWN
04-18 16:23:20.052 30413-30413/guanaj.com.scrollerdemo I/MainActivity:
#onTouch ACTION_MOVE
04-18 16:23:20.062 30413-30413/guanaj.com.scrollerdemo I/MainActivity:
#onTouch ACTION_MOVE
04-18 16:23:20.082 30413-30413/guanaj.com.scrollerdemo I/MainActivity:
#onTouch ACTION_MOVE
04-18 16:23:20.132 30413-30413/guanaj.com.scrollerdemo I/MainActivity:
#onTouch ACTION_MOVE
04-18 16:23:20.132 30413-30413/guanaj.com.scrollerdemo I/MainActivity:
#onTouch ACTION_MOVE
04-18 16:23:20.152 30413-30413/guanaj.com.scrollerdemo I/MainActivity:
#onTouch ACTION_MOVE
04-18 16:23:20.162 30413-30413/guanaj.com.scrollerdemo I/MainActivity:
#onTouch ACTION_MOVE
04-18 16:23:20.172 30413-30413/guanaj.com.scrollerdemo I/MainActivity:
#onTouch ACTION_UP
04-18 16:23:20.192 30413-30413/guanaj.com.scrollerdemo I/MainActivity:
#onClick 我被点击了

  • -||手抖了一下,出现了几个MOVE
  • 从log里我们能得到一下信息:
    1、
    OnTouch事件是优先于OnClick事件的,也就是说点击事件会先传递到OnTouch,然后才会传递到OnClick。
    2、ACTION_DOWN
    与ACTION_UP只会发生一次,也就是手指压下去ACTION_DOWN,手指松开ACTION_UP,此时恰巧手抖了,出现了多个ACTION_MOVE。
    3、ACTION_UP后OnClick事件才会触发。

可能你会发现,onClick回调方法没有返回值,OnTouch回调方法返回个false?懵逼的我尝试着把false变成true……..懵逼的事情发生了。
log


04-18 16:35:42.672 1294-1294/guanaj.com.scrollerdemo I/MainActivity:
#onTouch ACTION_DOWN
04-18 16:35:42.702 1294-1294/guanaj.com.scrollerdemo I/MainActivity:
#onTouch ACTION_MOVE
04-18 16:35:42.722 1294-1294/guanaj.com.scrollerdemo I/MainActivity:
#onTouch ACTION_MOVE
04-18 16:35:42.732 1294-1294/guanaj.com.scrollerdemo I/MainActivity:
#onTouch ACTION_MOVE
04-18 16:35:42.762 1294-1294/guanaj.com.scrollerdemo I/MainActivity:
#onTouch ACTION_MOVE
04-18 16:35:42.772 1294-1294/guanaj.com.scrollerdemo I/MainActivity:
#onTouch ACTION_MOVE
04-18 16:35:42.772 1294-1294/guanaj.com.scrollerdemo I/MainActivity:
#onTouch ACTION_UP

懵逼的OnClick事件没有触发~~懵逼的我只能为了满足小小的好奇心,到网上查了些资料以及查看了源码:

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图