Android(安卓)View的绘制之 从源码了解measure的过程。
Android View的绘制之measure过程初解。
前言
每个Android开发者,开发到一定程度后,都不可避免的涉及到各种自定义控件,各种性能优化的问题。
而学习和了解View的绘制过程,会对你的控件开发,性能优化等东西有很大的帮助。今天写这边博客,就是希望带大家从View和ViewGroup的基本的源码中,了解View的绘制过程。整个View的绘制涉及测量(measure),布局(layout),绘制(draw)等一系列过程。layout和draw相对比较简单,我后面的文章会继续跟进,今天这里我们来交流一下测量(measure)的过程。下面附上view绘制的流程图:
measure的目的
首先你应该要知道measure是用来做神马的。简单的来讲measure是为了对view的mMeasuredWidth和mMeasuredHeight这两个变量进行赋值,因此只要这两个变量赋值完成了,view的measure就完成了。目的就是那么简单。
measure的流程
要知道流程,我们必须知道源码中涉及的关键方法。对应的view以及viewGroup中关键方法如下。按调用顺序排列,为了篇幅清洁暂时没写入参。
View中:
- public final void measue();
- protected void onMeasure()
- protected final void setMeasuredDimension()
ViewGroup中:
- protected void measureChildren()
- protected void measureChild()
- protected void measureChildWithMargins()
- public static int getChildMeasureSpec()
我们来依次的研究这些方法。先从关键的view开始。因为赋值是是在view中进行。viewGroup中其实是遍历ViewTree中的所有子view,再调用view中的measure方法实现赋值。
预备知识
在讲解之前给大家复习一下MeasureSpec(测量细则).
请各位还不是特别了解的看官移步:MeasureSpec介绍
View
进入正题,知道这些,让我们一起开始读源码。
第一个方法measure():
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { //若widthMeasureSpec或heightMeasureSpec就调用onMeasure去赋值。 if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { // first clears the measured dimension flag mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; resolveRtlPropertiesIfNeeded(); // 关键的调用方法。 onMeasure(widthMeasureSpec, heightMeasureSpec); if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) { throw new IllegalStateException("onMeasure() did not set the" + " measured dimension by calling" + " setMeasuredDimension()"); } mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; } mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; }
Tips:mPrivateFlags相关的方法是一套固有逻辑不影响用户measure的过程,这里不展开讨论。大家略过这些方法就好。(其实是自己也不太清楚哈哈~~T.T)
看方法,measure其实非常简单,在发现widthMeasureSpec或 heightMeasureSpec发生变化时就去调用onMeasure()方法。大家可以发现measure方法定义成了final类型,设计者在设计之初就不希望这个方法被开发者修改。保证了android初始化view的原理是一致的。
第二个方法 onMeasure();
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
Tips:这是view的三个measure方法中唯一一个不是final的方法。因此我们在自定义空间中最多控制的也是这个方法。将你需要的widthMeasureSpec和heightMeasureSpec直接传过来就能调用setMeasuredDimension()方法进行赋值了。
第三种方法 setMeasuredDimension()
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }
Tips:不难看出,mMeasuredWidth和mMeasuredHeight已经赋值成功,证明view的measure完成。是不是非常的简单。下面来了解一下ViewGroup的分发方法。
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); } } }
遍历所有的viewTree下所有个子view.若子View未GONE就去measureChild(),测量对应的view.
方法二measureChild()
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); //获取对应的子view测量出来的宽高。 final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); //调用子view的measure方法对mMeasureWidth和mMeasureHeight进行赋值。完成测量 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
方法三measureChildWithMargins()
//和方法二measureChild()完全一样的逻辑。只是宽高计算的时候加入了margin. protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final 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); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
和方法而逻辑雷同。关键是调用方法四的时候,传入的第二个参数int padding的数值加入了margin.
方法4getChildMeasureSpec()
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { //viewGroup的spec int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); //父控件的大小减去padding得到子View的大小。 int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; //根据Mode以及childDimension获取子view对应的MeasureSpec。 switch (specMode) { case MeasureSpec.EXACTLY: //若childDimension有设置具体值,布局具体到具体值。 //若childDimension = match_parent。size赋予测出的大小,mode设置为EXACTLY。 //若childDimension = wrap_content。size赋予测出的大小,mode设置为AT_MOST。 if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. // LayoutParams.MATCH_PARENT的大小 resultSize = size; resultMode = MeasureSpec.EXACTLY; } 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; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: //若childDimension有设置具体值,布局具体到具体值。 //若childDimension = match_parent。size赋予测出的大小,mode设置为AT_MOST。 //若childDimension = wrap_content。size赋予测出的大小,mode设置为AT_MOST。 if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = 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; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: //若childDimension有设置具体值,布局具体到具体值。 //若childDimension = match_parent。size设置为0,mode设置为UNSPECIFIED。 //若childDimension = wrap_content。size设置为0,mode设置为UNSPECIFIED。 if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } break; } //返回一个测量好的 MeasureSpec,包含resultSize以及resultMode。 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
Tips: getChildMeasureSpec是测量的关键步奏。他根据childDimension的情况,把测量出的大小和定好Mode。拼接好,并返回给子view去measure。我在代码中进行了必要的注解,大家可以打开源码,多读几遍加深影响。
后记
这里就是view以及viewGroup中measure最主要的方法介绍。了解了这些,当你再读LinearLayout等布局的measure时候就能水到渠成了。希望这篇文章能对大家能有所帮助。
我的下篇博客会针对view绘制的另两个过程 layout以及draw进行分享,
至于LinearLayout具体的计算绘制过程当我有一定心得后也会写博文和大家分享。
楼猪是工作一年的小虾。各位大神们发现问题希望能及时的拍砖。我会学马上改正,虚心学习的~
更多相关文章
- Android(安卓)九宫格解锁Demo--Android(安卓)进阶之路
- Android面试的一些总结
- Android(安卓)中的 Service 全面总结详解【下】
- Android接口回调机制
- android在java代码中动态添加组件及相关布局方法(LayoutParams)
- Android简单涂鸦以及撤销、重做的实现方法
- Android(安卓)webview加载https链接错误或无响应
- Android(安卓)NDK开发之Jni调用Java对象
- Android(安卓)中 Activity的内存泄漏,原因以及处理方法