您的位置:首页 > 移动开发 > Android开发

CoordinateLayout onMeasure流程分析

2016-03-24 14:24 495 查看
先来看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里头这样定义anchor
app: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截取了嘛)

以上
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息