安卓嵌套滾動NestedScroll了解一下

其實嵌套滾動已經(jīng)算一個比較常見的特效了,下面這個動圖就是嵌套滾動的一個例子:

demo.gif

看到這個動效,大家可能都知道可以用CoordinatorLayout去實現(xiàn).其實CoordinatorLayout是基于NestedScroll機制去實現(xiàn)的,而我們直接通過NestedScroll機制也能很方便的實現(xiàn)這個動效.

原理

NestedScroll的其實很簡單.

一般的觸摸消息的分發(fā)都是從外向內(nèi)的,由外層的ViewGroup的dispatchTouchEvent方法調(diào)用到內(nèi)層的View的dispatchTouchEvent方法.

而NestedScroll提供了一個反向的機制,內(nèi)層的view在接收到ACTION_MOVE的時候,將滾動消息先傳回給外層的ViewGroup,看外層的ViewGroup是不是需要消耗一部分的移動,然后內(nèi)層的View再去消耗剩下的移動.內(nèi)層view可以消耗剩下的滾動的一部分,如果還沒有消耗完,外層的view可以再選擇把最后剩下的滾動消耗掉.

上面的描述可能有點繞,可以看下面的圖來幫助理解:

1.png

具體實現(xiàn)

NestedScroll機制會涉及到四個類:

NestedScrollingChild, NestedScrollingChildHelper 和 NestedScrollingParent , NestedScrollingParentHelper

NestedScrollingChild和NestedScrollingParent是兩個接口,我們先看看他們的聲明:

public interface NestedScrollingChild {
    public void setNestedScrollingEnabled(boolean enabled);

    public boolean isNestedScrollingEnabled();

    public boolean startNestedScroll(int axes);

    public void stopNestedScroll();

    public boolean hasNestedScrollingParent();

    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow);

    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow);

    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);

    public boolean dispatchNestedPreFling(float velocityX, float velocityY);
}

public interface NestedScrollingParent {
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);

    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);

    public void onStopNestedScroll(View target);

    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed);

    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);

    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);

    public boolean onNestedPreFling(View target, float velocityX, float velocityY);

    public int getNestedScrollAxes();
}

這里真正重要的其實是NestedScrollingParent的幾個方法,因為其他方法都能直接讓NestedScrollingChildHelper或者NestedScrollingParentHelper去代理:

  • onStartNestedScroll 是否接受嵌套滾動,只有它返回true,后面的其他方法才會被調(diào)用
  • onNestedPreScroll 在內(nèi)層view處理滾動事件前先被調(diào)用,可以讓外層view先消耗部分滾動
  • onNestedScroll 在內(nèi)層view將剩下的滾動消耗完之后調(diào)用,可以在這里處理最后剩下的滾動
  • onNestedPreFling 在內(nèi)層view的Fling事件處理之前被調(diào)用
  • onNestedFling 在內(nèi)層view的Fling事件處理完之后調(diào)用

我們只要讓子view和父view分別實現(xiàn)NestedScrollingChild和NestedScrollingParent接口,然后分別調(diào)用NestedScrollingChildHelper和NestedScrollingParentHelper的對應(yīng)方法去代理一些具體功能,然后在NestedScrollingChild的onTouchEvent那里根據(jù)需求調(diào)用startNestedScroll/dispatchNestedPreScroll/stopNestedScroll就能實現(xiàn)嵌套滾動了:

//NestedScrollingChild
private NestedScrollingChildHelper mHelper = new NestedScrollingChildHelper(this);

public boolean startNestedScroll(int axes) {
  return mHelper.startNestedScroll(axes);
}
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
  return mHelper.dispatchNestedScroll(dxConsumed,  dyConsumed,
             dxUnconsumed,  dyUnconsumed, offsetInWindow);
}
...
//NestedScrollingParent
private NestedScrollingParentHelper mHelper = new NestedScrollingParentHelper(this);

public void onNestedScrollAccepted(View child, View target, int axes) {
  mHelper.onNestedScrollAccepted(child, target, axes);
}

public int getNestedScrollAxes() {
  return mHelper.getNestedScrollAxes();
}
...

但是如果你使用sdk21及以上的版本,NestedScroll機制已經(jīng)直接集成到了View中了,你只需要直接重寫View的對應(yīng)方法就好

布局

我們先看布局文件

<me.linjw.nestedscrolldemo.NestedScrollParentView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <FrameLayout
        android:id="@+id/header"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:src="@mipmap/ic_launcher" />
    </FrameLayout>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorAccent"
        android:text="Title"
        android:textAlignment="center"
        android:textSize="20dp" />

    <android.support.v7.widget.RecyclerView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</me.linjw.nestedscrolldemo.NestedScrollParentView>

最外層是我們自定義的NestedScrollParentView,其實它是一個LinearLayout,內(nèi)部豎直排列了三個子view:

  • 一個由FrameLayout包裹的ImageView
  • 一個TextView
  • 一個RecyclerView

代碼

為了簡便起見,我們先直接用sdk22的版本用重寫View方法的方式去實現(xiàn)它.

NestedScrollParentView中有兩個方法比較重要,嵌套滾動基本上就是由這兩個方法實現(xiàn)的:

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

    @Override
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        super.onNestedPreScroll(target, dx, dy, consumed);

        boolean headerScrollUp = dy > 0 && getScrollY() < mHeaderHeight;
        boolean headerScrollDown = dy < 0 && getScrollY() > 0 && !target.canScrollVertically(-1);
        if (headerScrollUp || headerScrollDown) {
            scrollBy(0, dy);
            consumed[1] = dy;
        }
    }
  • onStartNestedScroll 這個方法如果返回true的話代表接受由內(nèi)層傳來的滾動消息,我們直接返回true就好,否則后面的消息都接受不到

  • onNestedPreScroll 這個方法用于消耗內(nèi)層view的一部分滾動.我們需要將消耗掉的滾動存到counsumed中讓consumed知道.例如我們這里在頂部的FrameLayout需要移動的情況下會消耗掉所有的dy,這樣內(nèi)層的view(即RecyclerView)就不會滾動了.

這里的mHeaderHeight保存的是頂部的FrameLayout的高度:

 @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mHeaderHeight = mHeader.getMeasuredHeight();
    }

到這里基本上就實現(xiàn)了動圖的效果,是不是很簡單?

完整代碼可以參考 https://github.com/bluesky466/NestedScrollDemo/tree/sdk22

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android手勢分發(fā)和嵌套滾動機制 前言 在開始介紹下面的嵌套滾動時有必要先打個廣告,我們的APP可以在 Fin...
    黃名堡閱讀 5,863評論 2 80
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,781評論 25 709
  • 電話那頭,朋友哭了。今天生日,老公卻送了一個特別丑的蛋糕,索性將蛋糕扔了。 Z小姐安慰著電話那頭的朋友,說起自己與...
    小六六二閱讀 431評論 1 6
  • 來源sqlite3 進(jìn)入sqlite3數(shù)據(jù)庫命令行 .exit/.quit 退出sqlite3命令行 sqlite...
    onzing閱讀 906評論 2 0
  • R·閱讀原文片段 現(xiàn)在已經(jīng)有很好的方法來培養(yǎng)樂觀情緒,這個方法就是指認(rèn)出自己的悲觀想法,并且反駁它。 下面教你如何...
    Maggie玲閱讀 265評論 3 0

友情鏈接更多精彩內(nèi)容