Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 從源碼角度分析嵌套滑動機制NestedScrolling

從源碼角度分析嵌套滑動機制NestedScrolling

編輯:關於Android編程

現在講到android的機制,就是事件分發,事件攔截。但我不知道大家聽沒聽說過嵌套的滑動機制,准確的可以理解成把事件分發,事件攔截綜合在一起。

如果聽說過這個的,你們第一個應該是想到的CoordinatorLayout。也就是只要自己定義個layout實現NestedScrollingChild。就可以實現這些機制,但我今天講的重點並不是Child。而是Parent。眾所周知,需要用到滾動機制的無非就2種。第一個,scrollview。第二個列表。而現在的listview早已被recyclerview取代了。而recyclerview已經實現了nestedscrollingchild的接口。關於scrollview。v4包裡有一個NestedScrollView也實現滑動機制的接口。

今天我們通過觀察CoordinatorLayout的源碼來了解nestedscrollingparent。

首先我們來看實現接口需要實現的幾個方法:

public interface NestedScrollingParent {
    /**
     * 有嵌套滑動到來了,問下該父View是否接受嵌套滑動
     * @param child 嵌套滑動對應的父類的子類(因為嵌套滑動對於的父View不一定是一級就能找到的,可能挑了兩級父View的父View,child的輩分>=target)
     * @param target 具體嵌套滑動的那個子類
     * @param nestedScrollAxes 支持嵌套滾動軸。水平方向,垂直方向,或者不指定
     * @return 是否接受該嵌套滑動
     */
    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes);

    /**
     * 該父View接受了嵌套滑動的請求該函數調用。onStartNestedScroll返回true該函數會被調用。
     * 參數和onStartNestedScroll一樣
     */
    public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes);

    /**
     * 停止嵌套滑動
     * @param target 具體嵌套滑動的那個子類
     */
    public void onStopNestedScroll(View target);

    /**
     * 嵌套滑動的子View在滑動之後報告過來的滑動情況
     *
     * @param target 具體嵌套滑動的那個子類
     * @param dxConsumed 水平方向嵌套滑動的子View滑動的距離(消耗的距離)
     * @param dyConsumed 垂直方向嵌套滑動的子View滑動的距離(消耗的距離)
     * @param dxUnconsumed 水平方向嵌套滑動的子View未滑動的距離(未消耗的距離)
     * @param dyUnconsumed 垂直方向嵌套滑動的子View未滑動的距離(未消耗的距離)
     */
    public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
                               int dxUnconsumed, int dyUnconsumed);

    /**
     * 在嵌套滑動的子View未滑動之前告訴過來的准備滑動的情況
     * @param target 具體嵌套滑動的那個子類
     * @param dx 水平方向嵌套滑動的子View想要變化的距離
     * @param dy 垂直方向嵌套滑動的子View想要變化的距離
     * @param consumed 這個參數要我們在實現這個函數的時候指定,回頭告訴子View當前父View消耗的距離 
     *                    consumed[0] 水平消耗的距離,consumed[1] 垂直消耗的距離 好讓子view做出相應的調整
     */
    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed);

    /**
     * 嵌套滑動的子View在fling之後報告過來的fling情況
     * @param target 具體嵌套滑動的那個子類
     * @param velocityX 水平方向速度
     * @param velocityY 垂直方向速度
     * @param consumed 子view是否fling了
     * @return true 父View是否消耗了fling
     */
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);

    /**
     * 在嵌套滑動的子View未fling之前告訴過來的准備fling的情況
     * @param target 具體嵌套滑動的那個子類
     * @param velocityX 水平方向速度
     * @param velocityY 垂直方向速度
     * @return true 父View是否消耗了fling
     */
    public boolean onNestedPreFling(View target, float velocityX, float velocityY);

    /**
     * 獲取嵌套滑動的軸
     * @see ViewCompat#SCROLL_AXIS_HORIZONTAL 垂直
     * @see ViewCompat#SCROLL_AXIS_VERTICAL 水平
     * @see ViewCompat#SCROLL_AXIS_NONE 都支持
     */
    public int getNestedScrollAxes();
}

執行過程:

在實現的NestedScrollingParent幾個接口中(onNestedScrollAccepted, onStopNestedScroll, getNestedScrollAxes)調用NestedScrollingParentHelper對應的函數。 視情況而定onNestedScroll onNestedPreScroll onNestedFling onNestedPreFling 做相應的處理。
我們一個一個類來看:
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
        boolean handled = false;

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target,
                        nestedScrollAxes);
                handled |= accepted;

                lp.acceptNestedScroll(accepted);
            } else {
                lp.acceptNestedScroll(false);
            }
        }
        return handled;
    }
onStartNestedScroll,判斷父view是否參與滾動事件,源碼是從Behavior以及使用了遞歸調用講handled=true;因為我對Behavior不是很了解,你們有興趣的可以自行研究。我們可以在這裡這裡return ture;告訴子view我會參與你的滾動事件。
public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) {
        mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes);
        mNestedScrollingDirectChild = child;
        mNestedScrollingTarget = target;

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            if (!lp.isNestedScrollAccepted()) {
                continue;
            }

            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                viewBehavior.onNestedScrollAccepted(this, view, child, target, nestedScrollAxes);
            }
        }
    }

