Android-自定义View-onMeasure方法

作者: MonkeyLei | 来源:发表于2019-07-12 09:48 被阅读0次

    上一篇的自定义属性中Android-自定义View-自定义属性,我们进行了属性的xml设置以及构造方法里面获取对应的值。其中有关于宽高的获取,以及同时根据宽高来限定半径的最大范围,防止半径*2超出了控件本身的大小。

    现在问题来了,就是我们现在的宽高可能是matchparent或者时wrap_content**(不是文本控件的情况下,比如设置背景来源为资源图片): 这个时候就不能用下面的方法来获取了,因为已经不是一个值了哟,没法进行转换....

        //width = ta.getDimensionPixelOffset(R.styleable.MyTextView01_android_layout_width, 200);
        //height = ta.getDimensionPixelOffset(R.styleable.MyTextView01_android_layout_height, 200);
    

    那么我们这个时候需要在onMeasure中去测量控件的大小,同时针对半径做处理。Let's start it....

    1. start之前了,我们先重写onMeasure,但是还是调用系统的onMeasure

         @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            ///< 采用默 @设置 认的onMeasure看看
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    

    1.1 然后我们分别设置一下自定义控件的wrap_content,match_parent看看...

    1.1.1 对于match_parent

          <me.heyclock.hl.customcopy.MyTextView01
            style="@style/MyTextView01"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@color/colorPrimary"
            app:ccolor="#F50808"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:radius="30dp" />
    

    其中默认宽高是,所以(由于目前宽高还没有计算得到,半径也就相应没处理)半径最多也就是6:

        ///< 控件宽度和高度
        private int width = 12;
        private int height= 12;
    

    全屏效果就是:

    image

    1.1.1 对于wrap_content - 加了一个一定宽高的背景图片android:background="@drawable/luffy"

    <?xml version="1.0" encoding="utf-8"?>
    <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">
    
        <me.heyclock.hl.customcopy.MyTextView01
            style="@style/MyTextView01"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/luffy"
            app:ccolor="#F50808"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:radius="30dp" />
    
    </android.support.constraint.ConstraintLayout>
    

    结果还是全屏:

    image

    **疑问1? **为什么设置的图片是具有一定宽高,但却还是全屏?

    疑问2? 为什么我们设置了背景,但是并没有进行绘制,仍然显示背景?

    先看疑问2,直接看父类构造函数

    image

    如下, 看见没 - 这就是为什么背景会自己有绘制,那是父类帮你做了这件事件....

    image

    所以,一般情况下来讲,我们不需要去管背景,父类去做就行了。除非我们的背景有特殊需求,那我们可以单独定义一个app:bgdrawable属性来做:

        <!--android:background="@drawable/luffy"-->
        <me.heyclock.hl.customcopy.MyTextView01
            style="@style/MyTextView01"
            android:layout_width="200dp"
            android:layout_height="200dp"
            app:ccolor="#F50808"
            app:bgdrawable="@drawable/luffy"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:radius="30dp" />
    

    代码里面获取Bitmap

        ///< 控件自定义背景
        private Bitmap bgDrawable;
    
        .....
        Drawable drawable = ta.getDrawable(R.styleable.MyTextView01_bgdrawable);
            if (null != drawable){
                BitmapDrawable bd = (BitmapDrawable) drawable;
                bgDrawable = bd.getBitmap();
            }
    

    然后进行背景绘制,先绘制背景,再绘制内容

         @Override
        protected void onDraw(Canvas canvas) {
            //super.onDraw(canvas);
            ///< 2.进行绘制
            ///< 先绘制一个背景
            canvas.drawBitmap(bgDrawable, 0, 0, paint);
            ///< 绘制一个圆圈吧-> drawCircle(float cx, float cy, float radius, Paint paint)
            canvas.drawCircle(width/2, height/2,
                    radius + changeRadius, paint);
        }
    

    Nice,兄嘚(当然自定义color作为背景也是可以滴啦....啦...啦...)

    image

    再看疑问1?疑问2相对好点,疑问1其实就是涉及到控件的相关测量,算是比较核心的内容了...搞懂这个才能很好的对我们的控件大小进行很好的控制!

    采用系统默认的super.onMeasure(widthMeasureSpec, heightMeasureSpec);看下源码先:

    image

    然后重点看getDefaultSize, getSuggestedMinimumWidth/Height() - (说点别的,其实如果一开始我们不太懂onMeasure,完全可以把源码的拷过来用先看看效果~~~喵喵....)

       /**
         * Utility to return a default size. Uses the supplied size if the
         * MeasureSpec imposed no constraints. Will get larger if allowed
         * by the MeasureSpec.
         *
         * @param size Default size for this view
         * @param measureSpec Constraints imposed by the parent
         * @return The size this view should be.
         */
        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;
        }
    

    解释:我们去看下官方网站怎么解释(不一定看得很明白哟), 一个是getMode, 一个是getSize View.MeasureSpec | Android Developers

    A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and a mode. There are three possible modes:
    
    UNSPECIFIED
    The parent has not imposed any constraint on the child. It can be whatever size it wants.
    EXACTLY
    The parent has determined an exact size for the child. The child is going to be given those bounds regardless of how big it wants to be.
    AT_MOST
    The child can be as large as it wants up to the specified size.
    MeasureSpecs are implemented as ints to reduce object allocation. This class is provided to pack and unpack the <size, mode> tuple into the int. 
    

    片段1翻译:MeasureSpec包含了父控件、子控件布局所需要的要素。每一个MeasureSpec提供了宽高所需要的必要条件。MeasureSpec由大小和mode组成。有三种模式:

    UNSPECIFIED - 父控件没有对子控件有任何限制 Constant Value: 0 (0x00000000)

    EXACTLY - *父控件决定了子控件准确的尺寸 *Constant Value: 1073741824 (0x40000000)

    AT_MOST - *子控件会成为一定的尺寸大小 *Constant Value: -2147483648 (0x80000000)

    看了官方其实还是不太理解,有一种办法就是我们进行布局不同情况的配置,然后打印看看都什么值: 看这些地方 -> Log.e("test", "h's UNSPECIFIED");

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            ///< 采用默认的onMeasure看看
            //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
            ///< 自己进行相关测量
            int defaultW = 12;
            int defaultH = 12;
    
            int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);
            int wSize = MeasureSpec.getSize(widthMeasureSpec);
            int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
            int hSize = MeasureSpec.getSize(heightMeasureSpec);
    
            ///< 在wrap_content的情况下默认长度为默认宽/高与背景图片对比后的最大值,假设背景图片是200*200,则显示200*200
            int minWSize = Math.max(dp2px(context, defaultW), getSuggestedMinimumWidth());
            int minHSize = Math.max(dp2px(context, defaultH), getSuggestedMinimumHeight());
            ///< wrap_content的specMode是AT_MOST模式,这种情况下宽/高等同于specSize
            //  查表得这种情况下specSize等同于parentSize,也就是父容器当前剩余的大小
            //  在wrap_content的情况下如果不特殊处理,效果等同martch_parent
    
            switch (wSpecMode) {
                case UNSPECIFIED:   ///< 父控件没有针对子控件进行大小限制
                    Log.e("test", "w's UNSPECIFIED");
                    break;
                case EXACTLY:       ///< 父控件决定了子控件准确的尺寸
                    Log.e("test", "w's EXACTLY");
                    break;
                case AT_MOST:       ///< 子控件会成为一定的尺寸大小
                    Log.e("test", "w's AT_MOST");
                    break;
            }
    
            switch (hSpecMode) {
                case UNSPECIFIED:
                    Log.e("test", "h's UNSPECIFIED");
                    break;
                case EXACTLY:
                    Log.e("test", "h's EXACTLY");
                    break;
                case AT_MOST:
                    Log.e("test", "h's AT_MOST");
                    break;
            }
    
            if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(minWSize, minHSize);
            } else if (wSpecMode == MeasureSpec.EXACTLY && hSpecMode == MeasureSpec.EXACTLY) {
                setMeasuredDimension(wSize, hSize);
            } else if (wSpecMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(minWSize, hSize);
            } else if (hSpecMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(wSize, minHSize);
            }
    
            ///< 设置控件宽高
            //setMeasuredDimension(defaultW, defaultH);
        }
    

    **情况1, **wrap_content * wrap_content:

        <!--android:background="@drawable/luffy"-->
        <!--android:minWidth=""-->
        <!--android:minHeight=""-->
        <!--app:bgdrawable="@drawable/luffy"-->
        <!--android:background="@drawable/luffy"-->
        <!--android:minWidth="12dp"-->
        <me.heyclock.hl.customcopy.MyTextView01
            style="@style/MyTextView01"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:ccolor="#F50808"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:radius="30dp" />
    

    wrap_content的模式下是AT_MOST - (其实可以理解:你想想,前两种模式一个是父控件没有做任何限制:一会看看什么情况是这种模式(这种情况很少)。一个是父控件决定尺寸:什么情况下父控件决定尺寸了,很常见的就是子控件设置match_parent,这样自然就是父控件决定尺寸了呀!)

    image

    **情况2 **match_parent * match_parent:

        <!--android:background="@drawable/luffy"-->
        <!--android:minWidth=""-->
        <!--android:minHeight=""-->
        <!--app:bgdrawable="@drawable/luffy"-->
        <!--android:background="@drawable/luffy"-->
        <!--android:minWidth="12dp"-->
        <me.heyclock.hl.customcopy.MyTextView01
            style="@style/MyTextView01"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:ccolor="#F50808"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:radius="30dp" />
    

    match_parent下是EXACTLY模式,就是我们上面说的父控件决定子控件尺寸了啦...

    image

    **情况2 **match_parent * 200dp:其中一个指定宽度dp

        <!--android:background="@drawable/luffy"-->
        <!--android:minWidth=""-->
        <!--android:minHeight=""-->
        <!--app:bgdrawable="@drawable/luffy"-->
        <!--android:background="@drawable/luffy"-->
        <!--android:minWidth="12dp"-->
        <me.heyclock.hl.customcopy.MyTextView01
            style="@style/MyTextView01"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            app:ccolor="#F50808"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:radius="30dp" />
    

    结果是200dp下的模式是EXACTLY, 我的理解是父控件如果只有100,那么还不是只有100,如果父控件大于200, 那么显示的值就应该是子控件的200dp.

    image

    所以,综上打印可以总结为(这样了解以后我们就可以再代码针对我们的布局进行宽高的设置,同时还可以处理下默认尺寸的规范):

    MeasureSpec.EXACTLY是精确尺寸,当我们将控件的layout_width或layout_height指定为具体数值时
    如andorid:layout_width="50dip",或者为FILL_PARENT是,都是控件大小已经确定的情况,都是精确尺寸。
    
    MeasureSpec.AT_MOST是最大尺寸,当控件的layout_width或layout_height指定为WRAP_CONTENT时,
    控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父控件允许的最大尺寸即可。
    因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。
    
    MeasureSpec.UNSPECIFIED是未指定尺寸,这种情况不多,一般都是父控件是AdapterView,
    通过measure方法传入的模式。
    

    Then, 看看getSuggestedMinimumWidth

         protected int getSuggestedMinimumWidth() {
            return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
        }
    

    解释:这个相对好理解,其实就是说如果你的控件设置了背景,那么如果背景不为空的情况下,我们需要获取背景与设置的minWidth之间最大的一个值,比如下面的xml配置:

    image

    这个时候这个方法获取的就应该是图片的宽度/高度...具体有什么用途,后面我们实践的时候再分析。

    疑问1.1 到这里,其实我们大概了解了,如果我们设置的wrap_content模式,虽然指定了背景,但是此时采用默认的系统的super.onMeasure(widthMeasureSpec, heightMeasureSpec); 仍然会是AT_MOST的效果,

    image

    所以会是一个match_parent的效果。这个时候我们就需要自己进行相应的处理,把背景图片的宽高考虑进去:

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            ///< 采用默认的onMeasure看看
            //super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
            ///< 自己进行相关测量
            int defaultW = 12;
            int defaultH = 12;
    
            int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);
            int wSize = MeasureSpec.getSize(widthMeasureSpec);
            int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
            int hSize = MeasureSpec.getSize(heightMeasureSpec);
    
            ///< 在wrap_content的情况下默认长度为默认宽/高与背景图片对比后的最大值,假设背景图片是200*200,则显示200*200
            int minWSize = Math.max(dp2px(context, defaultW), getSuggestedMinimumWidth());
            int minHSize = Math.max(dp2px(context, defaultH), getSuggestedMinimumHeight());
            ///< wrap_content的specMode是AT_MOST模式,这种情况下宽/高等同于specSize
            //  查表得这种情况下specSize等同于parentSize,也就是父容器当前剩余的大小
            //  在wrap_content的情况下如果不特殊处理,效果等同martch_parent
    
            ///< 打印看看妮
            //        switch (wSpecMode) {
            //            case UNSPECIFIED:   ///< 父控件没有针对子控件进行大小限制
            //                Log.e("test", "w's UNSPECIFIED");
            //                break;
            //            case EXACTLY:       ///< 父控件决定了子控件准确的尺寸
            //                Log.e("test", "w's EXACTLY");
            //                break;
            //            case AT_MOST:       ///< 子控件会成为一定的尺寸大小
            //                Log.e("test", "w's AT_MOST");
            //                break;
            //        }
            //
            //        switch (hSpecMode) {
            //            case UNSPECIFIED:
            //                Log.e("test", "h's UNSPECIFIED");
            //                break;
            //            case EXACTLY:
            //                Log.e("test", "h's EXACTLY");
            //                break;
            //            case AT_MOST:
            //                Log.e("test", "h's AT_MOST");
            //                break;
            //        }
    
            if (wSpecMode == MeasureSpec.AT_MOST && hSpecMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(minWSize, minHSize);
            } else if (wSpecMode == MeasureSpec.EXACTLY && hSpecMode == MeasureSpec.EXACTLY) {
                setMeasuredDimension(wSize, hSize);
            } else if (wSpecMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(minWSize, hSize);
            } else if (hSpecMode == MeasureSpec.AT_MOST) {
                setMeasuredDimension(wSize, minHSize);
            }
    
    

    这样不管wrap_content, match_parent, 还是200,wrap_content+带背景都没有问题....

      <me.heyclock.hl.customcopy.MyTextView01
            style="@style/MyTextView01"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@color/colorPrimary"
            app:ccolor="#F50808"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:radius="30dp" />
    

    最少能保证12dp的大小...

    image

    下一步就可以进行绘制半径的重新计算了哟...

    下一篇继续(不然一会字数超了哟)...

    来来来,喝了这杯还有一杯....

    相关文章

      网友评论

        本文标题:Android-自定义View-onMeasure方法

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