Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> CoordinatorLayout裡Behavior簡單分析

CoordinatorLayout裡Behavior簡單分析

編輯:關於Android編程

  當給CoordinatorLayout的直接子View設置Behavior的時候,CoordinatorLayout裡面的一些事件就可以傳入到Behavior,Behavior又可以指導View做一些相應的處理。

  Behavior對象是怎麼被實例化的

  CoordinatorLayout裡面的子View是怎麼拿到Behavior對象的。設置Bevior有兩種方式

  1. 代碼裡面直接設置(LayoutParams)child.getLayoutParams().setBehavior()這個就好說了。

  2. XML裡面去設置app:layout_behavior=”” 。

  對應第二種情況Behavior是怎麼實例化的。

  CoordinatorLayout裡面LayoutParams兩個參數的構造函數。
 

LayoutParams(Context context, AttributeSet attrs) {
    super(context, attrs);

    final TypedArray a = context.obtainStyledAttributes(attrs,
            R.styleable.CoordinatorLayout_LayoutParams);

    this.gravity = a.getInteger(
            R.styleable.CoordinatorLayout_LayoutParams_android_layout_gravity,
            Gravity.NO_GRAVITY);
    mAnchorId = a.getResourceId(R.styleable.CoordinatorLayout_LayoutParams_layout_anchor,
            View.NO_ID);
    this.anchorGravity = a.getInteger(
            R.styleable.CoordinatorLayout_LayoutParams_layout_anchorGravity,
            Gravity.NO_GRAVITY);

    this.keyline = a.getInteger(R.styleable.CoordinatorLayout_LayoutParams_layout_keyline,
            -1);

    mBehaviorResolved = a.hasValue(
            R.styleable.CoordinatorLayout_LayoutParams_layout_behavior);
    if (mBehaviorResolved) {
        mBehavior = parseBehavior(context, attrs, a.getString(
                R.styleable.CoordinatorLayout_LayoutParams_layout_behavior));
    }

    a.recycle();
}

 

  第20行是否設置了app:layout_behavior=”” 如果設置了調用parseBehavior函數。利用反射去實例化出Behavior對象。

  Behavior簡單分析

  Behavior都是配合CoordinatorLayout來使用的。

  對於Behavior我們分三種情況來考慮。

  1. Behavior的onInterceptTouchEvent + onTouchEvent。

  2. Behavior的layoutDependsOn + onDependentViewChanged + onDependentViewRemoved。View引起的變化。

  3. Behavior的onStartNestedScroll + onNestedScrollAccepted + onStopNestedScroll + onNestedScroll + onNestedPreScroll + onNestedFling + onNestedPreFling。嵌套滑動引起的變化。

  大概的去知道每種情況裡面每個函數的調用時機。和大概的作用。

  Behavior的onInterceptTouchEvent + onTouchEvent

  分兩部分來考慮。

  1. CoordinatorLayout裡面是怎麼調用到我們指定View的Behavior的onInterceptTouchEvent和onTouchEvent裡面去的。

  2. View的Behavior裡面onInterceptTouchEvent和onTouchEvent裡面干了什麼事情。(這個一般是我們自定義Behavior的時候處理,這個先不管)。

  CoordinatorLayout裡面是怎麼調用到我們指定View的Behavior的onInterceptTouchEvent和onTouchEvent裡面去的。直接CoordinatorLayout的onInterceptTouchEvent函數和onTouchEvent函數裡面了先CoordinatorLayout類的onInterceptTouchEvent函數。
 

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

    final int action = MotionEventCompat.getActionMasked(ev);

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

    final boolean intercepted = performIntercept(ev, TYPE_ON_INTERCEPT);

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

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

    return intercepted;
}

 

  第12行調用了performIntercept函數,參數type TYPE_ON_INTERCEPT表示從onInterceptTouchEvent函數進來的,TYPE_ON_TOUCH表示從onTouchEvent函數進來的。
 

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);

    // 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.
            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;
            }
        }

        // 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);
        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;
}

 

  第14行開始,遍歷CoordinatorLayout所有的View,第19行第一個if做的事情是如果現在已經有View對應的Behavoir攔截了的,並且不是ACTION_DOWN的時候,其他View的Behavoir都會收到ACTION_CANCEL事件。

  第40行第二個if如果沒有被攔截並且有Behavoir則調用Behavoir對應的函數onInterceptTouchEvent或者onTouchEvent函數。

  CoordinatorLayout的onTouchEvent函數
 

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

    final int action = MotionEventCompat.getActionMasked(ev);

    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
    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();
    }

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

    return handled;
}

 

  CoordinatorLayout的onTouchEvent函數就是看下當前事件是不是有哪個View的Behavior感興趣。如果感興趣就給View的Behavior做處理,不敢興趣就給CoordinatorLayout自己處理。

  這樣Behavior的onInterceptTouchEvent + onTouchEvent。兩個函數的調用也引導進去了。

  Behavior的layoutDependsOn + onDependentViewChanged + onDependentViewRemoved
 


   /**
     * child是否要依賴dependency
     * @param parent     CoordinatorLayout
     * @param child      該Behavior對應的那個View
     * @param dependency 要檢查的View(child是否要依賴這個dependency)
     * @return true 依賴, false 不依賴
     */
    public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) {
        return false;
    }

    /**
     * 在layoutDependsOn返回true的基礎上之後,及時報告dependency的狀態變化
     * @param parent     CoordinatorLayout
     * @param child      該Behavior對應的那個View
     * @param dependency child依賴dependency
     * @return true 處理了, false  沒處理
     */
    public boolean onDependentViewChanged(CoordinatorLayout parent, V child, View dependency) {
        return false;
    }

    /**
     * 在layoutDependsOn返回true的基礎上之後,報告dependency被移除了
     * @param parent     CoordinatorLayout
     * @param child      該Behavior對應的那個View
     * @param dependency child依賴dependency
     */
    public void onDependentViewRemoved(CoordinatorLayout parent, V child, View dependency) {
    }

 

  所有的源頭都在CoordinatorLayout類裡面

  CoordinatorLayout類裡面onAttachedToWindow
 

