视图绘制三部曲之onMeasure()源码最简解析 带你轻松领略源代码之美
2017-11-19 15:02
232 查看
performTraversals():
(怎么找到这个类:使用as的全局搜索功能搜索ViewRootImpl)
(太长了:由于这个方法800多行,所以着重挑选出与视图绘制有关的内容。)
(为什么要看这个类?View的绘制从这个方法开始。)
取得父视图的大小
getRootMeasureSpec
就是用这个方法取得根布局的大小的。
1.match_parent
对应MeasureSpec.EXACTLY,该是多少就是多少
2.wrap_content
对应 MeasureSpec.AT_MOST,你这个资源本身是多大就是多大,大到不能再大为止
3.自己定义了多少多少dp
对应MeasureSpec.EXACTLY,该是多少就是多少
performMeasure()
到了这里,performTraversals()就可以不看了,因为已经进入我们的onMeasure流程了(后面还会有performlayout,performDraw)
进入performMeasure以后,又会在带着参数,调用View的measure方法,measure中又会调用onmeasure方法(方法之间的跳来跳去有点多)
onMeasure()
getDefaultSize():获取视图的大小
这个方法代表着AT_MOST,EXACTLY(即wrap_content,Match_parent,多少多少dp)这几种正常情况,都是直接把specSize作为结果。UNSPECIFIED一般只会在自定义控件的时候出现这种操作。(你直接覆盖父类的onMeasure()为指定数值时)
setMeasuredDimension():设置我们通过getDefaultSize()获取的视图大小
这里很容易理解。每个view都有类型模式。这里的if语句指代的是,如果你不是父模式,那你必须在父模式下对你实际测量出来的大小再进行修改,你得按照你的父亲的大小来。
如果你就是根布局,那你直接就可以setMeasuredDimensionRaw(measuredWidth, measuredHeight);
setMeasuredDimensionRaw()
那么子视图的测量又在哪里呢?
这里仿佛就戛然而止了,但是根布局一定是容器控件,那一定是继承viewGroup的,所以对子view进行measure是不需要外部调用的,是每个继承自viewgroup的自己的任务。
measureChildren
viewGroup内部维护一个子view的数组mChildren,以及子view的数目mChildrenCount。
会通过简单的for循环(其实这里用增强for循环更好)通过measureChild方法来测绘每个子的大小
measureChild
好了,又回到measure了(这里再次强调:只有容器控件才能存放子view,所以必须得继承自viewgroup,所以会自己去测绘子view的大小,不需要外力)
最后梳理下流程
performTraversals()开始我们的视图绘制
getRootMeasureSpec()测出根布局的大小,把测出来的大小传给performMeasure()
performMeasure()调用view的measure方法
measure()调用measure方法
onMeasure()调用getDefaultSize方法获取大小,并把获取的大小通过调用setMeasuredDimension方法进行设置
setMeasuredDimension()对宽高进行最后一步处理,把最终的宽高通过调用setMeasuredDimensionRaw方法进行设置
setMeasuredDimensionRaw()把得到的真实宽高赋给全局变量
一次测绘结束
如果当前的是viewgroup且里面还有子布局,遍历测绘
全部测绘结束
贴上所有源码
performTraversals():太长了,所以贴上有关测量大小的代码
getRootMeasureSpec()
performMeasure()
measure()
onMeasure()
getDefaultSize()
setMeasuredDimensionRaw()
setMeasuredDimensionRaw()
有耐心看到这里的人我会告诉他更多的东西(其实就是我一开始忘了现在补上去)
1. performMeasure其实是告诉host(你当前的宿主view,简单来说就是你要返回的view)你想要怎么样的宽高
注释就是这个意思。
所以就这样取出来用了。
2.重测机制:当你拥有weight属性的时候,你觉得你设置的宽高还是你设置的宽高吗?
(比如说LinearLayout的weight,你设为1,方向为横向,那这个时候你设置他的宽度还有什么意义吗?)
(但是我不是很搞得懂为什么一开始不对这个weight的情况进行判断,这样不是更好吗,重测两次性能是不是有点浪费。)
一目了然,水平weight的时候修改宽度怎样怎样,竖直的时候怎样怎样。
3.layoutRequested设为true,开启onLayout()
写的真漂亮!
(怎么找到这个类:使用as的全局搜索功能搜索ViewRootImpl)
(太长了:由于这个方法800多行,所以着重挑选出与视图绘制有关的内容。)
(为什么要看这个类?View的绘制从这个方法开始。)
取得父视图的大小
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
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.match_parent
对应MeasureSpec.EXACTLY,该是多少就是多少
2.wrap_content
对应 MeasureSpec.AT_MOST,你这个资源本身是多大就是多大,大到不能再大为止
3.自己定义了多少多少dp
对应MeasureSpec.EXACTLY,该是多少就是多少
performMeasure()
// Ask host how big it wants to be performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
到了这里,performTraversals()就可以不看了,因为已经进入我们的onMeasure流程了(后面还会有performlayout,performDraw)
进入performMeasure以后,又会在带着参数,调用View的measure方法,measure中又会调用onmeasure方法(方法之间的跳来跳去有点多)
onMeasure()
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
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; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; }
这个方法代表着AT_MOST,EXACTLY(即wrap_content,Match_parent,多少多少dp)这几种正常情况,都是直接把specSize作为结果。UNSPECIFIED一般只会在自定义控件的时候出现这种操作。(你直接覆盖父类的onMeasure()为指定数值时)
setMeasuredDimension():设置我们通过getDefaultSize()获取的视图大小
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { boolean optical = isLayoutModeOptical(this); if (optical != isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int opticalWidth = insets.left + insets.right; int opticalHeight = insets.top + insets.bottom; measuredWidth += optical ? opticalWidth : -opticalWidth; measuredHeight += optical ? opticalHeight : -opticalHeight; } setMeasuredDimensionRaw(measuredWidth, measuredHeight); }
这里很容易理解。每个view都有类型模式。这里的if语句指代的是,如果你不是父模式,那你必须在父模式下对你实际测量出来的大小再进行修改,你得按照你的父亲的大小来。
如果你就是根布局,那你直接就可以setMeasuredDimensionRaw(measuredWidth, measuredHeight);
setMeasuredDimensionRaw()
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }
那么子视图的测量又在哪里呢?
这里仿佛就戛然而止了,但是根布局一定是容器控件,那一定是继承viewGroup的,所以对子view进行measure是不需要外部调用的,是每个继承自viewgroup的自己的任务。
measureChildren
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } }
viewGroup内部维护一个子view的数组mChildren,以及子view的数目mChildrenCount。
会通过简单的for循环(其实这里用增强for循环更好)通过measureChild方法来测绘每个子的大小
measureChild
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
好了,又回到measure了(这里再次强调:只有容器控件才能存放子view,所以必须得继承自viewgroup,所以会自己去测绘子view的大小,不需要外力)
最后梳理下流程
performTraversals()开始我们的视图绘制
getRootMeasureSpec()测出根布局的大小,把测出来的大小传给performMeasure()
performMeasure()调用view的measure方法
measure()调用measure方法
onMeasure()调用getDefaultSize方法获取大小,并把获取的大小通过调用setMeasuredDimension方法进行设置
setMeasuredDimension()对宽高进行最后一步处理,把最终的宽高通过调用setMeasuredDimensionRaw方法进行设置
setMeasuredDimensionRaw()把得到的真实宽高赋给全局变量
一次测绘结束
如果当前的是viewgroup且里面还有子布局,遍历测绘
全部测绘结束
贴上所有源码
performTraversals():太长了,所以贴上有关测量大小的代码
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed! mWidth="
+ mWidth + " measuredWidth=" + host.getMeasuredWidth()
+ " mHeight=" + mHeight
+ " measuredHeight=" + host.getMeasuredHeight()
+ " coveredInsetsChanged=" + contentInsetsChanged);
// Ask host how big it wants to be performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
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; }
performMeasure()
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure"); try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } }
measure()
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { boolean optical = isLayoutModeOptical(this); if (optical != isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int oWidth = insets.left + insets.right; int oHeight = insets.top + insets.bottom; widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth); heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight); } // Suppress sign extension for the low bytes long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL; if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2); 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 flag mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; resolveRtlPropertiesIfNeeded(); int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) { // measure ourselves, this should set the measured dimension flag back onMeasure(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 needed setMeasuredDimensionRaw((int) (value >> 32), (int) value); mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } // flag not set, setMeasuredDimension() was not invoked, we raise // an exception to warn the developer if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) { throw new IllegalStateException("View with id " + getId() + ": " + getClass().getName() + "#onMeasure() did not set the" + " measured dimension by calling" + " setMeasuredDimension()"); } mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; } mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 | (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension }
onMeasure()
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
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; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; }
setMeasuredDimensionRaw()
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { boolean optical = isLayoutModeOptical(this); if (optical != isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int opticalWidth = insets.left + insets.right; int opticalHeight = insets.top + insets.bottom; measuredWidth += optical ? opticalWidth : -opticalWidth; measuredHeight += optical ? opticalHeight : -opticalHeight; } setMeasuredDimensionRaw(measuredWidth, measuredHeight); }
setMeasuredDimensionRaw()
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }
有耐心看到这里的人我会告诉他更多的东西(其实就是我一开始忘了现在补上去)
1. performMeasure其实是告诉host(你当前的宿主view,简单来说就是你要返回的view)你想要怎么样的宽高
// Ask host how big it wants to be performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
注释就是这个意思。
int width = host.getMeasuredWidth(); int height = host.getMeasuredHeight(); boolean measureAgain = false;
所以就这样取出来用了。
2.重测机制:当你拥有weight属性的时候,你觉得你设置的宽高还是你设置的宽高吗?
(比如说LinearLayout的weight,你设为1,方向为横向,那这个时候你设置他的宽度还有什么意义吗?)
(但是我不是很搞得懂为什么一开始不对这个weight的情况进行判断,这样不是更好吗,重测两次性能是不是有点浪费。)
if (lp.horizontalWeight > 0.0f) { width += (int) ((mWidth - width) * lp.horizontalWeight); childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); measureAgain = true; } if (lp.verticalWeight > 0.0f) { height += (int) ((mHeight - height) * lp.verticalWeight); childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); measureAgain = true; } if (measureAgain) { if (DEBUG_LAYOUT) Log.v(mTag, "And hey let's measure once more: width=" + width + " height=" + height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); }
一目了然,水平weight的时候修改宽度怎样怎样,竖直的时候怎样怎样。
3.layoutRequested设为true,开启onLayout()
layoutRequested = true;
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw); boolean triggerGlobalLayoutListener = didLayout || mAttachInfo.mRecomputeGlobalAttributes; if (didLayout) { performLayout(lp, mWidth, mHeight);
写的真漂亮!
相关文章推荐
- LayoutInflater源码最简解读 带你轻松领略源代码之美
- 视图的绘制过程 onMeasure()、onLayout()、dispatchDraw()
- Android如何绘制视图,解释了为何onMeasure有时要调用多次
- android应用程序窗口框架学习(2)-view绘制流程源代码解析-setContentView与LayoutInflater加载解析机制源码分析
- Android如何绘制视图,解释了为何onMeasure有时要调用多次
- FrameLayout measure过程源码Log全解析之六:onMeasure第一部分之MeasureSpec类mode和size
- Android如何绘制视图,解释了为何onMeasure有时要调用多次
- 源码解析Android onmeasure()的量算过程
- FrameLayout measure过程源码Log全解析之四:onMeasure第一部分之ViewGroup对view的管理
- Android如何绘制视图,解释了为何onMeasure有时要调用多次
- Android如何绘制视图,解释了为何onMeasure有时要调用多次
- Android如何绘制视图,解释了为何onMeasure有时要调用多次
- Android如何绘制视图,解释了为何onMeasure有时要调用多次
- FrameLayout measure过程源码Log全解析之五:onMeasure第一部分之MeasureSpec类的bit-mask
- Android自定义视图(二)——onLayout源码 流程 思路详解
- 源码解析Android中View的measure量算过程
- Android View绘制过程,基于Framework源码解析
- Android视图绘制流程完全解析,带你一步步深入了解View(二)
- Android一步步深入了解View(二):视图绘制流程完全解析
- 自定义View系列教程02--onMeasure源码详尽分析