2.這個是處理和子view一樣的滾動事件,如果我們自定義的話,調用
helper.onNestedScrollAccepted(child, target, axes)。他的參數和start一樣,即只要參與了滾動事件,我們就需要處理滾動事件。

  public void onStopNestedScroll(View target) {
        mNestedScrollingParentHelper.onStopNestedScroll(target);

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            if (!lp.isNestedScrollAccepted()) {
                continue;
            }

            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                viewBehavior.onStopNestedScroll(this, view, target);
            }
            lp.resetNestedScroll();
            lp.resetChangedAfterNestedScroll();
        }

        mNestedScrollingDirectChild = null;
        mNestedScrollingTarget = null;
    }

3.讓view停止滾動,此時會通知子view停止滾動事件,相當於action_up的效果。

 public void onNestedScroll(View target, int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed) {
        final int childCount = getChildCount();
        boolean accepted = false;

        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            if (!lp.isNestedScrollAccepted()) {
                continue;
            }

            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                viewBehavior.onNestedScroll(this, view, target, dxConsumed, dyConsumed,
                        dxUnconsumed, dyUnconsumed);
                accepted = true;
            }
        }

        if (accepted) {
            dispatchOnDependentViewChanged(true);
        }
    }

4.這段的意思是:
子view滑動結束調用
dyUnconsumed < 0 向下滾
dyUnconsumed > 0 向上滾
after childview move over, dyUnconsumed <0 pull down else up
我來畫個圖方便大家理解把:
這裡寫圖片描述
此時的view2是存在的,但是因為view1鋪滿整個屏幕,所以view2是看不見的,如果我們想讓view2滑出來。只要當view1滾動結束,使用此方法讓view2滾動出來。

 public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
        int xConsumed = 0;
        int yConsumed = 0;
        boolean accepted = false;

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            if (!lp.isNestedScrollAccepted()) {
                continue;
            }

            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                mTempIntPair[0] = mTempIntPair[1] = 0;
                viewBehavior.onNestedPreScroll(this, view, target, dx, dy, mTempIntPair);

                xConsumed = dx > 0 ? Math.max(xConsumed, mTempIntPair[0])
                        : Math.min(xConsumed, mTempIntPair[0]);
                yConsumed = dy > 0 ? Math.max(yConsumed, mTempIntPair[1])
                        : Math.min(yConsumed, mTempIntPair[1]);

                accepted = true;
            }
        }

        consumed[0] = xConsumed;
        consumed[1] = yConsumed;

        if (accepted) {
            dispatchOnDependentViewChanged(true);
        }
    }

4.這個相當於在子view滾動之前讓父view滾動。但我不理解的試,consumed[0],和consumed[1]。是告訴子view父view滾動的x,y的偏移量。為什麼源碼裡的cousumed[0]和cousumed[1]都等於0?這是當時看源碼的時候我最不理解的地方。這裡我們只要告訴子view我們滾動的x,y值。讓子view一起滾動就行了(注意:雖然是讓子view滾動,但我們效果實際是滾動父view。總不能父view滾下去,然後回來了。子view下去了就回不來了把。)如果都為0,我畫個圖或許你們就懂了。
這裡寫圖片描述
我們想要的效果是左邊的,而如果你都給他返回0的話就是右邊的效果。這並不是我們想要的。

 public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
        boolean handled = false;


        if (handled)
        { 
            final int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) 
            {
                final View view = getChildAt(i);
                final LayoutParams lp = (LayoutParams) view.getLayoutParams();
                if (!lp.isNestedScrollAccepted()) 
                {
                continue;
                }

                final Behavior viewBehavior = lp.getBehavior();
                if (viewBehavior != null) 
                {
                handled |= viewBehavior.onNestedFling(this, view, target, velocityX, velocityY,
                        consumed);
                }
            }
            dispatchOnDependentViewChanged(true);
        }
        return handled;
    }

這個我更沒搞懂。他確定中間的代碼會執行?不鳥他,我們直接return false就可以了。

 public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
        boolean handled = false;

        final int childCount = getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = getChildAt(i);
            final LayoutParams lp = (LayoutParams) view.getLayoutParams();
            if (!lp.isNestedScrollAccepted()) {
                continue;
            }

            final Behavior viewBehavior = lp.getBehavior();
            if (viewBehavior != null) {
                handled |= viewBehavior.onNestedPreFling(this, view, target, velocityX, velocityY);
            }
        }
        return handled;
    }

這個確定沒搞錯?不應該和上面那個一樣??,依舊是return false。

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

這個就簡單了。
我們通過看源碼了解了CoordinatorLayout的執行流程。其實如果自定義layout實現這個接口反而比CoordinatorLayout簡單很多。因為從源碼角度和我之前的注釋來講,我們的核心代碼是寫在:onNestedPreScroll()和onNestedScroll(),因為這2個一個是在子view滾動之前,一個是在子view滾動之後。
現在我們可以通過實現這個接口來自定義屬於自己的Coordinatorlayout了。我自己寫了一個才200+行代碼,而原生的快3000行,而且還不一定符合自己的效果。如果你了解了滑動機制,就去實現屬於自己的Coordinatorlayout吧。
我的android交流群:232748032

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved