學習Android NestedScroll

NestedScrollingChildHelper

這是一個用於實現子視圖嵌套滾動的輔助類,並提供對Android 5.0以前版本的前兼容。ide

View要做爲嵌套滾動中的Child,要在構造方法中實例化一個final的NestedScrollingChildHelper對象。該View中有一系列方法簽名(即方法名和參數列表)和此類中相同的方法(這些方法實際來自於View實現的NestedScrollingChild接口),經過委託模式(delegate),這些方法的實際實現被委派給helper對象來完成。這提供了一種標準的結構化策略,來實現嵌套滾動。this

例如,下面是NestedScrollView中相關的代碼:spa

public class NestedScrollView extends FrameLayout implements NestedScrollingParent,
        NestedScrollingChild {
    ......
    private final NestedScrollingParentHelper mParentHelper;
    // 這裏文檔中特別強調須要final修飾,並且要在構造方法中實例化
    private final NestedScrollingChildHelper mChildHelper;

public NestedScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        ......
        mParentHelper = new NestedScrollingParentHelper(this);
        mChildHelper = new NestedScrollingChildHelper(this);
        ......
    }

    // 下面9個方法來自於NestedScrollingChild接口,注意他們只是簡單調用了mChildHelper中同名同參數列表的方法。

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

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

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

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

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

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

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

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

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

    ......
}

使用嵌套滾動功能的視圖,應該始終使用ViewCompat, ViewGroupCompat 或者 ViewParentCompat中相關的兼容性靜態方法。這保證了和Android 5.0以後的嵌套滾動視圖之間互操做的正確性。code

下面來具體的看這九個方法在NestedScrollingChildHelper類中的實現:對象

  1. 使能或禁止Child的嵌套滾動。接口

    public void setNestedScrollingEnabled(boolean enabled) {
         // 若是處於使能狀態,有可能正在滾動,須要先中止。
         if (mIsNestedScrollingEnabled) {
             // 至關於調用View.stopNestedScroll()。
             // 這個方法若是View正在嵌套滾動,會中止嵌套滾動,反之無影響。
             // 注意這裏實際仍是會調用到NestedScrollingChildHelper.stopNestedScroll()。
             ViewCompat.stopNestedScroll(mView);
         }
         // 這個標誌位表明可否嵌套滾動。
         mIsNestedScrollingEnabled = enabled;
     }
  2. 返回是否能夠嵌套滾動。事件

    // 若是這裏返回true,說明相應的子視圖能夠嵌套滾動。
     // 那麼helper類會負責將滾動操做過程當中的相關數據傳遞給相關聯的nested scrolling parent。
     public boolean isNestedScrollingEnabled() {
         return mIsNestedScrollingEnabled;
     }
  3. 檢查是否有關聯的父視圖,來接受嵌套滾動過程當中的事件。ci

    // 注意這裏的mNestedScrollingParent初始化時是null,
     // 是在下面的startNestedScroll(int axes)方法中找到的。
     public boolean hasNestedScrollingParent() {
         return mNestedScrollingParent != null;
     }
  4. 開始一個新的嵌套滾動。文檔

    // 參數axes表明滾動軸,取ViewCompat#SCROLL_AXIS_HORIZONTAL and/or ViewCompat#SCROLL_AXIS_VERTICAL。
     // 若是找到了嵌套滾動的parent view,而且對於當前手勢使能了嵌套滾動,返回true。
     public boolean startNestedScroll(int axes) {
         if (hasNestedScrollingParent()) {
             // 已經在嵌套滾動過程當中。
             return true;
         }
         // 嵌套滾動功能使能狀況下才能開始。
         if (isNestedScrollingEnabled()) {
             ViewParent p = mView.getParent();
             View child = mView;
             // 依次找父視圖,直到找到第一個接受嵌套滾動的父視圖。
             while (p != null) {
                 // 實際調用的是NestedScrollingParent中的
                 // onStartNestedScroll(View child, View target, int nestedScrollAxes)方法。
                 // 返回父視圖是否接受此嵌套滾動操做。
                 if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) {
                     mNestedScrollingParent = p;
                     // 這裏實際會調用NestedScrollingParentHelper.onNestedScrollAccepted(View child, View target, int axes)方法。
                     ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes);
                     return true;
                 }
                 if (p instanceof View) {
                     child = (View) p;
                 }
                 p = p.getParent();
             }
         }
         return false;
     }
  5. 中止嵌套滾動get

    public void stopNestedScroll() {
         if (mNestedScrollingParent != null) {
             //  實際調用的是NestedScrollingParentHelper.onStopNestedScroll(View target)。
             ViewParentCompat.onStopNestedScroll(mNestedScrollingParent, mView);
             mNestedScrollingParent = null;
         }
     }
  6. 將嵌套滾動過程當中的一步分發給當前的nested scrolling parent。

    // 返回true表明該事件被分發(即父視圖消費任意的嵌套滾動事件),反之則是沒法被分發。
     public boolean dispatchNestedScroll(
         int dxConsumed, // 滾動中被此view消費的水平距離
         int dyConsumed, // 滾動中被此view消費的垂直距離
         int dxUnconsumed, // 滾動中未被此view消費的水平距離
         int dyUnconsumed, // 滾動中未被此view消費的垂直距離
         int[] offsetInWindow // 滾動先後此view的座標偏移量
         ) {
         if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
             if (dxConsumed != 0 || dyConsumed != 0 || dxUnconsumed != 0 || dyUnconsumed != 0) {
                 int startX = 0;
                 int startY = 0;
                 if (offsetInWindow != null) {
                     mView.getLocationInWindow(offsetInWindow);
                     startX = offsetInWindow[0];
                     startY = offsetInWindow[1];
                 }
    
                 // 此方法來自NestedScrollingParent.onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed);
                 // 具體實現由父視圖完成。
                 ViewParentCompat.onNestedScroll(mNestedScrollingParent, mView, dxConsumed,
                         dyConsumed, dxUnconsumed, dyUnconsumed);
    
                 if (offsetInWindow != null) {
                     mView.getLocationInWindow(offsetInWindow);
                     // 偏移量是滾動後的位置減滾動前的位置
                     offsetInWindow[0] -= startX;
                     offsetInWindow[1] -= startY;
                 }
                 return true;
             } else if (offsetInWindow != null) {
                 // No motion, no dispatch. Keep offsetInWindow up to date.
                 offsetInWindow[0] = 0;
                 offsetInWindow[1] = 0;
             }
         }
         return false;
     }
相關文章
相關標籤/搜索