CoordinatorLayout 與 Behavior

CoordinatorLayout 與 Behavior

CoordinatorLayout 的使用

先看官網(wǎng)對(duì) CoordinatorLayout 的介紹
CoordinatorLayout is a super_powered FrameLayout。

CoordinatorLayout is intended for two primary use cases:

  1. As a top-level application decor or chrome layout;

  2. As a container for a specific interaction with one or more child views

我們常用的是第二種情況居多

CoordinatorLayout 結(jié)合 AppBarLayout,CollapsingToolbarLayout 和 Toolbar 一起使用,可以給我們的應(yīng)用帶來更多的交互效果。
它們的布局關(guān)系

<android.support.design.widget.CoordinatorLayout...>
    <android.support.design.widget.AppBarLayout...>
        <android.support.design.widget.CollapsingToolbarLayout...>
            <!-- your collapsed view -->
            <View.../>
            <android.support.v7.widget.Toolbar.../>
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <!-- Scroll view -->
    <android.support.v7.widget.RecyclerView.../>
</android.support.design.widget.CoordinatorLayout>

這是效果圖


coor_1.gif

先看看它們的幾個(gè)屬性常用的幾個(gè)布局元素

CoordinatorLayout

這幾個(gè)屬性都是直接在子布局中使用的

app:layout_behavior

指定子 View 的 behavior, 關(guān)于 behavior 后面會(huì)有詳細(xì)的論述

app:layout_anchor

指定錨點(diǎn)的 View

app:layout_anchorGravity

指相對(duì)于錨 view 的布局重心

app:layout_keyline

AppBarLayout

AppBarLayout 一般作為 CoordinatorLayout 的直接子類使用;
AppBarLayout 的子 View 通過設(shè)置自身的 srollFlags 進(jìn)行希望的滑動(dòng)行為;
如果把 AppBarLayout 放到普通的 ViewGroup 中而不是 CoordinatorLayout 中,AppBarLayout 的功能將不會(huì)起作用

app:layout_scrollFlags/ setScrollFlags(int)

app:layout_scrollFlags 標(biāo)記位是子布局設(shè)置是否可滑動(dòng)

  • scroll: 滑動(dòng)

  • enterAlways: 獲取屏幕外,向下滑,會(huì)重新出現(xiàn)

  • exitUntilCollapsed 滑動(dòng)一定距離出屏幕,會(huì)收疊成 minHeight

  • snap: 在活動(dòng)停止之后,會(huì)自動(dòng)滑動(dòng)靠邊的一側(cè)

  • enterAlwaysCollapsed:要與 minHeight 和 enterAlways 一起使用, 當(dāng) View 達(dá)到 minHeight 的高度時(shí),CollapsingToolbarLayout 開始展開展開完之后,才會(huì)進(jìn)行滾動(dòng)

    <android.support.design.widget.CoordinatorLayout
    
     ...
     
     <android.support.design.widget.CollapsingToolbarLayout
             android:id="@+id/collapsing_toolbar_layout"
             android:layout_width="match_parent"
             android:layout_height="220dp"
             android:fitsSystemWindows="true"
             android:minHeight="100dp"
             app:expandedTitleMarginStart="38dp"
             app:layout_scrollFlags="scroll|enterAlwaysCollapsed|enterAlways"/>
            
     .../>
    

app:expanded

設(shè)置 AppBarLayout 是否展開

CollapsingToolbarLayout

CollapsingToolbarLayout 是一個(gè)實(shí)現(xiàn)了折疊功能包裹 Toolbar 的 View, 它做一位 AppBarLayout 子 View 使用

app:layout_collapseMode

  • pin 固定,釘住
  • parallax 會(huì)呈現(xiàn)視覺差,需要collapseParallaxMultiplier(0.0~1.0之間) 視覺差系數(shù)一起配合使用在代碼中設(shè)置
       <ImageView
            android:id="@+id/img_bg"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:fitsSystemWindows="true"
            android:scaleType="centerCrop"
            android:src="@drawable/coor_bg"
            app:layout_collapseMode="parallax"
            app:layout_collapseParallaxMultiplier="0.7"/>

