先來看CoordinateLayout:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
prepareChildren();
ensurePreDrawListener();
...
開頭就是兩個關鍵方法 prepareChildren();和 ensurePreDrawListener();
private void prepareChildren() {
mDependencySortedChildren.clear();
for (int i = 0, count = getChildCount(); i < count; i++) {
final View child = getChildAt(i);
final LayoutParams lp = getResolvedLayoutParams(child);
lp.findAnchorView(this, child);
mDependencySortedChildren.add(child);
}
// We need to use a selection sort here to make sure that every item is compared
// against each other
selectionSort(mDependencySortedChildren, mLayoutDependencyComparator);
}
先來看這個方法裏的
LayoutParams getResolvedLayoutParams(View child) {
final LayoutParams result = (LayoutParams) child.getLayoutParams();
if (!result.mBehaviorResolved) {
Class<?> childClass = child.getClass();
DefaultBehavior defaultBehavior = null;
while (childClass != null &&
(defaultBehavior = childClass.getAnnotation(DefaultBehavior.class)) == null) {
childClass = childClass.getSuperclass();
}
if (defaultBehavior != null) {
try {
result.setBehavior(defaultBehavior.value().newInstance());
} catch (Exception e) {
Log.e(TAG, "Default behavior class " + defaultBehavior.value().getName() +
" could not be instantiated. Did you forget a default constructor?", e);
}
}
result.mBehaviorResolved = true;
}
return result;
}
用反射的方法來解析註解生成behavior的實例,然後設置到LayoutParams裏頭並返回,這個處理的就是在類的頭部用註解定義behavior的那種
@DefaultBehavior(MyBehavior.class)
Class xxxxx{
xxxx
}
再來看
findAnchorView
View findAnchorView(CoordinatorLayout parent, View forChild) {
if (mAnchorId == View.NO_ID) {
mAnchorView = mAnchorDirectChild = null;
return null;
}
if (mAnchorView == null || !verifyAnchorView(forChild, parent)) {
resolveAnchorView(forChild, parent);
}
return mAnchorView;
}
顧名思義,就是得到anchor標記的那個view了。比如在xml裏頭這樣定義anchorapp:layout_anchor="@id/main.appbar"
,那麼在LayoutParams構造方法裏頭就有相應的
mAnchorId = a.getResourceId(R.styleable.CoordinatorLayout_LayoutParams_layout_anchor,View.NO_ID);
來獲取相應的屬性
來詳細分析一下findAnchorView裏頭的函數
/**
* Determine the anchor view for the child view this LayoutParams is assigned to.
* Assumes mAnchorId is valid.
*/
private void resolveAnchorView(final View forChild, final CoordinatorLayout parent) {
mAnchorView = parent.findViewById(mAnchorId);//得到依賴的那個anchor view。。一句話就結束了。接下來就是對這個anchorview的合理性做檢查
....
View directChild = mAnchorView;
//肯定不能錨定coordinateLayout嘛
if (mAnchorView == parent) {
throw new IllegalStateException(
"View can not be anchored to the the parent CoordinatorLayout");
}
//沿着Anchorview的樹向上檢查
for (ViewParent p = mAnchorView.getParent();
p != parent && p != null;
p = p.getParent()) {
//Anchor view也不能是依賴的view的子節點,要不然到底是誰決定誰呢?循環依賴了就
if (p == forChild) {
throw new IllegalStateException(
"Anchor must not be a descendant of the anchored view");
}
}
if (p instanceof View) {
directChild = (View) p;
}
}
mAnchorDirectChild = directChild;
//記錄描定的那個孩子???
}
}
回到prepareChildren這個函數,我們找完了anchor的view並且記錄在layoutparams裏頭之後,把view添加到mDependencySortedChildren這個ArrayList裏頭,並且緊接着進行排序
selectionSort(mDependencySortedChildren, mLayoutDependencyComparator);
顧名思義,就是一個選擇排序,傳入待排序數組和一個比較器。
先看一眼比較器:
final Comparator<View> mLayoutDependencyComparator = new Comparator<View>() {
@Override
public int compare(View lhs, View rhs) {
if (lhs == rhs) {
return 0;
} else if (((LayoutParams) lhs.getLayoutParams()).dependsOn(
CoordinatorLayout.this, lhs, rhs))
{
return 1;
} else if (((LayoutParams) rhs.getLayoutParams()).dependsOn(
CoordinatorLayout.this, rhs, lhs)) {
return -1;
} else {
return 0;
}
}
};
如果左邊view依賴右邊就返回1,否則返回-1,就是按照依賴的順序進行選擇排序嘛。
看看那個依賴函數dependsOn的實現方式
/**
* Check if an associated child view depends on another child view of the CoordinatorLayout.
*
* @param parent the parent CoordinatorLayout
* @param child the child to check
* @param dependency the proposed dependency to check
* @return true if child depends on dependency
*/
boolean dependsOn(CoordinatorLayout parent, View child, View dependency) {
return dependency == mAnchorDirectChild
|| (mBehavior != null && mBehavior.layoutDependsOn(parent, child, dependency));
}
你看,之前保存的 mAnchorDirectChild就派上用場了,直接檢查自己所保存的依賴的那個Anchor 的 view和當前傳入的dependency是否一致,或者是在Behavior裏頭顯式地重寫layoutDependsOn來定義自己所依賴的view。
再看一樣選擇排序
private static void selectionSort(final List<View> list, final Comparator<View> comparator) {
if (list == null || list.size() < 2) {
return;
}
final View[] array = new View[list.size()];
list.toArray(array);
final int count = array.length;
for (int i = 0; i < count; i++) {
int min = i;
for (int j = i + 1; j < count; j++) {
if (comparator.compare(array[j], array[min]) < 0) {
//關鍵部分:比較器代碼相應代碼如下
// if (((LayoutParams) rhs.getLayoutParams()).dependsOn(
// CoordinatorLayout.this, rhs, lhs)) {
// return -1;
// }
min = j;
}
}
if (i != min) {
// We have a different min so swap the items
final View minItem = array[min];
array[min] = array[i];
array[i] = minItem;
}
}
// Finally add the array back into the collection
list.clear();
for (int i = 0; i < count; i++) {
list.add(array[i]);
}
}
}
當比較器的結果小於0的時候,就是右側的參數依賴於左側的那個參數,就返回-1,記錄左側的參數爲min。綜上,所以被依賴的view排在數組的前面,依賴他人的view排在數組的後面
總結起來,做完選擇排序之後的mDependencySortedChildren會保證把被依賴的view排在最前面,而把依賴別人的view排在後面,第二次序纔是view的添加次序(就是一開始被add加入mDependencySortedChildren的數組的次序)。
注意,這個mDependencySortedChildren是相當關鍵的一個數組,它成了後面幾乎所有的遍歷子view操作的那個順序。其實也很好理解,對他人進行依賴的view必然是隨着被依賴的那個view的變化而變化,那麼我們自然要優先處理那個自變量,然後再處理因變量嘛。
接下來看
ensurePreDrawListener
/**
* Add or remove the pre-draw listener as necessary.
*/
void ensurePreDrawListener() {
boolean hasDependencies = false;
final int childCount = getChildCount();
//檢查是否有依賴的存在
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (hasDependencies(child)) {
hasDependencies = true;
break;
}
}
if (hasDependencies != mNeedsPreDrawListener) {
if (hasDependencies) {
addPreDrawListener();
} else {
removePreDrawListener();
}
}
}
這段比較容易:檢查是否有依賴的存在,有的話就調用addPreDrawListener(); 預繪製的監聽器
/**
* Add the pre-draw listener if we're attached to a window and mark that we currently
* need it when attached.
*/
void addPreDrawListener() {
if (mIsAttachedToWindow) {
// Add the listener
if (mOnPreDrawListener == null) {
mOnPreDrawListener = new OnPreDrawListener();
}
final ViewTreeObserver vto = getViewTreeObserver();
vto.addOnPreDrawListener(mOnPreDrawListener);
}
// Record that we need the listener regardless of whether or not we're attached.
// We'll add the real listener when we become attached.
mNeedsPreDrawListener = true;
}
明顯的,ViewTreeObserver加入一個監聽器,在draw之前都會進行調用。那麼自然是看看監聽器到底寫了什麼鬼了
class OnPreDrawListener implements ViewTreeObserver.OnPreDrawListener {
@Override
public boolean onPreDraw() {
dispatchOnDependentViewChanged(false);
return true;
}
}
跟蹤關鍵函數,我們又到了一個重要的地方
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);//按照依賴的先後順序取出子view!!
final LayoutParams lp = (LayoutParams) child.getLayoutParams();//取出子view的lp
// Check child views before for anchor
for (int j = 0; j < i; j++) {
final View checkChild = mDependencySortedChildren.get(j);//內循環,取出當前位置之前的所有的view(就是有可能對他們產生依賴的view,因爲排在他前面嘛)
if (lp.mAnchorDirectChild == checkChild) {//如果是Anchor依賴
offsetChildToAnchor(child, layoutDirection);//對當前view進行調整(我們知道anchor這種屬性是隨着被依賴的view變化的)
}
}
// Did it change? if not continue
//看看當前的view的Rect和上一次記錄的Rect是否一致(就是上下左右四個值是不是一樣啦)
final Rect oldRect = mTempRect1;
final Rect newRect = mTempRect2;
getLastChildRect(child, oldRect);//從這個view的LP裏頭得到上一次記錄的這個view的Rect值,就是上下左右的值啦
getChildRect(child, true, newRect);
if (oldRect.equals(newRect)) {//比較是否有位置變化等
continue; //沒變化就跳過後面步驟,直接檢查下一個view的依賴
}
recordLastChildRect(child, newRect);//把View當前的Rect記錄在這個view自己的LayoutParams裏頭(供下一次取出和比對)
// Update any behavior-dependent views for the change
//如果進行anchor調整之後,當前view發生了變化,那麼就開始向後進行循環調整(處在後面的view都有可能對當前view產生依賴嘛)
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)) {
//如果後面的view對當前view有依賴,那麼要隨着當前view的變化而變化
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);
//回調後面的view的onDependentViewChanged方法(自己依賴的view發生變化啦!)
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);
}
}
}
}
}
前面有段註釋如是說:
* Usually run as part of the pre-draw step when at least one child view has a reported
* dependency on another view. This allows CoordinatorLayout to account for layout
* changes and animations that occur outside of the normal layout pass.
*
* It can also be ran as part of the nested scrolling dispatch to ensure that any offsetting
* is completed within the correct coordinate window.‘
這個函數除了用在這個繪製前監聽之外,如果看看nestedScroll相關代碼,裏頭也大量用到了這個函數。
總結一下上面這個函數,是爲了處理anchor這個屬性而存在的。從前到後以依賴的順序取出子view,然後對每個view檢查對前面的view是否存在anchor依賴,如果是就調整來配合anchor的view(offsetChildToAnchor函數)
在根據Anchor調整完畢之後,View位置大小就基本確定下來了,這個時候就接着檢查這個view的lp所記錄的Rect和當前的位置是否發生變化,如果是,那麼就要遍歷自己之後的元素看有沒有元素依賴自己,有的話要回調他們相應的behavior.onDependentViewChanged 方法。
以上方法會在每次繪製前調用,保證了每個View變化的時候,依賴他的view能跟着一起變化
可以看到這個依賴的實現相當地粗暴簡單,沒有用到什麼圖論之類的知識,直接暴力循環了。。。
現在回到最最開始的onMeasure方法,我們上面搞了半天分析了兩個函數
prepareChildren();
ensurePreDrawListener();
接下來繼續向下分析onMeasure的相關部分
...
for (int i = 0; i < childCount; i++) {
final View child = mDependencySortedChildren.get(i);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
...
final Behavior b = lp.getBehavior();
if (b == null || !b.onMeasureChild(this, child, childWidthMeasureSpec, keylineWidthUsed,
childHeightMeasureSpec, 0)) {
onMeasureChild(child, childWidthMeasureSpec, keylineWidthUsed,
childHeightMeasureSpec, 0);
}
}
可以看到以依賴的先後順序取出子view,並開始調用子view的lp的behavior的相應onMeasureChild,如果這裏返回true,那麼正常的onMeasureChild流程就不會進行調用了(被Behavior截取了嘛)
以上