Android O: View的绘制流程(二):测量
在前一篇博客Android O: View的绘制流程(一): 创建和加载中,
我们分析了系统创建和加载View的过程,这部分内容完成了View绘制的前置工作。
本文开始分析View的测量的流程。
一、绘制流程的起点
在分析View的测量的流程前,我们先来寻找一下界面绘制流程的起点。
当Activity启动时,会调用ActivityThread的handleLaunchActivity方法:
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {............//这部分代码,会调用Activity的onCreate, 进而调用setContentView//完成上一篇博客描述的前置工作Activity a = performLaunchActivity(r, customIntent);if (a != null) {.............//重点关注该函数handleResumeActivity(r.token, false, r.isForward,!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);.............} else {.............}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
我们跟进一下handleResumeActivity函数:
final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {..........// 会回调Activity的onResume接口r = performResumeActivity(token, clearHide, reason);if (r != null) {final Activity a = r.activity;.........if (r.window == null && !a.mFinished && willBeVisible) {//之前已经创建出Activity对应的PhoneWindow和DecorView//将这些对象记录到ActivityRecord中r.window = r.activity.getWindow();View decor = r.window.getDecorView();decor.setVisibility(View.INVISIBLE);//得到WindowManagerImplViewManager wm = a.getWindowManager();WindowManager.LayoutParams l = r.window.getAttributes();a.mDecor = decor;..........//如果Activity可见if (a.mVisibleFromClient) {if (!a.mWindowAdded) {a.mWindowAdded = true;//Activity的DecorView递交给WindowManagerwm.addView(decor, l);} else {...........}}} else if (...) {.......}.......} else {.........}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
从上述代码可以看出,解析完XML对应的View后,
最终将DecorView递交给WindowManager。
我们跟进一下WindowManagerImpl中的addView函数:
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyDefaultToken(params);//实际上定义于WindowManagerGlobal中mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);}
- 1
- 2
- 3
- 4
- 5
继续跟进WindowManagerGlobal中的代码:
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {............final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;//进一步调整wparams.............ViewRootImpl root;View panelParentView = null;synchronized (mLock) {..........//创建出View对应的ViewRootroot = new ViewRootImpl(view.getContext(), display);view.setLayoutParams(wparams);mViews.add(view);mRoots.add(root);mParams.add(wparams);try {//关联View和ViewRootImplroot.setView(view, wparams, panelParentView);} catch (RuntimeException e) {...........}}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
至此我们知道了,WindowManager将DecorView和对应的ViewRootImpl关联起来了。
现在来一起看看ViewRootImpl的setView函数:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {synchronized (this) {if (mView == null) {mView = view;...........//初次布局开始requestLayout();...........}}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
容易看出ViewRootImpl与View关联后,会调用requestLayout函数,
该函数将开启整个绘制流程。
眼见为实,我们来看看这个requestLayout函数:
public void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {checkThread();mLayoutRequested = true;//继续跟进scheduleTraversals();}}void scheduleTraversals() {//mTraversalScheduled用于限制绘制的次数if (!mTraversalScheduled) {mTraversalScheduled = true;...........//将mTraversalRunnable加入执行队列mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);...........}}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
最后,我们来看看TraversalRunnable的实现:
final class TraversalRunnable implements Runnable {@Overridepublic void run() {doTraversal();}}
- 1
- 2
- 3
- 4
- 5
- 6
TraversalRunnable在执行时,会调用doTraversal函数,对应代码如下:
void doTraversal() {if (mTraversalScheduled) {mTraversalScheduled = false;..........//开始绘制了performTraversals();..........}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
绘制的主要逻辑定义于ViewRootImpl的performTraversals中,
该函数会遍历整个视图书,逐一绘制每个View。
performTraversals函数接近1000行左右且涉及较多琐碎的细节,
个人感觉没有逐行解析的必要,因此我们主仅关注主要的逻辑。
实际上performTraversals的代码流程可以大致分为三个阶段,如下所示:
private void performTraversals() {.............// 测量阶段int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);............performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);............// 布局阶段performLayout(lp, mWidth, mHeight);............// 绘制阶段performDraw();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
总结一下上述整个代码的调用流程,大致如下所示:
二、Measure阶段
将performTraversals划分为三个阶段后,整体的逻辑就可以简化为下图:
现在我们来看看其中Measure阶段的代码。
2.1 MeasureSpec
在分析测量的代码前,我们先要了解一下MeasureSpec的概念。
MeasureSpec是定义于View.java中的内部类,表示一个32位的整形值。
它的高2位表示测量模式SpecMode,低30位表示在相应模式下的测量尺寸SpecSize。
目前SpecMode的取值可以为以下三种:
/*** Measure specification mode: The parent has not imposed any constraint* on the child. It can be whatever size it wants.*/public static final int UNSPECIFIED = 0 << MODE_SHIFT;/*** Measure specification mode: The parent has determined an exact size* for the child. The child is going to be given those bounds regardless* of how big it wants to be.*/public static final int EXACTLY = 1 << MODE_SHIFT;/*** Measure specification mode: The child can be as large as it wants up* to the specified size.*/public static final int AT_MOST = 2 << MODE_SHIFT;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
如注释所述:
UNSPECIFIED 表示不指定测量模式,对应的场景是:
父视图没有限制子试图大小,子试图可以是想要的任何尺寸。
这种模式基本用不到。
EXACTLY 表示精确测量模式,对应的场景是:
父视图已经指定了子试图的精确大小,此时测量值就是SpecSize的值。
当视图的layout_width或者layout_height指定为具体的数值,
或指定为match_parent时,该模式生效。
AT_MOST 表示最大值模式,对应的场景是:
当视图的layout_width或layout_height指定为wrap_content时,
子视图的尺寸可以是不超过父视图允许最大值的任何尺寸。
我们来看看前文代码中的getRootMeasureSpec函数:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {int measureSpec;switch (rootDimension) {case ViewGroup.LayoutParams.MATCH_PARENT:// Window can't resize. Force root view to be windowSize.measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);break;case ViewGroup.LayoutParams.WRAP_CONTENT:// Window can resize. Set max size for root view.measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);break;default:// Window wants to be an exact size. Force root view to be that size.measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);break;}return measureSpec;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
从代码来看,根据LayoutParams的参数,getRootMeasureSpec会得到对应模式的MeasureSpec。
其中主要用到的还是EXACTLY和AT_MOST模式。
对于DecorView而言,它的MeasureSpec由窗口尺寸和其自身的LayoutParams共同决定;
对于普通的View,它的MeasureSpec由父视图的MeasureSpec和自身的LayoutParams共同决定。
2.2 measure
了解完MeasureSpec后,我们来看看performMeasure函数:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {........//实际上调用的还是View的measure接口mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);........
}
- 1
- 2
- 3
- 4
- 5
- 6
我们跟进View的measure函数:
//参数为父ViewGroup对当前View的约束信息
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {//当前View为ViewGroup且设置为视觉边界布局模式时,才返回trueboolean optical = isLayoutModeOptical(this);//当前View与父容器的模式不同时,需要调整MeasureSpecif (optical != isLayoutModeOptical(mParent)) {............widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);}// Suppress sign extension for the low bytes// 计算key值, 用于判断是否有缓存及作为存储键值long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);//判断是否需要强制重新布局//例如View调用requestLayout时,会在mPrivateFlags添加该标记final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;// 以下其实就是判断是否需要重新布局// Optimize layout by avoiding an extra EXACTLY pass when the view is// already measured as the correct size. In API 23 and below, this// extra pass is required to make LinearLayout re-distribute weight.final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec|| heightMeasureSpec != mOldHeightMeasureSpec;final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);final boolean needsLayout = specChanged&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);//强制重新布局或需要重新布局if (forceLayout || needsLayout) {// first clears the measured dimension flagmPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;//尝试解析RTL相关的属性resolveRtlPropertiesIfNeeded();//没有forceLayout时,尝试从缓存获取int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);//获取缓存失败,或忽略缓存时,才开始测量if (cacheIndex < 0 || sIgnoreMeasureCache) {// measure ourselves, this should set the measured dimension flag backonMeasure(widthMeasureSpec, heightMeasureSpec);mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;} else {//命中缓存,直接从缓存取结果long value = mMeasureCache.valueAt(cacheIndex);// Casting a long to int drops the high 32 bits, no mask neededsetMeasuredDimensionRaw((int) (value >> 32), (int) value);mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;}...........}mOldWidthMeasureSpec = widthMeasureSpec;mOldHeightMeasureSpec = heightMeasureSpec;//存入缓存mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
从上面的代码来看,当需要(包括强制)重新布局且不使用(包括无缓存)缓存数据时,
才会调用onMeasure进行View的测量工作。
上述代码的整体流程,大致如下图所示:
2.3 ViewGroup的onMeasure
onMeasure函数一般会被View的子类覆盖,因此对于DecorView而言,
实际调用的应该是FrameLayout的onMeasure方法。
我们来跟进一下FrameLayout的onMeasure方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//FrameLayout是ViewGroup的子类, 此处获取子View的数量int count = getChildCount();//长或宽的SpecMode不为EXACTLY时, measureMatchParentChildren置为true//意味着ViewGroup的长或宽为wrap_contentfinal boolean measureMatchParentChildren =MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;mMatchParentChildren.clear();int maxHeight = 0;int maxWidth = 0;int childState = 0;//依次measure子Viewfor (int i = 0; i < count; i++) {final View child = getChildAt(i);//判断能否measure该子Viewif (mMeasureAllChildren || child.getVisibility() != GONE) {//具体的测量函数measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);//不断迭代出子View需要的最大宽度和最大高度final LayoutParams lp = (LayoutParams) child.getLayoutParams();maxWidth = Math.max(maxWidth,child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);maxHeight = Math.max(maxHeight,child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);//迭代childStatechildState = combineMeasuredStates(childState, child.getMeasuredState());if (measureMatchParentChildren) {if (lp.width == LayoutParams.MATCH_PARENT ||lp.height == LayoutParams.MATCH_PARENT) {//统计matchParentChildrenmMatchParentChildren.add(child);}}}}// Account for padding too// 最大宽度和高度需要叠加paddingmaxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();// Check against our minimum height and width// 需要判断是否满足设置的最小宽高的要求maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());// Check against our foreground's minimum height and width// 还需要满足前景图像的要求final Drawable drawable = getForeground();if (drawable != null) {maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());}//经过了以上一系列步骤后,我们就得到了ViewGroup的maxHeight和maxWidth的最终值//表示当前容器用这个尺寸就能够正常显示其所有子View//此处resolveSizeAndState根据数值、MeasureSpec和childState计算出最终的数值//然后用setMeasuredDimension保存到mMeasuredWidth与mMeasuredHeight成员变量 (定义于View.java)setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),resolveSizeAndState(maxHeight, heightMeasureSpec,childState << MEASURED_HEIGHT_STATE_SHIFT));//部分子View需要做最后的测量//当ViewGroup存在wrap_content的size(初始时,未明确定义大小)//且child View存在match_parent的size时(需要依赖父容器)//那么父容器计算完毕后,这类child view需要重新测量count = mMatchParentChildren.size();if (count > 1) {for (int i = 0; i < count; i++) {//根据父容器的参数生成新的约束条件............//重新测量child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
从以上代码的执行流程,我们可以看到,作为容器的ViewGroup,
将通过measureChildWithMargins方法,对所有子View进行测量,
然后才会计算自身的测量结果。
FrameLayout的onMeasure函数整体流程可以概括为下图:
2.4 measureChildWithMargins
接下来,我们来看下ViewGroup的measureChildWithMargins方法:
protected void measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed) {//获取子View的LayoutParamsfinal MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();//生成新的约束条件final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin+ widthUsed, lp.width);final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin+ heightUsed, lp.height);//调用子View的measurechild.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
从上述代码可以看出,ViewGroup会利用getChildMeasureSpec函数计算出子View的约束条件,
然后再调用子View的measure函数。
我们看看getChildMeasureSpec函数:
// 从measureChildWithMargins函数,可以看出:
// spec为父View的MeasureSpec
// padding为父View在相应方向的已用尺寸, 加上父View的padding和子View的margin
// childDimension为子View的LayoutParams相应方向的值
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {int specMode = MeasureSpec.getMode(spec);int specSize = MeasureSpec.getSize(spec);//得到父View在相应方向上的可用大小int size = Math.max(0, specSize - padding);//保存最终结果int resultSize = 0;int resultMode = 0;switch (specMode) {// Parent has imposed an exact size on uscase MeasureSpec.EXACTLY:// 表示子View的LayoutParams指定了具体大小值(xx dp)if (childDimension >= 0) {resultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size. So be it.// 子View为match_parentresultSize = size;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// Child wants to determine its own size. It can't be// bigger than us.// 子View为wrap_contentresultSize = size;resultMode = MeasureSpec.AT_MOST;}break;// Parent has imposed a maximum size on uscase MeasureSpec.AT_MOST:if (childDimension >= 0) {// Child wants a specific size... so be itresultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {// Child wants to be our size, but our size is not fixed.// Constrain child to not be bigger than us.resultSize = size;resultMode = MeasureSpec.AT_MOST;} else if (childDimension == LayoutParams.WRAP_CONTENT) {// Child wants to determine its own size. It can't be// bigger than us.resultSize = size;resultMode = MeasureSpec.AT_MOST;}break;case MeasureSpec.UNSPECIFIED://不关注................}//noinspection ResourceTypereturn MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
上面的方法展现了根据父View的MeasureSpec和子View的LayoutParams生成子View的MeasureSpec的过程,
从代码可以看出:
当子View指定了具体的大小时,resultSize就是指定的size,resultMode为EXACTLY,
父View对其没有影响;
当子View指定为MATCH_PARENT时,resultSize为父View可用的size,
resultMode与父View一致;
当子View指定为WRAP_CONTENT时,resultSize为父View可用的size,
resultMode为AT_MOST。
从前文我们知道,获取完子View的MeasureSpec后,
measureChildWithMargins就会调用子View的measure方法。
对于ViewGroup及其子类而言,将重新递归调用ViewGroup的onMeasure方法;
对于View及其子类而言,将调用View的onMeasure方法。
由于measureChildWithMargins会递归调用ViewGroup的onMeasure方法,
可以看出整个View的测量顺序是先序遍历的,但最终计算结果时是后序遍历的,
即子View测量完毕后,才能得到父View的size。
2.5 View的onMeasure
现在我们跟进一下View的onMeasure方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));}
- 1
- 2
- 3
- 4
从代码可以看出,普通View只需要完成自身的测量工作即可。
View以getDefaultSize方法的返回值来作为测量结果,通过setMeasuredDimension方法进行设置。
getDefaultSize的源码如下:
public static int getDefaultSize(int size, int measureSpec) {int result = size;int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);switch (specMode) {case MeasureSpec.UNSPECIFIED:result = size;break;// AT_MOST和EXACTLY这两种情况都返回了SpecSize作为result// 自定义View直接继承View类时,需要自己实现// 否则wrap_content就和match_parent效果一样case MeasureSpec.AT_MOST:case MeasureSpec.EXACTLY:result = specSize;break;}return result;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
三、总结
至此,我们大致了解了View的测量流程。
个人觉得重点在于了解MeasureSpec对测量过程的影响,
同时知道测量的顺序是先序遍历,计算最终结果是后序遍历即可。
此外,当父容器的宽或高为wrap_content,其子View的宽或高为match_parent时,
父容器得到最终的宽、高后,需要重新测量这部分子View。
下一篇博客,我们将继续关注View绘制的布局流程。
Android O: View的绘制流程(二):测量相关推荐
- Android O: View的绘制流程(三):布局和绘制
前一篇文章Android O: View的绘制流程(二):测量中, 我们分析了View的测量流程. 当View测量完毕后,就要开始进行布局和绘制相关的工作, 本篇文章就来分析下这部分流程. 一. ...
- Android之View的绘制流程解析
转载请标明出处:[顾林海的博客] 个人开发的微信小程序,目前功能是书籍推荐,后续会完善一些新功能,希望大家多多支持! ##前言 自定义View在Android中占据着非常重要的地位,因此了解View的 ...
- Android O: View的绘制流程(一): 创建和加载
从这篇博客开始,我们会用几篇文章, 基于Android O的代码,分析一下View的绘制流程. 在分析具体的绘制流程前,我们先来了解一下XML中定义的View, 如何被创建和加载. 一.setCo ...
- 从源码解析-Android中View的绘制流程及performTraversals方法
谈谈Activity的setContentView是怎么加载XML视图的 谈谈Activity的View怎么与View绘制工具ViewRootImpl关联的 在前面两篇文章中分析了View是如何跟绘制 ...
- Android View的绘制流程(1) -- 测量onMeasure
鉴于是首篇讲解自定义view流程,之前也在网上搜了一些博主的博客看了看,都是大同小异,今天抽时间自己总结一下,分享一下自己的感悟,也算是一篇笔记. (本篇为开头篇,稍微讲述一下有关的东西) View的 ...
- 【Android 应用开发】UI绘制流程 ( 生命周期机制 | 布局加载机制 | UI 绘制流程 | 布局测量 | 布局摆放 | 组件绘制 | 瀑布流布局案例 )
文章目录 一. 博客相关资料 及 下载地址 1. 代码查看方法 ( ① 直接获取代码 | ② JAR 包替换 ) 2. 本博客涉及到的源码查看说明 二. Activity 生命周期回调机制 1. An ...
- Android自定义View系列之详解View的绘制流程
目录 一.开场白 二.View的绘制流程 2.1测量的过程 2.2布局的过程 2.3绘制的过程 一.开场白 开讲之前我们先预设一种自定义ViewGroup的场景:我们知道LinearLayout.Fr ...
- android自定义view流程,Android 自定义View--从源码理解View的绘制流程
前言 在Android的世界里,View扮演着很重要的角色,它是Android世界在视觉上的具体呈现.Android系统本身也提供了很多种原生控件供我们使用,然而在日常的开发中我们很多时候需要去实现一 ...
- 【Android面试】View的绘制流程
目录 View的绘制流程简介 Activity和window和view 的关系 Activity和Window是什么时候建立联系的呢? ViewRootImpl View的绘制流程总结 View的绘制 ...
最新文章
- JS中的this对象详解
- nodejs之http-proxy几点常见问题
- IM开发基础知识补课(一):正确理解前置HTTP SSO单点登陆接口的原理
- java访问mongodb_Java中获取MongoDB连接的方法详解
- There is no Action mapped for namespace / and action name accredit.
- Linux系统(七)组管理和用户管理
- 2018 【第九届蓝桥杯省赛】 C/C++ B组
- SPSS 分层回归(图文+数据集)【SPSS 027期】
- IOS日历控件JTCalendar
- bin文件的安装方法
- MD5算法如何被破解
- 记录第二次进行的助教培训-评分
- 如何用python爬取股票数据选股_用python爬取股票数据
- [SDOI2015] 星际战争
- “会说话的汤姆猫家族-时代逐光者”3D数字藏品中奖名单公布
- dp主机_DP接口与HDMI接口的区别?
- 系统学习深度学习(六) --LSTM总结
- 从谷歌创始人的公开信中所学到的
- 计算机 90学时培训总结,90学时培训心得总结
- 统计一个数中二进制数1的个数(新手篇10)
热门文章
- java 获取当前时间月加1 ,年加1
- 正确理解HTML,XHTML页面的头部doctype定义
- 使用Oracle数据库开发中的一个技巧
- Win10+tensorflow:SSD调试问题:Unable to open table file ../checkpoints/ssd_300_vgg.ckpt
- Python学习笔记:电子邮件,POP3收取邮件
- 嵌入式驱动开发应具备的三大基础
- 图解linux下top命令的使用
- 5.14 图像频域处理
- 海南师范大学计算机设计大赛证书,我校品牌VI设计作品在中国大学生计算机设计大赛海南省赛中获得一等奖...
- oracle恢复某个表的数据库,如何从rman的全备份中恢复一张表?