app:contentScrim

折疊之后 toolbar 的顏色

Behavior

CoordinatorLayout 的子 View 進(jìn)行一些交互,需要設(shè)置 Behavior
如果是 CoordinatorLayout 內(nèi)部滑動(dòng)的 View 需要設(shè)置已經(jīng)為我們提供好的 behavior

app:layout_behavior="@string/appbar_scrolling_view_behavior"

behavior 的方法分為幾類,

布局相關(guān)的方法

 // 給 Behavior 設(shè)置 LayoutParams 時(shí)會(huì)調(diào)用
public void onAttachedToLayoutParams(...) {}

// LayoutParams 移除時(shí)會(huì)調(diào)用
public void onDetachedFromLayoutParams() {}

//  CoordinatorLayout 在測(cè)量時(shí)會(huì)回調(diào)這個(gè)方法
public boolean onMeasureChild(...) {
    return false;
}

//  CoordinatorLayout 在布局時(shí)會(huì)回調(diào)這個(gè)方法
public boolean onLayoutChild(...) {
    return false;
}

事件處理相關(guān)的方法

// 是否攔截 CoordinatorLayout 發(fā)過了的點(diǎn)擊事件
public boolean onInterceptTouchEvent(...) {
    return false;
}

// 接收 CoordinatorLayout 發(fā)過了的點(diǎn)擊事件
public boolean onTouchEvent(...) {
    return false;
}

滑動(dòng)事件相關(guān)的方法

// 當(dāng) CoordinatorLayout 內(nèi)有 NestedScrollView 開始滑動(dòng)的時(shí)候回調(diào)
public boolean onStartNestedScroll(...) {
    return false;
}

// 當(dāng)上面的 onStartNestedScroll 返回 true,會(huì)回到改方法
public void onNestedScrollAccepted(...) {}

// 當(dāng) CoordinatorLayout 內(nèi)有 NestedScrollView 停止滑動(dòng)的時(shí)候回調(diào)
public void onStopNestedScroll(...) {}

    
// 當(dāng) CoordinatorLayout 內(nèi)有 NestedScrollView 滑動(dòng)過程中的回調(diào)
public void onNestedScroll(...) {}

// 在 onNestedScroll 之前回調(diào)該方法
public void onNestedPreScroll(...) {}

 // 是否滑動(dòng)的慣性事件處理
public boolean onNestedFling(...) {
    return false;
}

// 滑動(dòng)的慣性事件開始的回調(diào)
public boolean onNestedPreFling(...) {
    return false;
}

依賴 View 相關(guān)的方法

這也是我們?cè)谧远x Behavior 時(shí)一定會(huì)重寫的方法

// 當(dāng)前 View 是否依賴指定 View 進(jìn)行變化
public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
    return false;
}

// 依賴的 View(dependency)變化時(shí)的回調(diào)
public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
    return false;
}

// 依賴的 View 被移除時(shí)的回調(diào)
public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {
}

