1. 程式人生 > >安卓behavior詳解3--自定義behavior詳解

安卓behavior詳解3--自定義behavior詳解

一.前言

官方定義:
A Behavior implements one or more interactions that a user can take on a child view. These interactions may include drags, swipes, flings, or any other gestures.
中文:
一個Behavior實現了一個或多個使用者可以採取的互動檢視。這些相互作用可能包括拖動,滑動,快速滑動,或任何其他的手勢。

二.類似觀察者概念

其實Behavior就是一個應用於View的觀察者模式,一個View跟隨者另一個View的變化而變化,或者說一個View監聽另一個View。

在Behavior中,被觀察的View 也就是事件源被稱為denpendcy,而觀察View,則被稱為child

Behavior內部的方法當在CoordinatorLayout上發生觸控事件的時候,CoordinatorLayout會處理觸控事件,並回調Behavior中的部分方法,我們可以通過處理這些回撥方法來實現需求

三.Behavior中的幾個重要方法

Behavior 是一個頂層抽象類,其他的一些具體行為的Behavior 都是繼承自這個類。它提供了幾個重要的方法:
layoutDependsOn
onDependentViewChanged
onStartNestedScroll
onNestedPreScroll
onNestedScroll
onStopNestedScroll
onNestedScrollAccepted
onNestedPreFling
onLayoutChild

    /**
     * 表示是否給應用了Behavior 的View 指定一個依賴的佈局,通常,當依賴的View 佈局發生變化時,不管被被依賴View 的順序怎樣,被依賴的View也會重新佈局
     * @param parent
     * @param child 繫結behavior 的View
     * @param dependency 依賴的view
     * @return 如果child 是依賴的指定的View 返回true,否則返回false
     */
    @Override
    public boolean layoutDependsOn
(CoordinatorLayout parent, View child, View dependency) { return super.layoutDependsOn(parent, child, dependency); } /** * 當被依賴的View 狀態(位置、大小等)發生變化時,這個方法被呼叫 * @param parent * @param child * @param dependency * @return */ @Override public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { return super.onDependentViewChanged(parent, child, dependency); } /** * 當coordinatorLayout 的子View試圖開始巢狀滑動的時候被呼叫。 * 當返回值為true的時候表明coordinatorLayout 充當nested scroll parent 處理這次滑動,需要注意的是隻有當返回值為true的時候,Behavior 才能收到後面的一些nested scroll 事件回撥(如:onNestedPreScroll、onNestedScroll等)這個方法有個重要的引數 nestedScrollAxes,表明處理的滑動的方向。 * @param coordinatorLayout 和Behavior 繫結的View的父CoordinatorLayout * @param child 和Behavior 繫結的View * @param directTargetChild * @param target * @param nestedScrollAxes 巢狀滑動 應用的滑動方向,看 {@link ViewCompat#SCROLL_AXIS_HORIZONTAL}, {@link ViewCompat#SCROLL_AXIS_VERTICAL} * @return */ @Override public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) { return super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); } /** * 巢狀滾動發生之前被呼叫 在nested scroll child 消費掉自己的滾動距離之前,巢狀滾動每次被nested scroll child 更新都會呼叫onNestedPreScroll。注意有個重要的引數consumed,可以修改這個數 組表示你消費了多少距離。假設使用者滑動了100px,child 做了90px 的位移,你需要 把 consumed[1]的值改成90, * 這樣coordinatorLayout就能知道只處理剩下的10px的滾動。 * @param coordinatorLayout * @param child * @param target * @param dx 使用者水平方向的滾動距離 * @param dy 使用者豎直方向的滾動距離 * @param consumed */ @Override public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) { super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed); } /** * 進行巢狀滾動時被呼叫 * @param coordinatorLayout * @param child * @param target * @param dxConsumed target 已經消費的x方向的距離 * @param dyConsumed target 已經消費的y方向的距離 * @param dxUnconsumed x 方向剩下的滾動距離 * @param dyUnconsumed y 方向剩下的滾動距離 */ @Override public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); } /** * 巢狀滾動結束時被呼叫,這是一個清除滾動狀態等的好時機。 * @param coordinatorLayout * @param child * @param target */ @Override public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) { super.onStopNestedScroll(coordinatorLayout, child, target); } /** * onStartNestedScroll返回true才會觸發這個方法,接受滾動處理後回撥,可以在這個 方法裡做一些準備工作,如一些狀態的重置等。 * @param coordinatorLayout * @param child * @param directTargetChild * @param target * @param nestedScrollAxes */ @Override public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) { super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); } /** * 使用者鬆開手指並且會發生慣性動作之前呼叫,引數提供了速度資訊,可以根據這些 速度資訊決定最終狀態,比如滾動Header,是讓Header處於展開狀態還是摺疊狀 態。返回true 表示消費了fling. * @param coordinatorLayout * @param child * @param target * @param velocityX x方向的速度 * @param velocityY y方向的速度 * @return */ @Override public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) { return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY); } /* * 可以重寫這個方法對子View 進行重新佈局 * @param coordinatorLayout * @param child * @param layoutDirection 佈局的方向 */ @Override public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) { return super.onLayoutChild(parent, child, layoutDirection); }

