主頁:http://cherylgood.cn/c/Android開發之CoordinatorLayout使用詳解一.php
官網描述爲:CoordinatorLayout是一個增強版的FrameLayout(繼承自ViewGroup)
用途:
1、作爲應用的頂層視圖。
2、作爲一個可以指定子View之間相互作用的容器,通過給CoordinatorLayout的子View指定CoordinatorLayout.Behavior 來定義子view之間的相互作用。(你可以想象成:CoordinatorLayout相當於在兩個View之間充當中介,這樣子的好處就是兩個view之間的耦合度降低了,只需要跟coordinatorLayout打交到即可,而CoordinatorLayout.Behavior 相當於兩個view之間的協議,即通過怎樣的規則來約束雙方的行爲。)
設計概念:
- CoordinatorLayout:CoordinatorLayout 作爲最頂層視圖,將負責管理所有的子view,使其內部的子View彼此間產生一種聯繫。這個聯繫通過Behavior來實現(包括了滑動狀態的處理以及View狀態的處理)。
- AppBarLayout:AppBarLayout 繼承自限性佈局,作爲增強版的線性佈局,他增加了對滑動手勢的處理。
- Behavior:Behavior 是google新提出的,能夠讓你以非侵入式的方式去處理目標View和其他View的交互行爲。Behavior需要設置在觸發事件(比如滾動)的view上,且這個View必須是CoordinatorLayout的第一層級下的子view,否則沒有效果,因爲Behavior的初始化是在CoordinatorLayout的LayoutParams中通過反射完成的。
Behavior實例化方式:1、通過app:layout_behavior聲明 ;2、在你的自定義View類上添加@DefaultBehavior(MyBehavior.class); - Behavior只是個接口,其調用是由NestedScrollingParent與NestedScrollingChild接口負責調用。
接下來我們通過閱讀部分源碼進行學習:
首先,我們從兩個view是如何通過coordinatorlayout產生關聯來入手;看代碼
LayoutParams(Context context, AttributeSet attrs) { super(context, attrs); final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CoordinatorLayout_Layout); this.gravity = a.getInteger( R.styleable.CoordinatorLayout_Layout_android_layout_gravity, Gravity.NO_GRAVITY); mAnchorId = a.getResourceId(R.styleable.CoordinatorLayout_Layout_layout_anchor, View.NO_ID); this.anchorGravity = a.getInteger( R.styleable.CoordinatorLayout_Layout_layout_anchorGravity, Gravity.NO_GRAVITY); this.keyline = a.getInteger(R.styleable.CoordinatorLayout_Layout_layout_keyline, -1); insetEdge = a.getInt(R.styleable.CoordinatorLayout_Layout_layout_insetEdge, 0); dodgeInsetEdges = a.getInt( R.styleable.CoordinatorLayout_Layout_layout_dodgeInsetEdges, 0); mBehaviorResolved = a.hasValue( R.styleable.CoordinatorLayout_Layout_layout_behavior); if (mBehaviorResolved) { mBehavior = parseBehavior(context, attrs, a.getString( R.styleable.CoordinatorLayout_Layout_layout_behavior)); } a.recycle(); if (mBehavior != null) { // If we have a Behavior, dispatch that it has been attached mBehavior.onAttachedToLayoutParams(this); } }
從
mBehaviorResolved = a.hasValue( R.styleable.CoordinatorLayout_Layout_layout_behavior); if (mBehaviorResolved) { mBehavior = parseBehavior(context, attrs, a.getString( R.styleable.CoordinatorLayout_Layout_layout_behavior)); }
這幾句我們可以看到。 mBehaviorResolved 是個boolean 變量,如果
R.styleable.CoordinatorLayout_Layout_layout_behavior CoordinatorLayout的
layout_behavior這個字段設置有值, 1、mBehaviorResolved = true -》調用parseBehavior方法,將所需參數傳入通過java的反射技術返回一個Behavior實例。
static Behavior parseBehavior(Context context, AttributeSet attrs, String name) { if (TextUtils.isEmpty(name)) { return null; } final String fullName; 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> constructors = sConstructors.get(); if (constructors == null) { constructors = new HashMap<>(); sConstructors.set(constructors); } Constructor c = constructors.get(fullName); if (c == null) { final Class clazz = (Class) 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的參數爲(context,attrs)的構造函數進行實例化。 實例化出Behavior之後我們會調用behavior的onAttachedToLayoutParams方法 將LayoutParams的實例對象傳進去mBehavior.onAttachedToLayoutParams(this);
mBehavior.onAttachedToLayoutParams是一個當LayoutParams被實例化後的回調方法。 通過這裏,我們的
CoordinatorLayout就能夠跟用layout_behavior標識的子View產生聯繫。 當子View發生變化時,CoordinatorLayout又是如何處理的的,請看下面代碼:
final void onChildViewsChanged(@DispatchChangeEvent final int type) { final int layoutDirection = ViewCompat.getLayoutDirection(this); final int childCount = mDependencySortedChildren.size(); final Rect inset = acquireTempRect(); final Rect drawRect = acquireTempRect(); final Rect lastDrawRect = acquireTempRect(); for (int i = 0; i < childCount; i++) { final View child = mDependencySortedChildren.get(i); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (type == EVENT_PRE_DRAW && child.getVisibility() == View.GONE) { // Do not try to update GONE child views in pre draw updates. continue; } // 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); } } // Get the current draw rect of the view getChildRect(child, true, drawRect); // Accumulate inset sizes if (lp.insetEdge != Gravity.NO_GRAVITY && !drawRect.isEmpty()) { final int absInsetEdge = GravityCompat.getAbsoluteGravity( lp.insetEdge, layoutDirection); switch (absInsetEdge & Gravity.VERTICAL_GRAVITY_MASK) { case Gravity.TOP: inset.top = Math.max(inset.top, drawRect.bottom); break; case Gravity.BOTTOM: inset.bottom = Math.max(inset.bottom, getHeight() - drawRect.top); break; } switch (absInsetEdge & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.LEFT: inset.left = Math.max(inset.left, drawRect.right); break; case Gravity.RIGHT: inset.right = Math.max(inset.right, getWidth() - drawRect.left); break; } } // Dodge inset edges if necessary if (lp.dodgeInsetEdges != Gravity.NO_GRAVITY && child.getVisibility() == View.VISIBLE) { offsetChildByInset(child, inset, layoutDirection); } if (type == EVENT_PRE_DRAW) { // Did it change? if not continue getLastChildRect(child, lastDrawRect); if (lastDrawRect.equals(drawRect)) { continue; } recordLastChildRect(child, drawRect); } // 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 (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) { // If this is from a pre-draw and we have already been changed // from a nested scroll, skip the dispatch and reset the flag checkLp.resetChangedAfterNestedScroll(); continue; } final boolean handled; switch (type) { case EVENT_VIEW_REMOVED: // EVENT_VIEW_REMOVED means that we need to dispatch // onDependentViewRemoved() instead b.onDependentViewRemoved(this, checkChild, child); handled = true; break; default: // Otherwise we dispatch onDependentViewChanged() handled = b.onDependentViewChanged(this, checkChild, child); break; } if (type == EVENT_NESTED_SCROLL) { // If this is from a nested scroll, set the flag so that we may skip // any resulting onPreDraw dispatch (if needed) checkLp.setChangedAfterNestedScroll(handled); } } } } releaseTempRect(inset); releaseTempRect(drawRect); releaseTempRect(lastDrawRect); }
我們可以看到文檔說明,大概意思是當子view發生變化會調用該方法。該方法會遍歷所有的子view, 然後調用如下代碼,layoutDependsOn()這個方法是做什麼的呢?我們接下來看下該方法。
if (b != null && b.layoutDependsOn(this, checkChild, child)) { if (type == EVENT_PRE_DRAW && checkLp.getChangedAfterNestedScroll()) { // If this is from a pre-draw and we have already been changed // from a nested scroll, skip the dispatch and reset the flag checkLp.resetChangedAfterNestedScroll(); continue; } final boolean handled; switch (type) { case EVENT_VIEW_REMOVED: // EVENT_VIEW_REMOVED means that we need to dispatch // onDependentViewRemoved() instead b.onDependentViewRemoved(this, checkChild, child); handled = true; break; default: // Otherwise we dispatch onDependentViewChanged() handled = b.onDependentViewChanged(this, checkChild, child); break; } if (type == EVENT_NESTED_SCROLL) { // If this is from a nested scroll, set the flag so that we may skip // any resulting onPreDraw dispatch (if needed) checkLp.setChangedAfterNestedScroll(handled); } }
/** * Determine whether the supplied child view has another specific sibling view as a * layout dependency. * *
This method will be called at least once in response to a layout request. If it * returns true for a given child and dependency view pair, the parent CoordinatorLayout * will:
*
- *
- Always lay out this child after the dependent child is laid out, regardless * of child order.
- *
- Call {@link #onDependentViewChanged} when the dependency view's layout or * position changes.
- *
* * @param parent the parent view of the given child * @param child the child view to test * @param dependency the proposed dependency of child * @return true if child's layout depends on the proposed dependency's layout, * false otherwise * * @see #onDependentViewChanged(CoordinatorLayout, android.view.View, android.view.View) */ public boolean layoutDependsOn(CoordinatorLayout parent, V child, View dependency) { return false; }
這個方法,大概意思是如果我們返回true,說明當前發生變化的子view發生變化時。也就是該方法決定我們用
layout_behavior標識的view是否應該做出相應的變化。默認返回false,該方法需要我們在創建自己的Behavior時重寫。 當返回true的話,可以看到會調用
b.onDependentViewRemoved(this, checkChild, child);
handled = b.onDependentViewChanged(this, checkChild, child); 爲了更好理解這兩句代碼,我們舉個例子,假設有ViewA和ViewB ,當ViewB發生移動時,ViewA要向反方向移動。 1、當ViewB被移除時會調用
b.onDependentViewRemoved(this, checkChild, child); 2、當ViewB發生變化時,會調用
handled = b.onDependentViewChanged(this, checkChild, child); 那麼我們就可以在
b.onDependentViewChanged裏面寫我們的功能代碼了。 通過以上的分析,希望能幫到大家對CoordinatorLayout的協作調用過程有一些些的幫助。
http://cherylgood.cn/c/Android開發之CoordinatorLayout使用詳解二.php