下面是 behavior 重要的方法

    public static abstract class Behavior<V extends View>{
        public Behavior() {
        }
        public Behavior(Context context, AttributeSet attrs) {}

       // 給 Behavior 設(shè)置 LayoutParams 時(shí)會(huì)調(diào)用
        public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams params) {}

        // LayoutParams 移除時(shí)會(huì)調(diào)用
        public void onDetachedFromLayoutParams() {}

             
        // 是否攔截 CoordinatorLayout 發(fā)過了的點(diǎn)擊事件
        public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
            return false;
        }

        // 接收 CoordinatorLayout 發(fā)過了的點(diǎn)擊事件
        public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
            return false;
        }

        // 設(shè)置 Behavior 所在 View 之外的 View 的蒙層顏色
        @ColorInt
        public int getScrimColor(CoordinatorLayout parent, V child) {
            return Color.BLACK;
        }

        // 設(shè)置蒙層的透明度
        @FloatRange(from = 0, to = 1)
        public float getScrimOpacity(CoordinatorLayout parent, V child) {
            return 0.f;
        }

        // 是否對(duì) Behavior 綁定 View 下面的 View 的進(jìn)行交互,
        // 默認(rèn)是是根據(jù) getScrimOpacity 的透明度決定的
        public boolean blocksInteractionBelow(CoordinatorLayout parent, V child) {
            return getScrimOpacity(parent, child) > 0.f;
        }

        // 當(dāng)前 View 是否依賴指定 View 進(jìn)行變化
        public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
            return false;
        }

        // 依賴的 View(dependency)變化時(shí)的回調(diào)
        public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
            return false;
        }

        // 依賴的 View 被移除時(shí)的回調(diào)
        public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {
        }

        //  CoordinatorLayout 在測(cè)量時(shí)會(huì)回調(diào)這個(gè)方法
        public boolean onMeasureChild(CoordinatorLayout parent, V child,
                int parentWidthMeasureSpec, int widthUsed,
                int parentHeightMeasureSpec, int heightUsed) {
            return false;
        }

        //  CoordinatorLayout 在布局時(shí)會(huì)回調(diào)這個(gè)方法
        public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
            return false;
        }

        // 設(shè)置 tag
        public static void setTag(View child, Object tag) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            lp.mBehaviorTag = tag;
        }

        // 獲取 tag
        public static Object getTag(View child) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            return lp.mBehaviorTag;
        }

        // 當(dāng) CoordinatorLayout 內(nèi)有 NestedScrollView 開始滑動(dòng)的時(shí)候回調(diào)
        public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View directTargetChild, @NonNull View target,
                @ScrollAxis int axes, @NestedScrollType int type) {
            if (type == ViewCompat.TYPE_TOUCH) {
                return onStartNestedScroll(coordinatorLayout, child, directTargetChild,
                        target, axes);
            }
            return false;
        }

        // 當(dāng)上面的 onStartNestedScroll 返回 true,會(huì)回到改方法
        public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View directTargetChild, @NonNull View target,
                @ScrollAxis int axes, @NestedScrollType int type) {
            if (type == ViewCompat.TYPE_TOUCH) {
                onNestedScrollAccepted(coordinatorLayout, child, directTargetChild,
                        target, axes);
            }
        }

        // 當(dāng) CoordinatorLayout 內(nèi)有 NestedScrollView 停止滑動(dòng)的時(shí)候回調(diào)
        public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View target, @NestedScrollType int type) {
            if (type == ViewCompat.TYPE_TOUCH) {
                onStopNestedScroll(coordinatorLayout, child, target);
            }
        }

        // 當(dāng) CoordinatorLayout 內(nèi)有 NestedScrollView 滑動(dòng)過程中的回調(diào)
        public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child,
                @NonNull View target, int dxConsumed, int dyConsumed,
                int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type) {
            if (type == ViewCompat.TYPE_TOUCH) {
                onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,
                        dxUnconsumed, dyUnconsumed);
            }
        }

        // 在 onNestedScroll 之前回調(diào)該方法
        public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed,
                @NestedScrollType int type) {
            if (type == ViewCompat.TYPE_TOUCH) {
                onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
            }
        }

         // 是否滑動(dòng)的慣性事件處理
        public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View target, float velocityX, float velocityY,
                boolean consumed) {
            return false;
        }

        // 滑動(dòng)的慣性事件開始的回調(diào)
        public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout,
                @NonNull V child, @NonNull View target, float velocityX, float velocityY) {
            return false;
        }

        // 如果給CoordinatorLayout設(shè)置了fitSystemWindow=true,可以在這里自己處理WindowInsetsCompat
        @NonNull
        public WindowInsetsCompat onApplyWindowInsets(CoordinatorLayout coordinatorLayout,
                V child, WindowInsetsCompat insets) {
            return insets;
        }

        // 在CoordinatorLayout的requestChildRectangleOnScreen()中被調(diào)用
        public boolean onRequestChildRectangleOnScreen(CoordinatorLayout coordinatorLayout,
                V child, Rect rectangle, boolean immediate) {
            return false;
        }  
    }

