自由滚动的嵌套RecycleView

效果

1626533445370.gif

说明

产品想要上面效果.android

  1. 外层是纵向的RecycleView. 其中的部分Item是一个横向的ScrollView.
  2. 当在操做横向的RecycleView滚动时, 若是用户作上下滑动, 那么外层纵向的RecycleView也会滚动

解决方案

摸索玩转如下三个touch事件方法:markdown

onInterceptTouchEvent
复制代码
onTouchEvent
复制代码
dispatchTouchEvent
复制代码

伸手党福利

直接贴代码吧. 反正我写了注释:ide

package com.test.scrolltest;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;

/**
 * 自由滚动的RecyclerView. 须要嵌套使用
 */
public class FreeScrollRecycleView extends RecyclerView{

    /** 记录父View的滚动状态 */
    int mFatherScrollState = -1;

    /** */
    private OnScrollListener mOnScrollListener;

    /** 父View*/
    private FreeScrollRecycleView mFatherScrollableView;

    /** 子View*/
    private FreeScrollRecycleView mJustStopScrollingChildView;

    public FreeScrollRecycleView(@NonNull Context context) {
        super(context);
    }

    public FreeScrollRecycleView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public FreeScrollRecycleView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        //监听父View的滚动
        setFatherScrollableView(mFatherScrollableView);
    }

    protected void setFatherScrollableView(FreeScrollRecycleView fatherScrollableView) {
        if(fatherScrollableView != null){
            mFatherScrollableView = fatherScrollableView;
            if(mOnScrollListener == null){
                mOnScrollListener = new OnScrollListener() {
                    @Override
                    public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                        //记录父view滚动状态
                        mFatherScrollState = newState;
                    }

                    @Override
                    public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                        super.onScrolled(recyclerView, dx, dy);
                    }
                };
            }
            mFatherScrollableView.removeOnScrollListener(mOnScrollListener);
            mFatherScrollableView.addOnScrollListener(mOnScrollListener);
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if(mFatherScrollableView != null){
            mFatherScrollableView.removeOnScrollListener(mOnScrollListener);
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        boolean ret = super.onInterceptTouchEvent(e);

        /*
        不拦截父View的Touch事件.
        当前是子View时, 在开始滚动时, 会在super.onInterceptTouchEvent()中调阻止父View滚动,
        因此在执行完onInterceptTouchEvent()后, 须要重置父View, 容许其接受事件:
         */
        getParent().requestDisallowInterceptTouchEvent(false);

        Log.d("FreeScrollRecycleView", getResources().getResourceName(getId())+": onInterceptTouchEvent: "+ret+" event:"+e.getAction()+"|"+e.hashCode());
        return ret;
    }

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        if(e.getAction()==MotionEvent.ACTION_CANCEL
                && mFatherScrollState ==SCROLL_STATE_DRAGGING){
            Log.d("FreeScrollRecycleView", getResources().getResourceName(getId())+": onTouchEvent: event:"+e.getAction()+"|"+e.hashCode());

            /*
            当前是子View时, 若父View能够开始滚动时, 则会向子View发送CANCEL事件, 阻止子View滚动.
            可是此时并不想子View中止滚动, 因此强行设置ACTION_MOVE
             */
            e.setAction(MotionEvent.ACTION_MOVE);
        }

        boolean ret = super.onTouchEvent(e);

        /*
        滚动时, 也会要求父布局不接受事件, 因此onTouchEvent()执行完毕后, 从新容许父View接受事件:
         */
        getParent().requestDisallowInterceptTouchEvent(false);
        Log.d("FreeScrollRecycleView", getResources().getResourceName(getId())+": onTouchEvent: "+ret+" event:"+e.getAction()+"|"+e.hashCode()+" mfatherScrollState:"+ mFatherScrollState);
        return ret;
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if(ev.getAction()==MotionEvent.ACTION_CANCEL
                && mFatherScrollState ==SCROLL_STATE_DRAGGING){
            Log.d("FreeScrollRecycleView", getResources().getResourceName(getId())+": dispatchTouchEvent: event:"+ev.getAction()+"|"+ev.hashCode());

            /*
            这个CANCEL事件, 是当父View从发送过来的,
            由于父View开始滚动了(father.onInterceptTouchEvent()==true),它要阻止子View滚动
            但此时子View也想滚动, 因此强制设置为 ACTION_MOVE
             */
            ev.setAction(MotionEvent.ACTION_MOVE);

        }

        boolean ret = super.dispatchTouchEvent(ev);
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE:
                if(mFatherScrollableView != null) {
                    //当前是子View, 告知父View, 须要手动发送Touch事件的对象是this
                    mFatherScrollableView.mJustStopScrollingChildView = this;
                }
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
            default:
                if(mFatherScrollableView != null) {
                    //当前是子View, 告知父View, 清空手动发送Touch事件的对象
                    mFatherScrollableView.mJustStopScrollingChildView = null;
                }
                //当前是父View. 清空须要手动发送Touch事件的子View
                mJustStopScrollingChildView = null;
        }

        if(mJustStopScrollingChildView != null){

            //向子View分发事件
            mJustStopScrollingChildView.dispatchTouchEvent(ev);
        }
        Log.d("FreeScrollRecycleView", getResources().getResourceName(getId())+": dispatchTouchEvent: "+ret+" event:"+ev.getAction()+"|"+ev.hashCode());
        return ret;
    }
}
复制代码
相关文章
相关标签/搜索