NestedScrolling 0

Why

Android事件分發機制一般情況下能滿足大部分情況下View事件處理,其中的關鍵點就是事件攔截和事件上傳,具體的詳情這里不做展開。假設現在有這樣一種場景,ViewGroup有且僅有一個Child View,?
要求是Child消費down事件、ViewGroup消費move事件,這種情況就不太好處理,原因是Child消費down事件后,后續的move、up和cancel的onTouchEvent返回false, 事件都會發送到Child, ViewGroup不會有機會處理??梢钥闯鰀own事件是否消費很關鍵,根據默認的事件分發機制似乎不能完成這里的要求,好在google提供了design(Android L版本提出), design庫其中的一個重要特性就是嵌套滑動機制。?

What

嵌套滑動可以簡單的理解成父(ViewGroup)和子(View或者ViewGroup)共同處理move事件,流程是這樣的:?子View先將滑動事件交給父View處理,?父View根據情況消耗全部或者部分move偏移量,子View再消耗剩余的move偏移量,最后子View再通知父View scroll消耗的情況,父View根據這個需求再進行滑動,整個過程的順序如下:

0,父View先處理scroll
1,子View再處理scroll
2,父View再處理scroll
3,子View再處理scroll

元素

主要接口有2個,分別是 NestedScrollingParent
NestedScrollingChild
,父 View 需要實現 NestedScrollingParent接口,而子 View 需要實現 NestedScrollingChild接口。

NestedScrollingChild接口的源碼如下:

public interface NestedScrollingChild {
    //設置是否開啟嵌套滑動功能
    public void setNestedScrollingEnabled(boolean enabled);
    public boolean isNestedScrollingEnabled();
    //開始一個新的嵌套滑動,參數指定橫向或者縱向
    public boolean startNestedScroll(int axes);
    //結束一個已經開始的嵌套
    public void stopNestedScroll();
    //是否有對應的parent配合處理滑動事件
    public boolean hasNestedScrollingParent();
    //scroll的預處理過程,對應上圖的0和1
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);
    //scroll的后處理過程,對應上圖的2和3
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);
    //fling和scroll和類似
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);
}

NestedScrollingParent接口的源碼如下:

public interface NestedScrollingParent {
    //是否接受子View的嵌套滑動
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);
    //成功后的回調
    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);
    //結束后的回調
    public void onStopNestedScroll(View target);
    //預處理過程,對應上圖的0
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);
    //后處理過程,對應上圖的2
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed);
    //fling和scroll和類似
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
    public boolean onNestedPreFling(View target, float velocityX, float velocityY);
    //橫向還是縱向
    public int getNestedScrollAxes();
}

google還提供了2個輔助類來幫助實現整個過程,分別是NestedScrollingChildHelperNestedScrollingParentHelper,從名字可以看出分別作用的對象是Child和Parent。

比如Child的用法如下:

// NestedScrollingChild

    @Override
    public void setNestedScrollingEnabled(boolean enabled) {
        getScrollingChildHelper().setNestedScrollingEnabled(enabled);
    }

    @Override
    public boolean isNestedScrollingEnabled() {
        return getScrollingChildHelper().isNestedScrollingEnabled();
    }

    @Override
    public boolean startNestedScroll(int axes) {
        return getScrollingChildHelper().startNestedScroll(axes);
    }

    @Override
    public void stopNestedScroll() {
        getScrollingChildHelper().stopNestedScroll();
    }

    @Override
    public boolean hasNestedScrollingParent() {
        return getScrollingChildHelper().hasNestedScrollingParent();
    }

    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
            int dyUnconsumed, int[] offsetInWindow) {
        return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed,
                dxUnconsumed, dyUnconsumed, offsetInWindow);
    }

    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
        return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
    }

    @Override
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
        return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed);
    }

    @Override
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
        return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY);
    }

都是直接的調用helper的方法,可以看出google為了減少開發者的學習和開發成本費了很多心思。Parent的helper相比于Child提供的方法就不是那么多了,主要的邏輯主要靠開發者自己實現。

流程

Child Parent
startNestedScroll onStartNestedScroll、onNestedScrollAccepted
dispatchNestedPreScroll onNestedPreScroll
dispatchNestedScroll onNestedScroll
stopNestedScroll onStopNestedScroll

可以看出是一一對應的關系,整個過程是Child主導,Parent配合完成,用文字說明一下整個過程:

1. 準備過程

Child發起一個嵌套滑動處理,調用startNestedScroll.
Parent回調onStartNestedScroll說明收到了來自Child的請求,根據情況決定是否接受Child請求,通過返回值告訴Child結果。
Parent如果接受Child的請求還會回調onNestedScrollAccepted。

2. 處理過程

每次滑動前,Child 先詢問 Parent 是否需要滑動,即 dispatchNestedPreScroll(),這就回調到 Parent 的 onNestedPreScroll(),Parent 可以在這個回調中“劫持”掉 Child 的滑動,也就是先于 Child 滑動。

Child 滑動以后,會調用 onNestedScroll(),回調到 Parent 的 onNestedScroll(),這里就是 Child 滑動后,剩下的給 Parent 處理,也就是 后于 Child 滑動。

3. 結束過程

Child調用stopNestedScroll發起一個結束請求,Parent回調回調onStopNestedScroll處理相關邏輯,比如重置參數等待下次滑動請求

版本兼容

雖然是Api21版本才提出的,google也提供了兼容方案,相關接口再android.support.v4包中

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容