CoordinatorLayout 與 Behavior 的關(guān)系

了解 CoordinatorLayout 與 Behavior 的關(guān)系,需要進(jìn)入 CoordinatorLayout 的源碼里面去看看。

CoordinatorLayout#onMeasure

CoordinatorLayout#onMeasure 方法里面會(huì)調(diào)用 Behavior.#onMeasureChild

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 準(zhǔn)備工作, 用 DFS 深度遍歷算法,對(duì)依賴的 View 進(jìn)行排序
    prepareChildren();
    // 根據(jù)情況添加或者移除OnPreDrawListener
    ensurePreDrawListener();

    ...

    final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this);

    final int childCount = mDependencySortedChildren.size();
    for (int i = 0; i < childCount; i++) {
        // 從排好續(xù)的集合中依次獲取Child Vie
        final View child = mDependencySortedChildren.get(i);
        if (child.getVisibility() == GONE) {
            // If the child is GONE, skip...
            continue;
        }

        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        ....
        
        // 處理 FitsSystemWindows
        int childWidthMeasureSpec = widthMeasureSpec;
        int childHeightMeasureSpec = heightMeasureSpec;
        if (applyInsets && !ViewCompat.getFitsSystemWindows(child)) {
            // We're set to handle insets but this child isn't, so we will measure the
            // child as if there are no insets
            ...
        }

        // Behavior 
        final Behavior b = lp.getBehavior();
        // Behavior.onMeasureChild 方法調(diào)用
        if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
                childHeightMeasureSpec, 0)) {
            onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
                    childHeightMeasureSpec, 0);
        }

       ...
    }

    ...
    // 設(shè)置 width,height
    setMeasuredDimension(width, height);
}

CoordinatorLayout#onLayout

CoordinatorLayout#onLayout 方法里面會(huì)調(diào)用 Behavior#onLayoutChild

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    final int layoutDirection = ViewCompat.getLayoutDirection(this);
    final int childCount = mDependencySortedChildren.size();
    for (int i = 0; i < childCount; i++) {
        final View child = mDependencySortedChildren.get(i);
        if (child.getVisibility() == GONE) {
            // If the child is GONE, skip...
            continue;
        }

        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        final Behavior behavior = lp.getBehavior();

        // 調(diào)用 Behavior#onLayoutChild, 如果 behavior 不進(jìn)行測(cè)量,則需要 CoordinatorLayout 自己測(cè)量
        if (behavior == null || !behavior.onLayoutChild(this, child, layoutDirection)) {
            onLayoutChild(child, layoutDirection);
        }
    }
}

public void onLayoutChild(View child, int layoutDirection) {
    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    if (lp.checkAnchorChanged()) {
        throw new IllegalStateException("An anchor may not be changed after CoordinatorLayout"
                + " measurement begins before layout is complete.");
    }
    if (lp.mAnchorView != null) {
        // 設(shè)置了 AnchorView  時(shí)候的布局, app:layout_anchor
        layoutChildWithAnchor(child, lp.mAnchorView, layoutDirection);
    } else if (lp.keyline >= 0) {
        // 設(shè)置了 keyline 的布局, app:layout_keyline
        layoutChildWithKeyline(child, lp.keyline, layoutDirection);
    } else {
        // 正常測(cè)量,像 FrameLayout 那樣布局
        layoutChild(child, layoutDirection);
    }
}

CoordinatorLayout#onLayout

