自定义View-onMeasure篇(1)

当年,我初学android的时候,一直觉得能自定义各种View是一件很了不起的事情,现在过了1年,在看了各位大神的博客(感谢我的两个前辈GcsSloop,谷歌的小弟),自己总结出了一套,为了让自己印象更深刻,更是为了能让新人看得懂,也许其中也有一些问题,如果看到不妥,一定要马上指出,不过至少我按照现在这套方法,还没发现比较严重的bug,哈哈。大家做个参考

大概按照国际标准流程去写
<b>onMeasure()
onLayout()
onDraw()</b>
最后在一起写一个基本的自定义ViewGroup,来总结一下。

如果大家觉得写的东西能让你有所收获,就来个赞,如果不能的话,你来打我啊!

OK步入正题,写下面的流程之前,先认识几个知识点,我们一会需要用到的。

<h2>MeasureSpec</h2>
对 就是这个MeasureSpec,我刚才还忘了怎么拼,去ide复制的。这个东西,官网说他是一个16位,还是32位的一个值,具体多少位,我也记不清了,其实我也不需要记得清楚,我就记得,他的一些功能就可以了。至少我是这么做的。我们可以把他理解成一个东西。什么样的东西呢?就是可以把View的测量模式,大小集合起来,又可以拆解的这么一个东西。

    MeasureSpec.makeMeasureSpec(int size,int mode)//将View大小和模式结合起来
    MeasureSpec.getSize(int spec);//从东西中拆分出测量大小
    MeasureSpec.getMode(int spec);//从东西中拆分出测量模式

View的大小:
宽高的实际测量值,地球人都知道

View的测量模式
说到测量模式,这可有的说了,大概是这三种测量模式。

    MeasureSpec.AT_MOST;//这个模式,父容器没有检测到子View的大小,告诉子View你最大不能超过多少
    MeasureSpec.EXACTLY;//父容器已经检测到子View的大小,给出确切的值。
    MeasureSpec.UNSPECIFIED;//这个我不知道,没用过。有兴趣的自己去查一下吧。哇咔咔。

有没有同学像我当初一样有点懵逼,如果有,没关系,看下源码就不懵逼了。

我们就来最简单的ViewGroup 嵌套 View来说,直接看ViewGroup源码。建议大家打开ide,找到ViewGroup的源码,一起分析。

先从ViewGroup的measureChildWithMargins说起

    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);
    }

先看一下各个参数:
<b>child</b> 顾名思义 就是子View
<b>parentWidthMeasureSpec</b> 宽度的测量规格
<b>widthUsed</b> 父View已经使用的宽度
<b>parentHeightMeasureSpec</b> 高度的测量规格
<b>heightUsed</b> 父View已经使用的高度

OK 继续深入里面的代码
//这一句,先拿到了待测量的子View的布局参数

final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 

然后下面有调用getChildMeasureSpec方法,并传进去了相关参数
先看一下传进去的3个参数
<b> parentWidthMeasureSpec</b> 父View的宽度测量规格
<b>mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin+widthUsed</b> 这个参数是在水平方向上内外边距与已经使用的宽度之和,其实也就是 当前viewgroup已经占据的宽度。
<b>lp.width</b> 子View的宽度,这个是从Xml解析出来的宽度

OK 参数分析完了,下一步进去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);
    }

有耐心的童鞋可以先简单的阅读一下代码,
前面两句

  int specMode = MeasureSpec.getMode(spec);
  int specSize = MeasureSpec.getSize(spec);

先从父View传来的测量规格里面,解析出测量模式,和在水平(垂直)方向上的大小(该大小是上级父View传递下来,给子View的总的可用大小,如果这里不理解可以看下面的分析)。

 int size = Math.max(0, specSize - padding);

这句声明size,是上级View传递下来的可用总大小,减去父View在水平(垂直)方向已经占用的大小(就是这个padding,上面传过来的),结果就是 该View在水平(垂直)方向最大的可用大小,就是说子View的宽度(高度)最大,不可超过该大小。如果超过了,那宽度(高度)的大小就是0.

OK继续下面。

int resultSize = 0;
int resultMode = 0;

这句定义了View最终的测量模式,和测量实际大小。

继续下面代码

       // 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;

看第一个case,也就是测量模式MeasureSpec.EXACTLY,看注释就可以知道,这个case是当父View已经有了实际的测量值的情况,我们可以理解为,父View的layout_width 是实际的值,而不是wrap_content或者match_parent判断childDimension是否>= 0 ,还记得childDimension是什么吗?是子view LayoutParams.width,也就是通过Xml解析出来的layout_width的结果。

if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } 

如果解析Xml解析出了layout_width的结果,那么最终大小(resultSize)就是解析出来的结果,而最终的测量模式resultMode 也就是精准测量模式MeasureSpec.EXACTLY,这里也就验证了我们上面提到的,为什么在子View的onMeasure里面可以得到MeasureSpec.EXACTLY.

OK继续下面

 else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            }

这里说一下,为什么childDimension会有<0的情况。
由于解析Xml时候,如果解析到这些属性,LayoutParams就会按如下方式赋值
public static final int MATCH_PARENT = -1;
public static final int WRAP_CONTENT = -2;
所以就会出现小于0的情况。

OK,暂且分析到这里,因为剩下的大家也要一起分析才能搞懂,大家先分析,然后,等到明天,我发下一部分 看看我们分析的是否一样,然后一起交流一下!嘿嘿嘿!!!
接<a href=’http://www.jianshu.com/p/fb687e55b0d1′>写给新人看的自定义View-onMeasure篇(2)</a>

    原文作者:suwanroy
    原文地址: https://www.jianshu.com/p/f6623f00b4a0
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