Android で RecyclerView と NestedScrollView の間のスライドの競合を解決する方法

1. RecyclerView と NestedScrollView の間のスライディングの競合を解決する

質問 1: RecyclerView コンポーネントをスライドすると、上のカルーセル画像はスライドしません (NestedScrollView はスライドしません。つまり、スライド イベントは RecyclerView によって消費されます)。RecyclerView が下にスライドすると、カルーセル画像部分がスライドします下の図に示すように、RecyclerView はスライドしましたが、カルーセル部分はスライドしていません。
ここに画像の説明を挿入します
全体的な配置

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:AutoLoopStyle="http://schemas.android.com/apk/res-auto"
    android:id="@+id/home_pager_parent"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/color_page_Bg"
    android:gravity="center"
    android:orientation="vertical">


        <!--滚动页面-->
        <androidx.core.widget.NestedScrollView
            android:id="@+id/home_pager_nested_scroller"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:overScrollMode="never">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center"
                android:orientation="vertical">

                <LinearLayout
                    android:id="@+id/home_pager_header_container"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"

                    android:orientation="vertical">

                    <RelativeLayout
                        android:layout_width="match_parent"
                        android:layout_height="125dp"
                        android:layout_marginBottom="14dp">

                        <com.example.taobaounion.ui.custom.AutoLoopViewPager
                            android:id="@+id/looper_pager"
                            android:layout_width="match_parent"
                            android:layout_height="match_parent"
                            AutoLoopStyle:duration="4000"
                            android:overScrollMode="never" />

                        <LinearLayout
                            android:id="@+id/looper_point_container"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:layout_alignParentBottom="true"
                            android:layout_marginBottom="10dp"
                            android:gravity="center"
                            android:orientation="horizontal" />

                    </RelativeLayout>
                    <!--标题-->
                    <include layout="@layout/include_home_pager_title_part"
                        />

                </LinearLayout>




                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/home_pager_content_list"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"

                    android:overScrollMode="never" />


            </LinearLayout>


        </androidx.core.widget.NestedScrollView>



</LinearLayout>

これは設計要件を満たしていません。最初にカルーセルを一番上にスライドさせてから、RecycleView をスライドさせたいと考えています。recycleView には、recyclerView.setNestedScrollingEnabled(false); を使用してスライディングの競合を解決するメソッドがあります。テストを実施する


    @BindView(R.id.home_pager_content_list)
    public RecyclerView mContentList;
    @Override
    protected void initView(View rootView) {
    
    

        mContentList.setNestedScrollingEnabled(false);  //没错,就是我了
    }

テストの結果、動作することは確認できましたが、カルーセル部分が画面から滑り出すと、そのまま上にスライドできなくなるという問題がありました。以下に示すように、現時点では、NestedScrollView も RecyclerView も上方向にスライドし続けることはできません。
ここに画像の説明を挿入します
この現象に基づいて、recyclerView.setNestedScrollingEnabled(false) メソッドは実際には NestedScrollView が下方向にスライディング イベントを配布し続けるのを防ぎ、このイベントだけを消費すると推測します。次に、NestScrollView の高さ制限 (カルーセルの高さとrecyclerView の項目の高さ) と、RecyclerView がスライド イベントを受信しない (項目データの更新を続行できない) ため、現時点では上にスライドし続けることができません。 。私の推測が正しい場合、この問題を解決するには、NestedScrollView が適切なタイミングでイベントを RecyclerView に配布できるようにするだけで済みます。つまり、適切なタイミングで mContentList.setNestedScrollingEnabled(true) を呼び出すことができます。そこで、 HomePagerFragment クラスの initListener() メソッドに NestedScrollView のリスナーを設定し、そのスライド距離に基づいてイベントのみを消費するかどうかを設定しました。

   //尝试解决滑动冲突
    mNestedScrollView.setOnScrollChangeListener(new NestedScrollView.OnScrollChangeListener() {
    
    
        @Override
        public void onScrollChange(NestedScrollView v, int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
    
    
            LogUtils.e(HomePagerFragment.class,"NestedScrollView --> scrollY -->" + scrollY);
	    //滑动的距离大于或等于mContentList的顶部位置时
            if (scrollY >= mContentList.getTop()) mContentList.setNestedScrollingEnabled(true);
            else mContentList.setNestedScrollingEnabled(false);
        }
    });

テスト結果: 実行可能、スライディングの競合は解決されましたが、まだいくつかの欠陥があります。

2. リフレッシュ制御の競合を解決する

スライディングの競合を解決した後、リフレッシュ コントロールを追加します。ここでは 1. リフレッシュ コンポーネントのソース コードを変更するには、そのモジュールの依存関係をプロジェクトに追加します。プロジェクト アドレス:
githubアドレス
ここに画像の説明を挿入します
ここに画像の説明を挿入します