CoordinatorLayout#onLayout 方法會(huì)調(diào)用 Behavior#onInterceptTouchEvent 方法詢問是否要攔截,也會(huì)調(diào)用 調(diào)用 Behavior#onTouchEvent 處理點(diǎn)擊事件

 @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    MotionEvent cancelEvent = null;

    final int action = ev.getActionMasked();

    // Make sure we reset in case we had missed a previous important event.
    // 重置 Behavior 
    if (action == MotionEvent.ACTION_DOWN) {
        resetTouchBehaviors(true);
    }

    // performIntercept 進(jìn)行判斷是否攔截
    final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);

    if (cancelEvent != null) {
        cancelEvent.recycle();
    }

    if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
        resetTouchBehaviors(true);
    }

    return intercepted;
}

private boolean performIntercept(MotionEvent ev, final int type) {
    boolean intercepted = false;
    boolean newBlock = false;

    MotionEvent cancelEvent = null;

    final int action = ev.getActionMasked();

    final List<View> topmostChildList = mTempList1;
    // View 按照 z-order 進(jìn)行排序
    getTopSortedChildren(topmostChildList);

    // Let topmost child views inspect first
    final int childCount = topmostChildList.size();
    for (int i = 0; i < childCount; i++) {
        final View child = topmostChildList.get(i);
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        final Behavior b = lp.getBehavior();

        if ((intercepted || newBlock) && action != MotionEvent.ACTION_DOWN) {
            // Cancel all behaviors beneath the one that intercepted.
            // If the event is "down" then we don't have anything to cancel yet.
            // 如果一個(gè) View 把事件攔截了,則把重疊于它之下的 behavior 事件都取消
            if (b != null) {
                if (cancelEvent == null) {
                    final long now = SystemClock.uptimeMillis();
                    cancelEvent = MotionEvent.obtain(now, now,
                            MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
                }
                switch (type) {
                    case TYPE_ON_INTERCEPT:
                        b.onInterceptTouchEvent(this, child, cancelEvent);
                        break;
                    case TYPE_ON_TOUCH:
                        b.onTouchEvent(this, child, cancelEvent);
                        break;
                }
            }
            continue;
        }

        if (!intercepted && b != null) {
            switch (type) {
                case TYPE_ON_INTERCEPT:
                    // 調(diào)用 Behavior#onInterceptTouchEvent 方法詢問是否要攔截
                    intercepted = b.onInterceptTouchEvent(this, child, ev);
                    break;
                case TYPE_ON_TOUCH:
                    // 調(diào)用 Behavior#onTouchEvent 處理點(diǎn)擊事件 
                    intercepted = b.onTouchEvent(this, child, ev);
                    break;
            }
            if (intercepted) {
                mBehaviorTouchView = child;
            }
        }

        ...
    }

    topmostChildList.clear();

    return intercepted;
}

CoordinatorLayout#onTouchEvent

在 CoordinatorLayout#onTouchEvent 方法里面會(huì)調(diào)用 Behavior#onTouchEvent 是否進(jìn)行攔截判斷

@Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean handled = false;
        boolean cancelSuper = false;
        MotionEvent cancelEvent = null;

        final int action = ev.getActionMasked();

        // 如果是 Behavior 攔截了,則把點(diǎn)擊事件教給 Behavior#onTouchEvent 處理
        if (mBehaviorTouchView != null || (cancelSuper = performIntercept(ev, TYPE_ON_TOUCH))) {
            // Safe since performIntercept guarantees that
            // mBehaviorTouchView != null if it returns true
            final LayoutParams lp = (LayoutParams) mBehaviorTouchView.getLayoutParams();
            final Behavior b = lp.getBehavior();
            if (b != null) {
                handled = b.onTouchEvent(this, mBehaviorTouchView, ev);
            }
        }

        // Keep the super implementation correct
       // 如果是 Behaivor 不攔截,則交給 CoordinatorLayout 的父類處理,按照事件傳遞流程處理
        if (mBehaviorTouchView == null) {
            handled |= super.onTouchEvent(ev);
        } else if (cancelSuper) {
            if (cancelEvent == null) {
                final long now = SystemClock.uptimeMillis();
                cancelEvent = MotionEvent.obtain(now, now,
                        MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
            }
            super.onTouchEvent(cancelEvent);
        }

        if (!handled && action == MotionEvent.ACTION_DOWN) {

        }

        if (cancelEvent != null) {
            cancelEvent.recycle();
        }

        // ACTION_UP 事件重置 Behavior
        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
            resetTouchBehaviors(false);
        }

        return handled;
    }

