ScrollView 嵌套 RecyclerVeiw, 輕松解決滑動沖突

過場背景

滑動沖突

在開發android中, 滑動沖突是一常見的事件沖突。

列如:在scrollView中嵌套listView 或 recyclerView。

由于這2種視圖都可以滑動,就會導致父視圖攔截了滑動事件,從而導致子視圖獲取不到滑動事件。

如何解決

第一種: 清晰的了解android的事件分發機制,在各個view的攔截事件中做相應的處理。

第二種:android在Lollipop之后為滑動機制提供了NestedScrolling特性,可以使用NestedScrollingChild和NestedScrollingParent 來解決滑動沖突。

今天我們主要來講第二種實現方式。

我們先看下他們的方法對應的關系:

子view 父view
startNestedScroll onStartNestedScroll、onNestedScrollAccepted
dispatchNestedPreScroll onNestedPreScroll
dispatchNestedScroll onNestedScroll
stopNestedScroll onStopNestedScroll

一般是子View發起,父view接受回調。

首先,我們先了解做為子view的RecyclerView是如何實現和調用NestedScrollingChild接口中的方法。

我們來看看reyclerView的onTouch()方法:

public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild {
 .......
    @Override
    public boolean onTouchEvent(MotionEvent e) {
      .......

        if (action == MotionEvent.ACTION_DOWN) {
            mNestedOffsets[0] = mNestedOffsets[1] = 0;
        }
        vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]);

        switch (action) {
            .......
            case MotionEvent.ACTION_MOVE: {
                .......
                int dx = mLastTouchX - x;
                int dy = mLastTouchY - y;

                //在recyclerView進行滑動之前,會先調用dispatchNestedPreScroll來判斷父view是否需要處理
                if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) {
                    // 這里主要對mScrollOffset數組賦值, mScrollConsumed[0]代表父View 在x坐標消耗的滑動
                    //的數值,mScrollConsumed[1]表示父View在Y軸消耗的滑動數值
                    dx -= mScrollConsumed[0];
                    dy -= mScrollConsumed[1];
                    vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
                    // Updated the nested offsets
                    mNestedOffsets[0] += mScrollOffset[0];
                    mNestedOffsets[1] += mScrollOffset[1];
                }

                .......
        }
        .......

        return true;
    }
}
 .......

我們發現recyclerView在滑動之前會調用dispatchNestedPreScroll方法來判斷是否有實現NestedScrollingParent 的父View處理。有的話就會執行里方法,減去父View已經消耗的滑動值。

OK 現在我們了解這些,可以正式進入我們的主題了。

如何快速的解決在scrollView中嵌套RecyclerVeiw的事件沖突?

我們可以看到,recylcerView已經實現了NestedScrollingChild 接口, 所以我們只需要自定義scrollView。我們只要讓scrollVeiw實現NestedScrollingParent,在recyclerView發起調用時做相應的操作就可以了。

看下我是如何實現的:

package eebochina.com.testtechniques.nestedScroll;

import android.content.Context;
import android.support.v4.view.NestedScrollingParent;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ScrollView;

/**
 * Created by User on 2017/1/17.
 */
public class NestedParent extends ScrollView implements NestedScrollingParent {

    //方便測試先固定。
    private int maxHeight = 464;
    private RecyclerView mRecyclerView;

    public NestedParent(Context context) {
        super(context);
    }

    public NestedParent(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public NestedParent(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    public void setMaxHeight(int maxHeight) {
        this.maxHeight = maxHeight;
    }

    @Override
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        return super.onStartNestedScroll(child, target, nestedScrollAxes);
    }

    @Override
    public void onNestedScrollAccepted(View child, View target, int axes) {
        super.onNestedScrollAccepted(child, target, axes);
    }

    @Override
    public void onStopNestedScroll(View target) {
        super.onStopNestedScroll(target);
    }

    @Override
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
    }

    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
        return consumed;
    }

    //返回true代表父view消耗滑動速度,子View將不會滑動
    @Override
    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
        if (null == mRecyclerView) mRecyclerView = (RecyclerView) target;
        if (mRecyclerView.computeVerticalScrollOffset() != 0) {
            return false;
        }
        this.fling((int) velocityY);
        return true;
    }

    //對應子view 的dispatchNestedPreScroll方法, 最后一個數組代表消耗的滾動量,下標0代表x軸,下標1代表y軸
    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        //判斷是否滾動到最大值
        if ( dy >= 0 && this.getScrollY() < maxHeight) {
            if (null == mRecyclerView) mRecyclerView = (RecyclerView) target;
            //計算RecyclerView的偏移量, 等于0的時候說明recyclerView沒有滑動,否則應該交給recyclerView自己處理
            if (mRecyclerView.computeVerticalScrollOffset() != 0) return;
            this.smoothScrollBy(dx, dy);
            consumed[1] = dy; //consumed[1]賦值為 dy ,代表父類已經消耗了改滾動。
        }
    }
}

代碼非常少,主要是實現了NestedScrollingParent方法,并在滑動和滾動的方法進行處理。

主要邏輯也進行了相應的注釋,最大值為了方便測試現也寫了一個固定值,也預留了賦值方法。

我們看下效果圖:

nestedScroll.gif

完整代碼地址:

https://github.com/hu5080126/SimpleExample/tree/master/nestedScrollView/src

到這里已經寫完了, 希望可以幫到大家。 ( 如果能去了解事件的分發機制那肯定是最好的)

大家有什么好的建議,歡迎提出。

 

來自:http://www.jianshu.com/p/a4484468d334

 

 本文由用戶 ee5100 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!