四.巢狀滾動原理和CoordinatorLayout

這裡寫圖片描述

NestedScrolling提供了一套父View和子View滑動互動機制。要完成這樣的互動,父View需要實現NestedScrollingParent介面,而子View需要實現NestedScrollingChild介面,系統提供的NestedScrollView控制元件就實現了這兩個介面,這兩個介面雖然有很多抽象方法,但均有NestedScrolling[Parent,Children]Helper輔助類來幫助處理大部分邏輯

這裡寫圖片描述

NestedScroll的機制的簡版是這樣的,當子View在處理滑動事件之前,先告訴自己的父View是否需要先處理這次滑動事件,父View處理完之後,告訴子View它處理的多少滑動距離,剩下的還是交給子View自己來處理

CoordinatorLayout的事件傳遞

CoordinatorLayout並不會直接處理觸控事件,而是儘可能地先交由子View的Behavior來處理,它的onInterceptTouchEvent和onTouchEvent兩個方法最終都是呼叫performIntercept方法,用來分發不同的事件型別分發給對應的子View的Behavior處理

//處理攔截或者自己的觸控事件
private boolean performIntercept(MotionEvent ev, final int type) {
    boolean intercepted = false;
    boolean newBlock = false;

    MotionEvent cancelEvent = null;

    final int action = MotionEventCompat.getActionMasked(ev);

    final List<View> topmostChildList = mTempList1;
    getTopSortedChildren(topmostChildList); //在5.0以上,按照z屬性來排序,以下,則是按照新增順序或者自定義的繪製順序來排列

    // 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();
        // 如果有一個behavior對事件進行了攔截,就傳送Cancel事件給後續的所有Behavior。假設之前還沒有Intercept發生,那麼所有的事件都平等地對所有含有behavior的view進行分發,現在intercept忽然出現,那麼相應的我們就要對除了Intercept的view發出Cancel
        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.
            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:
                    intercepted = b.onInterceptTouchEvent(this, child, ev);
                    break;
                case TYPE_ON_TOUCH:
                    intercepted = b.onTouchEvent(this, child, ev);  
                    break;
            }
            if (intercepted) {
                mBehaviorTouchView = child; //記錄當前需要處理事件的View
            }
        }

        // Don't keep going if we're not allowing interaction below this.
        // Setting newBlock will make sure we cancel the rest of the behaviors.
        final boolean wasBlocking = lp.didBlockInteraction();
        final boolean isBlocking = lp.isBlockingInteractionBelow(this, child); //behaviors是否攔截事件
        newBlock = isBlocking && !wasBlocking;
        if (isBlocking && !newBlock) {
            // Stop here since we don't have anything more to cancel - we already did
            // when the behavior first started blocking things below this point.
            break;
        }
    }
    topmostChildList.clear();
    return intercepted;
}