博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
BaseRecyclerViewAdapterHelper开源项目之点击事件源码学习
阅读量:7095 次
发布时间:2019-06-28

本文共 18154 字,大约阅读时间需要 60 分钟。

hot3.png

##version:2.8.5

  • 更多分享请看:

今天我们主要来分析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](https://my.oschina.net/u/1162528)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](https://my.oschina.net/u/1162528)public void onTouchEvent(RecyclerView rv, MotionEvent e) {    Log.i(TAG,">>>>onTouchEvent e"+e.getActionMasked());    mGestureDetector.onTouchEvent(e);}[@Override](https://my.oschina.net/u/1162528)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](https://my.oschina.net/u/1162528)    public boolean onDown(MotionEvent e) {        mIsPrepressed = true;        mPressedView = recyclerView.findChildViewUnder(e.getX(), e.getY());        super.onDown(e);        return false;    }    [@Override](https://my.oschina.net/u/1162528)    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
childClickViewIds = vh.getChildClickViewIds(); Set
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
longClickViewIds = vh.getItemChildLongClickViewIds(); Set
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时实现

####类内字段解析

  • 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是第一个被调用的。

@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) && 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
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来进行辅助。最好的老师就是阅读优秀的源码,欢迎一起学习,共同进步!

转载于:https://my.oschina.net/zaizaiangels/blog/867358

你可能感兴趣的文章
Java入门篇(五)——字符串/String类
查看>>
python 的StringIO
查看>>
第三个阶段事后诸葛亮
查看>>
java中的sql语句中如果有like怎么写
查看>>
【原创】驱动加载之StartService
查看>>
1751: [Usaco2005 qua]Lake Counting
查看>>
【BZOJ】4753: [Jsoi2016]最佳团体 01分数规划+树上背包
查看>>
iOS 获取设备信息之UIDevice的使用,Swift 基于 API
查看>>
IntelliJ cannot log in to GitHub上传github报错解决
查看>>
MySQL强制性操作
查看>>
对称密码-分组密码-AES
查看>>
腾讯地图点击地图创建锚点(且只创建一个)同事创建锚点提示
查看>>
洛谷P1345 [USACO5.4]奶牛的电信Telecowmunication(最小割)
查看>>
洛谷P3396 哈希冲突(分块)
查看>>
优化Mysql数据库的8个方法
查看>>
Centos7下用户登录失败N次后锁定用户禁止登陆的方法
查看>>
开源许可协议简介
查看>>
程序性能优化的3个级别
查看>>
有的日期输入框,可直接调用javascripts
查看>>
手工创建表控制条目
查看>>