RecyclerView的点击事件

此篇博客代码引用开源项目BaseRecyclerViewAdapterHelper,GitHub地址:https://github.com/CymChad/BaseRecyclerViewAdapterHelper

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

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);
}}

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

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

明白了这些方法名就可以继续分析代码了。
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的点击
来看代码

Set<Integer> childClickViewIds = vh.getClickableItemIds();//获取item中可以点击的id
Set<Integer> nestViewIds = vh.getNestIds();//item中嵌套recyclerview

关于viewholder的这两个方法大家可以查看一下源码,这里不在赘述。
当item当中有可以点击的的子view时

 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;//如果子view是recyclerview的话,不消耗
                                }
                         setPressViewHotSpot(e, childView);//修改childview的状态
                         childView.setPressed(true);
                         onItemChildClick(WXAdapter, childView, vh.getLayoutPosition());//执行itemchild点击
                          // onItemChildClick(WXAdapter, childView, vh.getLayoutPosition() - WeiXinAdapter.getHeaderLayoutCount());//设置点击事件
                            resetPressedView(childView);//重置childview的状态
                            return true;
                            } else {
                             childView.setPressed(false);//若点击范围不在child范围之类
                            }
                        }
                    }

注释已经写好了,大体就是遍历childview,查看点击范围是否属于childview的范围内,另外假如nestViewIds包含childview的id,也就是说子view如果也是recyclerview的时候,对点击事件不予处理。接下来最重要的一步就是实现onItemChildClick方法,里面可以处理点击逻辑事件。我感觉假如要处理的话,应该在添加一个手势检测吧。
假如执行完上面的一系列操作以后,childview的pressed仍然为false的话,说明用户点击不针对childview,则完成itemonclcik方法

 /*后续为item维度点击*/
                    setPressViewHotSpot(e, pressedView);
                    mPressedView.setPressed(true);
                    for (Integer childClickViewId : childClickViewIds) {
                        View childView = pressedView.findViewById(childClickViewId);
                        if (childView != null) {
                            childView.setPressed(false);//将子view全都取消点击
                        }
                    }
                    onItemClick(WXAdapter, pressedView, vh.getLayoutPosition());//执行item点击

后面的代码跟前面逻辑是一样,只不过处理的是没有点击view的item,综上看来,我总觉得代码还是有重复性的,不知道是后来维护没有维护好,还是怎么回事,

for (Integer childClickViewId : childClickViewIds) {`}

循环遍历view的这段代码在最后的逻辑判断当中是没有必要的,因为前面已经判断过了。也许纯粹个人想法,没有看到其深奥之处。

感兴趣的可以关注我最新开的公众号,重在分享!!微信搜索 开发Android的小学生

《RecyclerView的点击事件》 qrcode_for_gh_c686d73be7e1_430.jpg

    原文作者:上官若枫
    原文地址: https://www.jianshu.com/p/4cd1f91cb8fb
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