@Override
public void onAttachedToWindow() {
    super.onAttachedToWindow();
    resetTouchBehaviors();
    if (mNeedsPreDrawListener) {
        if (mOnPreDrawListener == null) {
            mOnPreDrawListener = new OnPreDrawListener();
        }
        final ViewTreeObserver vto = getViewTreeObserver();
        vto.addOnPreDrawListener(mOnPreDrawListener);
    }
    if (mLastInsets == null && ViewCompat.getFitsSystemWindows(this)) {
        // We're set to fitSystemWindows but we haven't had any insets yet...
        // We should request a new dispatch of window insets
        ViewCompat.requestApplyInsets(this);
    }
    mIsAttachedToWindow = true;
}

 

  第10行給ViewTreeObserver添加了一個OnPreDrawListener的監聽。OnPreDrawListener代碼如下
 

class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
    @Override
    public boolean onPreDraw() {
        dispatchOnDependentViewChanged(false);
        return true;
    }
}

 

  OnPreDrawListener實現了ViewTreeObserver.OnPreDrawListener接口重寫了onPreDraw函數,onPreDraw在每次draw的時候都會調用。就是在CoordinatorLayout每次重繪的時候調用。View狀態發生變化的時候調用。這樣重點就到了dispatchOnDependentViewChanged函數了參數false表示不是嵌套滑動引起的變化是View狀態改變引起的變化。
 

void dispatchOnDependentViewChanged(final boolean fromNestedScroll) {
    final int layoutDirection = ViewCompat.getLayoutDirection(this);
    final int childCount = mDependencySortedChildren.size();
    for (int i = 0; i < childCount; i++) {
        final View child = mDependencySortedChildren.get(i);
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();

        // Check child views before for anchor
        for (int j = 0; j < i; j++) {
            final View checkChild = mDependencySortedChildren.get(j);

            if (lp.mAnchorDirectChild == checkChild) {
                offsetChildToAnchor(child, layoutDirection);
            }
        }

        // Did it change? if not continue
        final Rect oldRect = mTempRect1;
        final Rect newRect = mTempRect2;
        getLastChildRect(child, oldRect);
        getChildRect(child, true, newRect);
        if (oldRect.equals(newRect)) {
            continue;
        }
        recordLastChildRect(child, newRect);

        // Update any behavior-dependent views for the change
        for (int j = i + 1; j < childCount; j++) {
            final View checkChild = mDependencySortedChildren.get(j);
            final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams();
            final Behavior b = checkLp.getBehavior();

            if (b != null && b.layoutDependsOn(this, checkChild, child)) {
                if (!fromNestedScroll && checkLp.getChangedAfterNestedScroll()) {
                    // If this is not from a nested scroll and we have already been changed
                    // from a nested scroll, skip the dispatch and reset the flag
                    checkLp.resetChangedAfterNestedScroll();
                    continue;
                }

                final boolean handled = b.onDependentViewChanged(this, checkChild, child);

                if (fromNestedScroll) {
                    // If this is from a nested scroll, set the flag so that we may skip
                    // any resulting onPreDraw dispatch (if needed)
                    checkLp.setChangedAfterNestedScroll(handled);
                }
            }
        }
    }
}

 

  第3行 mDependencySortedChildren裡面放的是CoordinatorLayout所有的子View,只不過把相關連的View放到一起了(相關聯有兩種一種是直接xml裡面設置了layout_anchor,一種是Behavior裡面設置了關聯layoutDependsOn函數)。接著遍歷所有的View。

  第9行到15行找到哪個View設置了layout_anchor也就是說設置顯示坐標的錨點 哪個View的layout_anchor對應child。調整位置,這個應該好理解點layout_anchor設置的View和當前的View是相關會一起變化的。

  第28行到49行找到哪個View的Behavior depend on child這個View。第0行調用了Behavior的layoutDependsOn判斷是否依賴。如果依賴繼續調用Behavior的onDependentViewChanged函數。

  到這裡Behavior的layoutDependsOn和onDependentViewChanged的調用的地方和調用的時機我們都知道了。還差一個onDependentViewRemoved函數。繼續看CoordinatorLayout裡面的HierarchyChangeListener裡 在哪裡用到了呢構造函數裡面super.setOnHierarchyChangeListener(new HierarchyChangeListener());設置了去監聽ViewGroup中的View的層次變化當View removed掉的時候這裡能夠監聽到了會調用onChildViewRemoved。
 

