直接继承View来自定义控件时,需要重写onMeasure()方法并设置wrap_content时的大小 原理分析
来源:互联网 发布:wifi连接上没有网络 编辑:程序博客网 时间:2024/05/21 21:39
之前在校学习的时候,一直没有在网上找到比较靠谱的解释,现在毕业了,编程能力也比之前有了不小的提高,就读了一些源代码,加上一些书上的解释,现在算是大体知道原因了吧!如果哪里说的不对,欢迎批评指正。
在开始本篇的正文之前,请允许我先粗略的解释一下MeasureSpec的作用,对本篇的理解会有帮助,但是关于View绘制的流程,本篇暂时不多做介绍了,对View的绘制流程还不是很熟悉的同学,请先通过一些书籍或者其他的博客了解一下View绘制的流程。后期有时间的时候,我会整理一下View绘制的流程,然后发一篇博客,虽然现在网上相关的内容也有不少,但是看了许多之后,自身感受就是要么博客写的千篇一律,要么就是涵盖不全(虽然我是个渣渣,但是宝宝会努力的),当然也有很多大神级的人物写的博客,还是很好的。(真心感谢一些大神的博客,收获颇丰)。下面开始:
MeasureSpec可以理解成是View测量的说明书吧,一个View的MeasureSpce受到本身的LayoutParams以及父View的MeasureSpec的影响。 MeasureSpce里有两个比较重要的属性SpecMode和SpecSize,SpecMode可以理解成是View的测量模式,SpecSize代表的是View的测量大小。MeasureSpec很大程度上影响了一个View尺寸。
其中SpecMode有三类测量模式:
(1)UNSPECIFIED: 这个模式平时用的貌似不太多,查了一下资料,表示父容器不对View有任何限制,一般用于系统内部,其他的也就不再多提了。
(2)EXACTLY:精确模式,此时View的大小就是SpecSize的值,对应match_parent和具体的数值这两种。
(3)AT_MOST:最大化模式,此时View的大小不能超过SpecSize的大小,对应于wrap_content。
好了下面开始正文:为什么在直接继承View来自定义控件时,需要重写onMeasure()方法并设置wrap_content的大小
我们应该都会了解过,View绘制的流程是从父View传递到子View的,对于ViewGroup来说,除了完成自己的measure过程之外,还要完成其所有子View的measure过程,因为对于不同的子View来说,测量的细节也是不相同的,ViewGroup不能够针对不同的子View做统一的measure,所以ViewGroup是一个抽象的类,把测量的过程交给了子View本身去测量,在ViewGroup中有一个measureChildren()方法来遍历所有的子View,执行子View的measure来进行子View的测量。以下是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); } } }
我们可以看到在measureChildren()方法中,去遍历了每一个子View,并通过measureChild()方法来进行对子View的测量,以下是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); }我们可以看到,在measureChild()方法中,调用了getChildMeasureSpec()方法来获取width和height对应的MeasureSpec(稍后会再对getChildMeasureSpec()方法做介绍,在这个地方请先忽略,只知道是获取MeasureSpec的方法就行了。),然后作为参数,调用子View的measure()方法,即child.measure(childWidthMeasureSpec , childHeightMeasureSpec)方法来进行子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 }
该代码相对比较多,我们只看比较重要的部分,由以上代码我们可以看出measure()方法为final类型的方法,因此不被重写,我们可以看到在以上代码的第38行,调用了onMeasure()方法来进行View的测量,下面是onMeasure()方法的相关代码:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
可以看到,在onMeasure()方法中调用了setMeasureDimension()方法,看这个方法的名字就就可以知道,setMeasureDimension()方法的作用是设置View测量后width和height的大小,这个方法的代码就不贴出来了,我们可以看到该方法的有两个参数,两个参数分别对代表View测量的width和height,两个参数的值都是getDefaultSize()方法的返回值,下面我们来看下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; }在getDefaultSize()中,首先根据传递过来的MeasureSpec,来获取对应的SpecMode和SpecSize,我们重点看下SpecMode的AT_MOST和EXACTLY模式,当View的SpecMode处于AT_MOST和EXACTLY这两种模式下的时候,其返回值result都是SpecSize的值。(结论1)
到这里,我先总结一下以上的流程:ViewGroup会以自身的MeasureSpec为参数,调用measureChildren()方法,遍历每一个子View,并以子View和自身的MeasureSpec为参数,调用measureChild()方法,在measureChild()中,会以自身的MeasureSpec为参数,调用getChildMeasureSpec()方法来获取子View的MeasureSpec,然后以获得的子View的MeasureSpec为参数,调用子View的measure()方法来进行子View的测量。在子View的measure()方法中,会去调用onMeasure()进行View的测量,在onMeasure()中会调用setMeasureDimension()方法来设置View测量的宽和高,该宽和高的值为getDefaultSize()方法的返回值,在getDefaultSize()方法中,当子View的MeasureSpec的specMode为AT_MOST和EXACTLY时,getDefaultSize()方法的返回值是子View的MeasureSpec中SpecSize的值。
下面我们来看一下上文中提到但是没有解释的getChildMeasureSpec()方法。以下是该方法的代码:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. 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: 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: 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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } //noinspection ResourceType return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }注意,该方法的第一个参数spec,即MeasureSpec,是父View的MeasureSpec,最后一个参数,childDimension对应的是wrap_content或者是match_parent或者是固定的值。
在该方法中,首先会获得父View的MeasureSpec中对应的specMode和specSize,然后对父View的specMode做判断,根据代码我们可以得出结论:不论父View的specMode是EXACTLY模式,还是AT_MOST模式,当childDimension == WRAP_CONTENT时,即我们设置View的layout_width或者layout_height为wrap_content时,最后得到的resultMode的结果都是AT_MOST模式,resultSize的值等于size的值,而size的值,请看上述代码的第5行,size = Math.max(0 , specSize - padding),即size的值是父View的specSize的值减去padding,也就是说,size的值是父View去掉padding之后剩余空间的值,总结一下就是:当我们对View设置wrap_content的时候,最终获得的View的MeasureSpec的值中,SpecMode的值是AT_MOST,SpecSize值是父View剩余去掉padding之后剩余空间的值,而从上述代码我们也可以看出,当childDimension == MATCH_PARENT时,最终获得的SpecMode的值可能是AT_MOST,也可能是EXACTLY,而SpecSize的值都是size,也就是父View去掉padding之后剩余空间的值,也就是说,不管我们对View设置为wrap_content还是match_parent,最终我们把子View的MeasureSpec传递到getDefaultSize()中之后,得到最后的测量后的大小,都是父View去掉padding之后剩余空间的值,因为不管是wrap_content还是match_parent,最终得到的SpecMode不是AT_MOST就是EXACTLY。(结论2)(在这个地方有点晕的话请看结论1)。
因此,现在我们可以得出结论,在我们直接继承View来自定义控件时,如果不重新onMeasure()方法,不对wrap_content做处理的话,最终对控件设置wrap_content和match_parent后,得到效果是一样的。所以我们需要重写onMeasure)()方法,设置wrap_content时的默认大小。
- 直接继承View来自定义控件时,需要重写onMeasure()方法并设置wrap_content时的大小 原理分析
- 重写View的onMeasure方法
- 自定义View中onMeasure在wrap_content时的处理
- onMeasure()源码分析及自定义View对于wrap_content的支持
- 自定义控件 继承View 使用OnMeasure定义控件宽高
- 自定义view onMeasure方法的重写
- 继承ViewGroup重写onMeasure方法的详解
- 继承ViewGroup重写onMeasure方法的详解
- 自定义View中为什么需要重写onMeasure()方法?
- Android 自定义控件源码分析----谈Android自定义控件中 onMeasure()方法处理 wrap_content 情况的必要性
- 自定义view 重写onMeasure()方法
- 继承ViewGroup后的子类如何重写onMeasure方法
- view工作原理-计算视图大小的过程(onMeasure)
- 自定义Dialog时,会遇到dialog wrap_content,这个时候需要手动设置Dialog大小
- 自定义ViewGroup时,重写方法onMeasure等的说明
- Android 自定义View时处理wrap_content和padding的方法
- View的onMeasure方法
- View 的onMeasure方法
- 准备PAT之All Roads Lead to Rome Dijikstra算法变型
- 高效实现List反序功能的实现
- Iterator、AbstractCollection、AbstractList
- C#路径中获取文件全路径、目录、扩展名、文件名称
- Android开发显示之前获取view的宽高方法
- 直接继承View来自定义控件时,需要重写onMeasure()方法并设置wrap_content时的大小 原理分析
- [日推荐]『拉了吗』出门必备
- 如何设置connect超时时间
- restful API接口规范
- keil5错误
- 606. Construct String from Binary Tree
- UE4获得机器码
- MySql错误
- 高斯噪声和椒盐噪声