How to solve the sliding conflict between RecyclerView and NestedScrollView in Android

1. Solve the sliding conflict between RecyclerView and NestedScrollView

Question 1: When we slide the RecyclerView component, the carousel picture above does not slide (NestedScrollView does not slide, that is, the sliding event is consumed by RecyclerView). When the RecyclerView slides to the bottom, the carousel picture part slides . As shown in the picture below, the RecyclerView has slid, but the carousel part has not.
Insert image description here
overall arrangement

<?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>

This does not meet our design requirements. We want the carousel to slide to the top first and then slide the RecycleView. There is a method in recycleView that uses recyclerView.setNestedScrollingEnabled(false); to resolve sliding conflicts. carry out testing


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

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

After testing, it was found that it worked, but another problem arose. When the carousel part slid out of the screen, it could not continue to slide upward. As shown below, neither NestedScrollView nor RecyclerView can continue to slide upward at this time.
Insert image description here
Based on this phenomenon, I guess that the recyclerView.setNestedScrollingEnabled(false) method actually prevents NestedScrollView from continuing to distribute the sliding event downwards, but consumes this event alone. Then due to the height limit of NestScrollView (the height of the carousel and the item height of recyclerView), and the RecyclerView does not receive the sliding event (cannot continue to update the item data), it cannot continue to slide up at this time. If my guess is correct, then to solve this problem, you only need to let NestedScrollView distribute events to RecyclerView at the right time, that is, call mContentList.setNestedScrollingEnabled(true) at the right time. So I set up a listener for NestedScrollView in the initListener() method of the HomePagerFragment class , and set whether to consume events alone based on its sliding distance .

   //尝试解决滑动冲突
    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);
        }
    });

Test results: feasible, the sliding conflict is resolved, but there are still some flaws.

2. Resolve refresh control conflicts

After resolving the sliding conflict, add the refresh control. Here 1. In order to modify the source code of the refresh component, add its module dependencies to the project.
Project address: github address
Insert image description here
Insert image description here

Found the problem occurred again. As shown in the picture below, it hasn't been pulled to the end yet, why did you load more for me?
Insert image description here
The principle is the same, because the refresh component TwinklingRefreshLayout consumes the event, and the RecyclerView does not receive the event, so this situation occurs. The solution is to judge the method of consuming events in the refresh component. If RecyclerView can still slide, then this event will not be consumed and the event will be distributed to RecyclerView. Otherwise, this event will be consumed to load data.
By observing the source code, we found that the RefreshProcessor class overrides the dispatchTouchEvent method, as shown in the following code

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

Insert image description here
The getTargetView in the isViewToBottom method here is to get the content we wrapped in the TwinklingRefreshLayout refresh control. Let’s
look at the isViewToBottom method again.

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

This method determines the type of the subcontainer in the refresh component and calls different methods according to the type. We add a statement to determine whether the subcontainer is NestedScrollView. Note that it should be written above the judgment of the ViewGroup type.

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

Complete method based on previous analysis

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

At this point, the refresh control conflict is resolved. However, this method has not been carefully polished, so there are still some problems. For example, the method finally implemented is actually not perfect. It can only be used normally when there is only one RecyclerView in the refresh component. But it’s also a way to solve the problem

Guess you like

Origin blog.csdn.net/ChenYiRan123456/article/details/130973175