自定义View-onMeasure篇(1)

作者: b496178cdc84 | 来源:发表于2017-02-18 09:41 被阅读813次

    当年,我初学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>

    相关文章

      网友评论

      本文标题:自定义View-onMeasure篇(1)

      本文链接:https://www.haomeiwen.com/subject/vwrpittx.html