final class HierarchyChangeListener implements OnHierarchyChangeListener {
    @Override
    public void onChildViewAdded(View parent, View child) {
        if (mOnHierarchyChangeListener != null) {
            mOnHierarchyChangeListener.onChildViewAdded(parent, child);
        }
    }

    @Override
    public void onChildViewRemoved(View parent, View child) {
        dispatchDependentViewRemoved(child);

        if (mOnHierarchyChangeListener != null) {
            mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
        }
    }
}

 

  繼續dispatchDependentViewRemoved函數。
 

void dispatchDependentViewRemoved(View removedChild) {
    final int childCount = getChildCount();
    for (int i = 0; i < childCount; i++) {
        final View child = getChildAt(i);
        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
        final Behavior b = lp.getBehavior();

        if (b != null && b.layoutDependsOn(this, child, removedChild)) {
            b.onDependentViewRemoved(this, child, removedChild);
        }
    }
}

 

  找到對應View的Behavior 如果是layoutDependsOn的就去調用Behavior的onDependentViewRemoved。

  到此Behavior的layoutDependsOn + onDependentViewChanged + onDependentViewRemoved三個函數的調用時機和調用順序都結束了。

  Behavior的onStartNestedScroll + onNestedScrollAccepted + onStopNestedScroll + onNestedScroll + onNestedPreScroll + onNestedFling + onNestedPreFling
 


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

    /**
     * Behavior接受了嵌套滑動的請求該函數調用。onStartNestedScroll返回true該函數會被調用。 參數和onStartNestedScroll一樣
     */
    public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout,
                                       V child,
                                       View directTargetChild,
                                       View target,
                                       int nestedScrollAxes) {
        // Do nothing
    }

    /**
     * 停止嵌套滑動
     *
     * @param coordinatorLayout CoordinatorLayout
     * @param child             該Behavior對應的View
     * @param target            具體嵌套滑動的那個子類
     */
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
        // Do nothing
    }

    /**
     * 嵌套滑動的子View在滑動之後報告過來的滑動情況
     *
     * @param coordinatorLayout CoordinatorLayout
     * @param child             該Behavior對應的View
     * @param target            具體嵌套滑動的那個子類
     * @param dxConsumed        水平方向嵌套滑動的子View滑動的距離(消耗的距離)
     * @param dyConsumed        垂直方向嵌套滑動的子View滑動的距離(消耗的距離)
     * @param dxUnconsumed      水平方向嵌套滑動的子View未滑動的距離(未消耗的距離)
     * @param dyUnconsumed      垂直方向嵌套滑動的子View未滑動的距離(未消耗的距離)
     */
    public void onNestedScroll(CoordinatorLayout coordinatorLayout,
                               V child,
                               View target,
                               int dxConsumed,
                               int dyConsumed,
                               int dxUnconsumed,
                               int dyUnconsumed) {
        // Do nothing
    }

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

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

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

 

  嵌套滑動引起的變化,根據前一篇文章Android 嵌套滑動分析的分析CoordinatorLayout 實現了 NestedScrollingParent,有嵌套滑動的時候會調用到CoordinatorLayout裡面的onStartNestedScroll onNestedScrollAccepted onStopNestedScroll onNestedScroll onNestedPreScroll onNestedFling onNestedPreFling這些函數。至於是怎麼調用到的這些函數可以看看Android 嵌套滑動分析的介紹。這些函數裡面做的事情也都是大同小異的都是直接過度給了Behavior裡面對應的函數。

  這樣我們就知道了Behavior的onStartNestedScroll + onNestedScrollAccepted + onStopNestedScroll + onNestedScroll + onNestedPreScroll + onNestedFling + onNestedPreFling。的調用時機是有嵌套滑動的時候會被調用到。

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