CoordinatorLayout 的嵌套滑動(dòng)

CoordinatorLayout 是實(shí)現(xiàn)了 NestedScrollingParent2,NestedScrollingParent2 繼承了 NestedScrollingParent

NestedScrollingParent2.java 的接口

// This interface should be implemented by ViewGroup  
// subclasses that wish to support scrolling operations 
// delegated by a nested child view
public interface NestedScrollingParent2 extends NestedScrollingParent {

     boolean onStartNestedScroll(@NonNull View child, @NonNull View target, @ScrollAxis int axes,
            @NestedScrollType int type);
            
     boolean onStartNestedScroll(@NonNull View child, @NonNull View target, @ScrollAxis int axes,
            @NestedScrollType int type);
            
     void onNestedScrollAccepted(@NonNull View child, @NonNull View target, @ScrollAxis int axes,
            @NestedScrollType int type);
            
    void onStopNestedScroll(@NonNull View target, @NestedScrollType int type);
    
    void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed,
            int dxUnconsumed, int dyUnconsumed, @NestedScrollType int type);
            
    void onNestedPreScroll(@NonNull View target, int dx, int dy, @Nullable int[] consumed,
            @NestedScrollType int type)
            
}

在 CoordinatorLayout 滑動(dòng)的時(shí)候,實(shí)現(xiàn)這些滑動(dòng)方法,都會(huì)直接傳入到 Behavior 中對(duì)應(yīng)的方法,例如 CoordinatorLayout#onStartNestedScroll 會(huì)分發(fā)到 Behavior#onStartNestedScroll


@Override
public boolean onStartNestedScroll(View child, View target, int axes, int type) {
    boolean handled = false;

    final int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        final View view = getChildAt(i);
        if (view.getVisibility() == View.GONE) {
            // If it's GONE, don't dispatch
            continue;
        }
        final LayoutParams lp = (LayoutParams) view.getLayoutParams();
        final Behavior viewBehavior = lp.getBehavior();
        if (viewBehavior != null) {
            // 分發(fā)到 Behavior#onStartNestedScroll
            final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child,
                    target, axes, type);
            handled |= accepted;
            lp.setNestedScrollAccepted(type, accepted);
        } else {
            lp.setNestedScrollAccepted(type, false);
        }
    }
    return handled;
}

關(guān)于嵌套滑動(dòng),在后續(xù)文章中再細(xì)說。

當(dāng)手指滑動(dòng)的范圍在 AppBarLayout 內(nèi)滑動(dòng),CoordinatorLayout 會(huì)通過 NestScrollView 的 Behavior 分發(fā)事件,讓 NestScrollView 產(chǎn)生滑動(dòng);
當(dāng)手指滑動(dòng)的范圍在 NestScrollView 內(nèi)滑動(dòng),CoordinatorLayout 會(huì)通過 AppBarLayout 的 Behavior 分發(fā)事件,讓 AppBarLayout 產(chǎn)生滑;AppBarLayout 默認(rèn)的 AppBarLayout#Behavior, 不需要顯式指定 Behavior。

AppBarLayout 滑動(dòng)監(jiān)聽

自定義 AppBarStateChangeListener, 同時(shí)定義 AppBarLayout 的狀態(tài) EXPANDED 展開,COLLAPSED 折疊 和 IDLE 轉(zhuǎn)態(tài)

public abstract class AppBarStateChangeListener implements AppBarLayout.OnOffsetChangedListener {

    private State mCurrentState = State.IDLE;

