自由滾動的嵌套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;
    }
}
複製代碼
相關文章
相關標籤/搜索