美文网首页程序员Android知识Android开发
BaseRecyclerViewAdapterHelper开源项

BaseRecyclerViewAdapterHelper开源项

作者: Angels_安杰 | 来源:发表于2017-03-25 20:01 被阅读273次

    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
    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时实现

    类内字段解析

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

    相关文章

      网友评论

        本文标题:BaseRecyclerViewAdapterHelper开源项

        本文链接:https://www.haomeiwen.com/subject/qnntottx.html