CoordinateLayout onMeasure流程分析
2016-03-24 14:24
495 查看
先来看CoordinateLayout:
开头就是两个关键方法 prepareChildren();和 ensurePreDrawListener();
先来看这个方法里的
用反射的方法来解析注解生成behavior的实例,然后设置到LayoutParams里头并返回,这个处理的就是在类的头部用注解定义behavior的那种
再来看
findAnchorView
顾名思义,就是得到anchor标记的那个view了。比如在xml里头这样定义anchor
来获取相应的属性
来详细分析一下findAnchorView里头的函数
回到prepareChildren这个函数,我们找完了anchor的view并且记录在layoutparams里头之后,把view添加到mDependencySortedChildren这个ArrayList里头,并且紧接着进行排序
selectionSort(mDependencySortedChildren, mLayoutDependencyComparator);
顾名思义,就是一个选择排序,传入待排序数组和一个比较器。
先看一眼比较器:
如果左边view依赖右边就返回1,否则返回-1,就是按照依赖的顺序进行选择排序嘛。
看看那个依赖函数dependsOn的实现方式
你看,之前保存的 mAnchorDirectChild就派上用场了,直接检查自己所保存的依赖的那个Anchor 的 view和当前传入的dependency是否一致,或者是在Behavior里头显式地重写layoutDependsOn来定义自己所依赖的view。
再看一样选择排序
当比较器的结果小于0的时候,就是右侧的参数依赖于左侧的那个参数,就返回-1,记录左侧的参数为min。综上,所以被依赖的view排在数组的前面,依赖他人的view排在数组的后面
总结起来,做完选择排序之后的mDependencySortedChildren会保证把被依赖的view排在最前面,而把依赖别人的view排在后面,第二次序才是view的添加次序(就是一开始被add加入mDependencySortedChildren的数组的次序)。
注意,这个mDependencySortedChildren是相当关键的一个数组,它成了后面几乎所有的遍历子view操作的那个顺序。其实也很好理解,对他人进行依赖的view必然是随着被依赖的那个view的变化而变化,那么我们自然要优先处理那个自变量,然后再处理因变量嘛。
接下来看
ensurePreDrawListener
这段比较容易:检查是否有依赖的存在,有的话就调用addPreDrawListener(); 预绘制的监听器
明显的,ViewTreeObserver加入一个监听器,在draw之前都会进行调用。那么自然是看看监听器到底写了什么鬼了
跟踪关键函数,我们又到了一个重要的地方
前面有段注释如是说:
* 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的相关部分
可以看到以依赖的先后顺序取出子view,并开始调用子view的lp的behavior的相应onMeasureChild,如果这里返回true,那么正常的onMeasureChild流程就不会进行调用了(被Behavior截取了嘛)
以上
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截取了嘛)
以上
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories