Android View measure (一) 流程分析
本篇模拟三个角色:Android 架构师-小福、Android 控件开发工程师-小黑、 Android 开发工程师-小白,下面按照三个角色不同角度分析measure过程。
小福负责分享:
- measure的本质 - ok
- measure代码流程 - 分析FrameLayout.onMeasure
- onMeasure方法与MeasureSpec - ok
- 提出问题
Android 架构师-小福的分享
一、Measure本质
二、Measure代码流程
public final class ViewRootImpl extends Handler implements ViewParent, View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks { // 1 所有子视图的requestLayout方法,最总都会触发根视图此方法 public void requestLayout() { checkThread(); // 需要重新布局 mLayoutRequested = true; scheduleTraversals(); } // 调度遍历 public void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; ..... // 当前类继承自Handler,发出一个空消息,目的是加入Message队列 sendEmptyMessage(DO_TRAVERSAL); } } @Override public void handleMessage(Message msg) { switch (msg.what) { ... case DO_TRAVERSAL: ... // 处理DO_TRAVERSAL消息 performTraversals(); ... break; ..... } } // 执行遍历 private void performTraversals() { final View host = mView; int desiredWindowWidth; int desiredWindowHeight; int childWidthMeasureSpec; int childHeightMeasureSpec; ...... if (mLayoutRequested && !mStopped) { ...... childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); ...... // host是一个View对象 host.measure(childWidthMeasureSpec, childHeightMeasureSpec); ...... } ...... } }
上面的代码一共分为5个步骤
- 界面中所有视图执行requestLayout,重新布局请求会逐步向上传递,最终传执行当前ViewRootImpl的requestLaout()
- 步骤1中会执行scheduleTraversals,其中发送一个空的消息,把重新布局的请求通过Handler发送到主线程的MeassQueue等待执行(具体可以学习Handler)。
- 因为当前ViewRootImpl是继承自Handler,所以直接查找覆写的handleMessage方法,因为传递的消息是DO_TRAVERSAL,分支调用performTraversals
- performTraversals方法中调用host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- 因为host是View对象所以接下来需要查看View.measure方法,才能进一步分析measure流程
接着上面的measure流程的第五步走下去,以下是android.view.View.java文件中的源码:
public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Callback, AccessibilityEventSource { // 方法是final类型,说明不能被覆写或者重载 public final void measure(int widthMeasureSpec, int heightMeasureSpec) { // 如果有重新请求标志,或者宽高发生改变 if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { ...... // 真正执行测量视图大小操作 // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); ...... // 添加重新请求子视图布局标志 mPrivateFlags |= LAYOUT_REQUIRED; } ...... } /** * Call this when something has changed which has invalidated the * layout of this view. This will schedule a layout pass of the view * tree. */ public void requestLayout() { if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT); } // 添加重新请求布局标志 mPrivateFlags |= FORCE_LAYOUT; mPrivateFlags |= INVALIDATED; if (mParent != null) { if (mLayoutParams != null) { mLayoutParams.resolveWithDirection(getResolvedLayoutDirection()); } if (!mParent.isLayoutRequested()) { mParent.requestLayout(); } } } }
上面代码的measure流程可以分为4个步骤
1 measure与requestLayout -> 2 onMeasure
- measure方法是final类型,说明此方法不能被修改。其中判断条件(mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT的值是在requestLayout 进行赋值的。只要测量的宽高等发生改变都会触发第二步。
- 执行当前的onMeasure方法,通过Hierarchy Viewer等工具可以获知根视图是FrameLayout类型(这里就不从源码验证了)
紧接着看下android.widget.FrameLayout类的onMeasure总都做了什么?
三、onMeasure方法与MeasureSpec
类名.方法名 | 解释 |
MeasureSpec.getMode(int measureSpec) | 根据提供的测量值(格式)提取模式(上述三个模式之一) |
MeasureSpec.getSize(int measureSpec) | 根据提供的测量值(格式)提取大小值 |
MeasureSpec.makeMeasureSpec(int size,int mode) | 根据提供的大小值和模式创建一个测量值(格式) |
模式 | 翻译 | 模式与Layout参数对应关系 | 模式描述 |
UNSPECIFIED | 无限制 | parent view不约束child view的大小 | |
AT_MOST | 最多的 | wrap_content | child view可以在parent view范围内取值 |
EXACTLY | 准确的 | fill_parent(例如50dip) | parent view为child view指定固定大小 |
3. MeasureSpec通过位运行从int类型的值中获取mode与sieze
四、提出问题
需要通过measureChilde(view, width, height), 或者childView.measure();
还有哪些?
View.getMeasureWidth(), View.getMeasureHiegh()
一个未添加到视图中的? 但是有时getMesureHeight 依然是返回0。为什么?
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。