    @Override
    public final void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
        if (verticalOffset == 0) {
            if (mCurrentState != State.EXPANDED) {
                onStateChanged(appBarLayout, State.EXPANDED, verticalOffset);
            }
            mCurrentState = State.EXPANDED;
        } else if (Math.abs(verticalOffset) >= appBarLayout.getTotalScrollRange()) {
            if (mCurrentState != State.COLLAPSED) {
                onStateChanged(appBarLayout, State.COLLAPSED, verticalOffset);
            }
            mCurrentState = State.COLLAPSED;
        } else {
            if (mCurrentState != State.IDLE) {
                onStateChanged(appBarLayout, State.IDLE, verticalOffset);
            }
            mCurrentState = State.IDLE;

        }
    }

    public abstract void onStateChanged(AppBarLayout appBarLayout, State state, int verticalOffset);
}

enum State {
    EXPANDED,
    COLLAPSED,
    IDLE
}

然后在 AppBarLayout 中設(shè)置監(jiān)聽

appBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
        @Override
        public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
            // 進(jìn)行操作
    });

這個(gè)回調(diào)是在 AppBarLayout$Behavior#setHeaderTopBottomOffset 方法中回調(diào)的。當(dāng) AppBarLayout 在進(jìn)行滑動(dòng)的時(shí)候,會(huì)調(diào)用 setHeaderTopBottomOffset 方法,在 setHeaderTopBottomOffset在中

@Override
    int setHeaderTopBottomOffset(CoordinatorLayout coordinatorLayout,
            AppBarLayout appBarLayout, int newOffset, int minOffset, int maxOffset) {
        final int curOffset = getTopBottomOffsetForScrollingSibling();
        int consumed = 0;

        if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) {
            // If we have some scrolling range, and we're currently within the min and max
            // offsets, calculate a new offset
            newOffset = MathUtils.clamp(newOffset, minOffset, maxOffset);
            if (curOffset != newOffset) {
                final int interpolatedOffset = appBarLayout.hasChildWithInterpolator()
                        ? interpolateOffset(appBarLayout, newOffset)
                        : newOffset;

                final boolean offsetChanged = setTopAndBottomOffset(interpolatedOffset);

                // Update how much dy we have consumed
                consumed = curOffset - newOffset;
                // Update the stored sibling offset
                mOffsetDelta = newOffset - interpolatedOffset;

                if (!offsetChanged && appBarLayout.hasChildWithInterpolator()) {
                    // If the offset hasn't changed and we're using an interpolated scroll
                    // then we need to keep any dependent views updated. CoL will do this for
                    // us when we move, but we need to do it manually when we don't (as an
                    // interpolated scroll may finish early).
                    coordinatorLayout.dispatchDependentViewsChanged(appBarLayout);
                }

// 分發(fā)到監(jiān)聽
appBarLayout.dispatchOffsetUpdates(getTopAndBottomOffset());

                // Update the AppBarLayout's drawable state (for any elevation changes)
                updateAppBarLayoutDrawableState(coordinatorLayout, appBarLayout, newOffset,
                        newOffset < curOffset ? -1 : 1, false);
            }
        } else {
            // Reset the offset delta
            mOffsetDelta = 0;
        }

        return consumed;
    }    
}    

// 分發(fā)到所有的監(jiān)聽器
void dispatchOffsetUpdates(int offset) {
if (mListeners != null) {
    for (int i = 0, z = mListeners.size(); i < z; i++) {
        final OnOffsetChangedListener listener = mListeners.get(i);
        if (listener != null) {
            listener.onOffsetChanged(this, offset);
        }
    }
}

注意事項(xiàng)

  1. 如果 CoordinatorLayout 里面使用了 ScrollView 或者 ViewPager, 需要指定 Bebeavior
<android.support.design.widget.CoordinatorLayout...>
    <android.support.design.widget.AppBarLayout...>
        <android.support.design.widget.CollapsingToolbarLayout...>
            <!-- your collapsed view -->
            <View.../>
            <android.support.v7.widget.Toolbar.../>
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <!-- 需要指定 appbar_scrolling_view_behavior -->
   <android.support.v4.view.ViewPager
        android:id="@+id/viewpager"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
</android.support.design.widget.CoordinatorLayout>

參考

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

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

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