問題が再び発生していることがわかりました。下の図に示すように、まだ最後まで読み込まれていません。なぜさらに読み込んだのですか?
ここに画像の説明を挿入します
原理は同じで、更新コンポーネント TwinklingRefreshLayout がイベントを消費し、RecyclerView はイベントを受信しないためです。したがってこの状況が発生します。解決策は、リフレッシュ コンポーネントでイベントを消費する方法を判断することです。RecyclerView がまだスライドできる場合は、このイベントは消費されずに RecyclerView にイベントが配布されます。そうでない場合は、このイベントはデータのロードに消費されます。
ソース コードを観察すると、次のコードに示すように、RefreshProcessor クラスがdispatchTouchEvent メソッドをオーバーライドしていることがわかりました。

 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    
    
        switch (ev.getAction()) {
    
    
            case MotionEvent.ACTION_DOWN:
                downEventSent = false;
                intercepted = false;
                mTouchX = ev.getX();
                mTouchY = ev.getY();

                if (cp.isEnableKeepIView()) {
    
    
                    if (!cp.isRefreshing()) {
    
    
                        cp.setPrepareFinishRefresh(false);
                    }
                    if (!cp.isLoadingMore()) {
    
    
                        cp.setPrepareFinishLoadMore(false);
                    }
                }

                cp.dispatchTouchEventSuper(ev);
                return true;
            case MotionEvent.ACTION_MOVE:
                mLastMoveEvent = ev;
                float dx = ev.getX() - mTouchX;
                float dy = ev.getY() - mTouchY;
                if (!intercepted && Math.abs(dx) <= Math.abs(dy) && Math.abs(dy) > cp.getTouchSlop()) {
    
    //滑动允许最大角度为45度
                    if (dy > 0 && ScrollingUtil.isViewToTop(cp.getTargetView(), cp.getTouchSlop()) && cp.allowPullDown()) {
    
    
                        cp.setStatePTD();
                        mTouchX = ev.getX();
                        mTouchY = ev.getY();
                        sendCancelEvent();
                        intercepted = true;
                        return true;
                    } else if (dy < 0 && ScrollingUtil.isViewToBottom(cp.getTargetView(), cp.getTouchSlop()) && cp.allowPullUp()) {
    
    
                        cp.setStatePBU();
                        mTouchX = ev.getX();
                        mTouchY = ev.getY();
                        intercepted = true;
                        sendCancelEvent();
                        return true;
                    }
                }
                if (intercepted) {
    
    
                    if (cp.isRefreshVisible() || cp.isLoadingVisible()) {
    
    
                        return cp.dispatchTouchEventSuper(ev);
                    }
                    if (!cp.isPrepareFinishRefresh() && cp.isStatePTD()) {
    
    
                        if (dy < -cp.getTouchSlop() || !ScrollingUtil.isViewToTop(cp.getTargetView(), cp.getTouchSlop())) {
    
    
                            cp.dispatchTouchEventSuper(ev);
                        }
                        dy = Math.min(cp.getMaxHeadHeight() * 2, dy);
                        dy = Math.max(0, dy);
                        cp.getAnimProcessor().scrollHeadByMove(dy);
                    } else if (!cp.isPrepareFinishLoadMore() && cp.isStatePBU()) {
    
    
                        //加载更多的动作
                        if (dy > cp.getTouchSlop() || !ScrollingUtil.isViewToBottom(cp.getTargetView(), cp.getTouchSlop())) {
    
    
                            cp.dispatchTouchEventSuper(ev);
                        }
                        dy = Math.max(-cp.getMaxBottomHeight() * 2, dy);
                        dy = Math.min(0, dy);
                        cp.getAnimProcessor().scrollBottomByMove(Math.abs(dy));
                    }
                    if (dy == 0 && !downEventSent) {
    
    
                        downEventSent = true;
                        sendDownEvent();
                    }
                    return true;
                }
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                if (intercepted) {
    
    
                    if (cp.isStatePTD()) {
    
    
                        willAnimHead = true;
                    } else if (cp.isStatePBU()) {
    
    
                        willAnimBottom = true;
                    }
                    intercepted = false;
                    return true;
                }
                break;
        }
        return cp.dispatchTouchEventSuper(ev);
    }

ここに画像の説明を挿入します
ここの isViewToBottom メソッドの getTargetView は、TwinklingRefreshLayout 更新コントロールでラップしたコンテンツを取得するためのものです。isViewToBottom
メソッドをもう一度見てみましょう。

    public static boolean isViewToBottom(View view, int mTouchSlop) {
    
    
        if (view instanceof AbsListView) return isAbsListViewToBottom((AbsListView) view);
        if (view instanceof RecyclerView) return isRecyclerViewToBottom((RecyclerView) view);
        if (view instanceof WebView) return isWebViewToBottom((WebView) view, mTouchSlop);
        if (view instanceof ViewGroup) return isViewGroupToBottom((ViewGroup) view);
        return false;
    }

このメソッドはリフレッシュコンポーネント内のサブコンテナの種類を判定し、種類に応じて異なるメソッドを呼び出します。サブコンテナがNestedScrollViewであるかどうかを判定するステートメントを追加します。ただし、ViewGroup型の判定より上に記述する必要があります。

        if (view instanceof NestedScrollView) return isNestedScrollViewToBottom((NestedScrollView)view);//这段是我添加的

以前の分析に基づいた完全なメソッド

  private static boolean isNestedScrollViewToBottom(NestedScrollView view) {
    
    
        ViewGroup viewGroup = (ViewGroup) view.getChildAt(0); //根据布局文件知道这其实是一个LinearLayout
        RecyclerView recyclerView = null;
        //找到LinearLayout中的RecyclerView
        for (int i = 0; i < viewGroup.getChildCount(); i++) {
    
    
            if (viewGroup.getChildAt(i) instanceof RecyclerView)
                recyclerView = (RecyclerView) viewGroup.getChildAt(i);
        }
        //如果RecyclerView能继续向上滑动,则不消费这个事件
        //recyclerView.canScrollVertically(1))表示是否可以向上滑动
        if (recyclerView != null && recyclerView.canScrollVertically(1)) return false;
        //否则消费该事件
        return true;
    }

この時点で、リフレッシュ制御の競合は解決されます。ただし、この方法は慎重に磨かれていないため、まだいくつかの問題があります。たとえば、最終的に実装されたメソッドは実際には完璧ではなく、refresh コンポーネントに RecyclerView が 1 つしかない場合にのみ正常に使用できます。しかし、それは問題を解決する方法でもあります

おすすめ

転載: blog.csdn.net/ChenYiRan123456/article/details/130973175