<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                在上一篇博客[CoordinatorLayout高級用法-自定義Behavior](http://blog.csdn.net/qibin0506/article/details/50290421)中,我們介紹了如何去自定義一個CoordinatorLayout的Behavior,通過文章也可以看出Behavior在CoordinatorLayout中地位是相當高的,那么今天我們就來接著上篇博客來從源碼分析一下Behavior的實現思路,如果你對CoordinatorLayout和Behavior還不熟悉的話,建議先去看看上篇博客[《CoordinatorLayout高級用法-自定義Behavior》](http://blog.csdn.net/qibin0506/article/details/50290421)。 這篇文章我們要分析的內容有: > 1. Behavior的實例化 > 1. layoutDependsOn和onDependentViewChanged調用過程 > 1. onStartNestedScroll和onNestedPreScroll實現原理 > 1. Behavior的事件分發過程 ### Behavior的實例化 大家都知道,我們在view中可以通過`app:layout_behavior`然后指定一個字符串來表示使用哪個behavior,稍微去想一下,在CoordinatorLayout中肯定是利用反射機制來完成的behavior的實例化,現在就讓我們從CoordinatorLayout的源碼中找尋答案,來驗證我們的猜想。首先,我們來看看CoordinatorLayout的一個內部類,也是大家熟悉的`LayoutParams`, ~~~ public static class LayoutParams extends ViewGroup.MarginLayoutParams { /** * A {@link Behavior} that the child view should obey. */ Behavior mBehavior; ... } ~~~ 在這里我們確實看到了behavior的影子,那它是在什么時候被初始化的呢?繼續看代碼, ~~~ LayoutParams(Context context, AttributeSet attrs) { super(context, attrs); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CoordinatorLayout_LayoutParams); ... 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(); } ~~~ 在LayoutParams的構造方法中,首先是去檢查了是不是有`layout_behavior`,這里很容易理解,接下來調用了`parseBehavior`方法,返回了Behavior的實例,我們非常有理由去看看`parseBehavior`到底干了嘛,或許我們要的答案就在里面! ~~~ // 這里是指定的Behavior的參數類型 static final Class<?>[] CONSTRUCTOR_PARAMS = new Class<?>[] { Context.class, AttributeSet.class }; ... static Behavior parseBehavior(Context context, AttributeSet attrs, String name) { if (TextUtils.isEmpty(name)) { return null; } // 代表了我們指定的那個behavior的完整路徑 final String fullName; // 如果是".MyBehavior" // 則在前面加上程序的包名 if (name.startsWith(".")) { // Relative to the app package. Prepend the app package name. fullName = context.getPackageName() + name; } else if (name.indexOf('.') >= 0) { // 這里我們指定了全名 // Fully qualified package name. fullName = name; } else { // Assume stock behavior in this package (if we have one) fullName = !TextUtils.isEmpty(WIDGET_PACKAGE_NAME) ? (WIDGET_PACKAGE_NAME + '.' + name) : name; } try { Map<String, Constructor<Behavior>> constructors = sConstructors.get(); if (constructors == null) { constructors = new HashMap<>(); sConstructors.set(constructors); } Constructor<Behavior> c = constructors.get(fullName); // 這里利用反射去實例化了指定的Behavior // 并且值得注意到是,這里指定了構造的參數類型 // 也就是說我們在自定義Behavior的時候,必須要有這種類型的構造方法 if (c == null) { final Class<Behavior> clazz = (Class<Behavior>) Class.forName(fullName, true, context.getClassLoader()); c = clazz.getConstructor(CONSTRUCTOR_PARAMS); c.setAccessible(true); constructors.put(fullName, c); } return c.newInstance(context, attrs); } catch (Exception e) { throw new RuntimeException("Could not inflate Behavior subclass " + fullName, e); } } ~~~ 上面的代碼很容易理解,就是利用反射機制去實例化了Behavior,調用的是兩個參數的那個構造方法,這也就是我們在自定義Behavior的時候為什么一定要去重寫, ~~~ public Behavior(Context context, AttributeSet attrs) { super(context, attrs); } ~~~ 這個構造的原因。看來獲取一個Behavior的實例還是很簡單的,那么,下面就讓我們開始分析Behavior中常用方法調用的機制吧。 ### layoutDependsOn和onDependentViewChanged調用過程 在上一篇博客中我們學會了自定義兩種形式的Behavior,其中第一種就是去觀察一個view的狀態變化,也就是涉及到`layoutDependsOn`和`onDependentViewChanged`兩個方法的調用,現在我們從源碼的角度來分析一下這兩個方法調用的時機和調用的過程,在前一篇博客中我們提到過`onDependentViewChanged`這個方法會在view的狀態發生變化后去調用,那在狀態發生變化時必定會執行什么操作呢?重繪,是的,狀態變化了,那肯定重繪是避免不了的,在`CoordinatorLayout`中注冊了一個`ViewTreeObserver`,我們可以從這里入手,因為它可以監聽到view的各種狀態變化, ~~~ @Override public void onAttachedToWindow() { super.onAttachedToWindow(); resetTouchBehaviors(); if (mNeedsPreDrawListener) { if (mOnPreDrawListener == null) { // 實例化了OnPreDrawListener // 并在下面注冊到了ViewTreeObserver中 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; } ~~~ 在`onAttachedToWindow`向ViewTreeObserver注冊了一個監聽draw變化的Observer,那在這里Observer中到底干了嘛呢? ~~~ class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener { @Override public boolean onPreDraw() { dispatchOnDependentViewChanged(false); return true; } } ~~~ 就兩行代碼,調用了`dispatchOnDependentViewChanged`方法,看方法名我們就知道這次找對對象了,懷著激動的心情來看看`dispatchOnDependentViewChanged` ~~~ void dispatchOnDependentViewChanged(final boolean fromNestedScroll) { final int layoutDirection = ViewCompat.getLayoutDirection(this); final int childCount = mDependencySortedChildren.size(); // 遍歷所有的子view for (int i = 0; i < childCount; i++) { final View child = mDependencySortedChildren.get(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); ... // 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; } // Update any behavior-dependent views for the change // 這里從下一個子view開始 //mDependencySortedChildren有一個排序規則 // selectionSort // 感興趣的可以看一下mDependencySortedChildren部分。 for (int j = i + 1; j < childCount; j++) { final View checkChild = mDependencySortedChildren.get(j); final LayoutParams checkLp = (LayoutParams) checkChild.getLayoutParams(); // 獲取到Behavior final Behavior b = checkLp.getBehavior(); // 這里調用Behavior的layoutDependsOn來判斷我們的帶有behavior的view是不是依賴這個view 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; } // 這里調用了Behavior的onDependentViewChanged final boolean handled = b.onDependentViewChanged(this, checkChild, child); ... } } } } ~~~ `dispatchOnDependentViewChanged`方法有一個布爾類型的參數,上面我們傳遞的是false, 這里主要是區分是view引起的狀態變化還是布局引起的,在一些的scroll中也會調用`dispatchOnDependentViewChanged`這個方法。 好了,現在我們終于搞懂了`onDependentViewChanged`調用機制了,下面我們來看看關于滑動監聽的部分。 ### onStartNestedScroll和onNestedPreScroll實現原理 在開始源碼之前,我們先來思考個問題,現在有一個view是可以上下滑動的,那這個view的滑動對于父view來說是不是可見的?或者說是可預知的?顯然不是,一個view的滑動對于父布局來說是透明的?所以現在我們不能簡簡單單的從`CoordinatorLayout`入手了,而是要從那個可以滑動的view入手,我們選擇`NestedScrollView`來進行分析。`NestedScrollView`有一個`NestedScrollingChildHelper`類型的變量`mChildHelper`引起了我們的注意,因為很多看名字很像關于滑動部分的代碼都調用了這個類的一些方法,來看看有哪些吧? ~~~ mChildHelper = new NestedScrollingChildHelper(this); ... @Override public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) { mParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes); startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); } @Override public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { final int oldScrollY = getScrollY(); scrollBy(0, dyUnconsumed); final int myConsumed = getScrollY() - oldScrollY; final int myUnconsumed = dyUnconsumed - myConsumed; dispatchNestedScroll(0, myConsumed, 0, myUnconsumed, null); } @Override public boolean startNestedScroll(int axes) { return mChildHelper.startNestedScroll(axes); } @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 onInterceptTouchEvent(MotionEvent ev) { ... switch (action & MotionEventCompat.ACTION_MASK) { ... case MotionEvent.ACTION_DOWN: { ... startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); } ... } ... } ~~~ 在down的時候我們調用了`startNestedScroll`方法,那我們就順著這條線往下看`mChildHelper.startNestedScroll(axes)`。 ~~~ public boolean startNestedScroll(int axes) { if (hasNestedScrollingParent()) { // Already in progress return true; } if (isNestedScrollingEnabled()) { // 獲取當前view的parent ViewParent p = mView.getParent(); View child = mView; // 一個循環,不斷的往上層去獲取parent // 直到條件成立,或者沒有parent了 退出 while (p != null) { // 這里是關鍵代碼,猜測這里肯定肯定去調用了CoordinatorLayout的對應方法。 if (ViewParentCompat.onStartNestedScroll(p, child, mView, axes)) { mNestedScrollingParent = p; ViewParentCompat.onNestedScrollAccepted(p, child, mView, axes); return true; } if (p instanceof View) { child = (View) p; } // 替換,繼續循環 p = p.getParent(); } } return false; } ~~~ 在這個方法中一個while循環,不斷的去獲取view的的parent,然后一個`ViewParentCompat.onStartNestedScroll`作為條件成立了就return true了,我們有理由猜測`ViewParentCompat.onStartNestedScroll`里去調用了`CoordinatorLayout`的相應方法。注意參數,p是我們遍歷到父view,我們先認為是`CoordinatorLayout`吧,child是`CoordinatorLayout`的直接嵌套著目標view的子view,mView在這里就是`NestedScrollView`了。 ~~~ public class ViewParentCompat { static class ViewParentCompatStubImpl implements ViewParentCompatImpl { @Override public boolean onStartNestedScroll(ViewParent parent, View child, View target, int nestedScrollAxes) { if (parent instanceof NestedScrollingParent) { return ((NestedScrollingParent) parent).onStartNestedScroll(child, target, nestedScrollAxes); } return false; } } } ~~~ 這里面很簡單,看看parent是不是`NestedScrollingParent`類型的,如果是,則調用了`onStartNestedScroll`這個方法,而我們的`CoordinatorLayout`肯定是實現了`NestedScrollingParent`接口的, ~~~ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParent { } ~~~ 好了,現在我們終于回到`CoordinatorLayout`了,來看看他的`onStartNestedScroll`方法, ~~~ 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) { // 調用遍歷出來的這個子view的onStartNestedScroll方法 final boolean accepted = viewBehavior.onStartNestedScroll(this, view, child, target, nestedScrollAxes); handled |= accepted; lp.acceptNestedScroll(accepted); } else { lp.acceptNestedScroll(false); } } return handled; } ~~~ 這里還是去遍歷了所有子view,然后去調用它的`onStartNestedScroll`方法,它的返回值,決定了`NestedScrollingChildHelper.onStartNestedScroll`是不是要繼續遍歷,如果我們的子view對這個view的滑動感興趣,就返回true,它的遍歷就會結束掉。 好了,現在start的過程我們分析完了,大體的流程就是: > NestedScrollView.onInterceptTouchEvent->NestedScrollingChildHelper.onStartNestedScroll->CoordinatorLayout.onStartNestedScroll 下面的各種滑動調用流程也是一樣的,這里我們就不再重復分析了,感興趣的可以自己去看一下源碼。 ### Behavior的事件分發過程 上面的分析其實已經將我們自定義Behavior中使用到的方法的調用流程分析完了,不過我們還是要拓展一下,其實Behavior也是支持事件的傳遞的,在這方面,Behavior好像是一個代理一樣,在CoordinatorLayout的各種事件處理的方法中去調用Behavior的事件處理方法,返回值決定了CoordinatorLayout對事件的消費情況。 ~~~ @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(); } // 去看看子view中behavior是有要攔截 // 如果要攔截,則我們要攔截 // 在這里Behavior類似一個代理 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; } ~~~ 這里面調用了`performIntercept`方法,而且指定了個常量`TYPE_ON_INTERCEPT`代表了我們在攔截階段調用的,既然有區分,肯定在別的地方也有調用,答案是肯定的,在`onTouch`里也有對`performIntercept`的調用, ~~~ @Override public boolean onTouchEvent(MotionEvent ev) { boolean handled = false; boolean cancelSuper = false; MotionEvent cancelEvent = null; final int action = MotionEventCompat.getActionMasked(ev); // 這里要說道說道 // 兩個條件:1 如果behavior想要攔截 // 2 behavior的onTouchEvent返回true // 為什么會有兩個條件呢? // 解答:第一個條件是正常的分發流程, 很容易理解 // // 第二個條件是在沒有子view消費事件,所以事件會冒泡到此 // 這時,我們還要繼續詢問behavior是否要消費該事件 // 這里在performIntercept中執行的是: // case TYPE_ON_TOUCH: // 從onTouchEvent調用的 // intercepted = b.onTouchEvent(this, child, ev); // break; // 當intercepted為true時,表示我們對該down事件感興趣 // 此時 mBehaviorTouchView也有了賦值 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) { // 這里同樣的事件會繼續執行一遍onTouchEvent? handled = b.onTouchEvent(this, mBehaviorTouchView, ev); } } // 如果behavior不感興趣 // 輪到自己了,問問自己干不感興趣 // Keep the super implementation correct if (mBehaviorTouchView == null) { handled |= super.onTouchEvent(ev); } else if (cancelSuper) { // 如果behavior執行了事件(并不是攔截了事件,上面的第一個if的第一個條件不成立,第二個條件成立) // 能執行到這,說明behavior沒有攔截事件,但在事件冒泡的過程中消費了事件 // mBehaviorTouchView是在performIntercept(ev, TYPE_ON_TOUCH)賦值的 // 則給自己執行一個cancel事件 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; } ~~~ 恩,這里面的代碼注釋已經寫的很明白了,但是需要注意的一點,這一點我很長時間沒有相通,就是為什么還要在`onTouch`里還要調用一遍`performIntercept`,是這樣的,假如現在事件沒有任何子view去消費,那么事件會冒泡到此,本著把Behavior看作是一個代理的原則,這里肯定還是要去詢問一下Behavior是不是要執行這個事件,注意這里說的是執行而不是攔截,這是因為`performIntercept`不僅僅會調用Behavior的攔截部分的代碼,也會調用執行的代碼,就是通過第二個參數區分的。可以看到,這里我們使用了`TYPE_ON_TOUCH`。 好了,說了這么多`performIntercept`,是時候來看看`performIntercept`的代碼了。 ~~~ 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(); // 如果現在已經有攔截了的 // 并且現在是down // 則 所有的behavior會受到一個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: // 從onInterceptTouchEvent調用的 b.onInterceptTouchEvent(this, child, cancelEvent); break; case TYPE_ON_TOUCH: // 從onTouch調用的 b.onTouchEvent(this, child, cancelEvent); break; } } continue; } // 如果現在還沒有攔截 并且具有behavior if (!intercepted && b != null) { switch (type) { case TYPE_ON_INTERCEPT: // 從onInterceptTouchEvent調用的 intercepted = b.onInterceptTouchEvent(this, child, ev); break; case TYPE_ON_TOUCH: // 從onTouchEvent調用的 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; } ~~~ 這里面的代碼也很容易理解,就是去遍歷所有的view,在不同的情景下調用Behavior的onInterceptTouchEvent或onTouch方法。 好了關于Behavior的源碼我們就分析到這里,相信大家在看完之后會對Behavior有一個全新的認識,而且google已經建議我們使用support design的東西了(沒發現現在的項目默認模板文件就是一個標準的support design布局嗎),所以我們還是有必要對新東西有個更加深入的認識,而且這樣也會有助于我們理解google工程師的思路,在解決一些問題的時候我們完全可以參考一下這些思路。 ok,不扯了,今天就到這里吧,拜拜。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看