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) {
        //這里可以不處理, 因為srollView內部已經重寫了改方法,我們可以直接調用
        super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
    }

    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
         return super.onNestedFling(target, velocityX, velocityY, 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

到這里已經寫完了, 希望可以幫到大家。 ( 如果能去了解事件的分發機制那肯定是最好的)
大家有什么好的建議,歡迎提出。
(只支持5.0+的版本)

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,963評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,348評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,083評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,706評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,442評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,802評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,795評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,983評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,542評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,287評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,486評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,030評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,710評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,116評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,412評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,224評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,462評論 2 378

推薦閱讀更多精彩內容