自定义的onMeasure()的理解

自定义View ,需要掌握的几个点是什么呢? 
我们先把自定义View细分一下,分为两种 
1) 自定义ViewGroup 
2) 自定义View

其实ViewGroup最终还是继承之View,当然它内部做了许多操作;继承之ViewGroup的View我们一般称之为容器,而今天我们不讲这方面,后续有机会再讲。 
来看看自定义View 需要掌握的几点,主要就是两点

一、重写 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {}方法。 
二、重写 protected void onDraw(Canvas canvas) {}方法

空讲理论很难理解,我们还得用例子来说明,记得我前面来写了一篇 Android 微信6.1 tab栏图标和字体颜色渐变的实现 的博客,里面tab的每个item就是通过自定义View来实现的,那么接下来就通过此例子来说明问题。

我们可以把View理解为一张白纸,而自定义View就是在这张白纸上画上我们自己绘制的图案,可以在绘制任何图案,也可以在白纸的任何位置绘制,那么问题来了,白纸哪里来?图案哪里来?位置如何计算?

a)白纸好说,只要我们继承之View,在onDraw(Canvas canvas)中的canvas就是我们所说的白纸

<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-javadoc" style="color: rgb(136, 0, 0); box-sizing: border-box;">/**
 * Created by moon.zhong on 2015/2/13.
 */</span>
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">CustomView</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">extends</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">View</span> {</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-title" style="box-sizing: border-box;">CustomView</span>(Context context) {
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>(context);
    }

    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-title" style="box-sizing: border-box;">CustomView</span>(Context context, AttributeSet attrs) {
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>(context, attrs);
    }

    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-title" style="box-sizing: border-box;">CustomView</span>(Context context, AttributeSet attrs, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> defStyleAttr) {
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>(context, attrs, defStyleAttr);
    }

    <span class="hljs-annotation" style="color: rgb(155, 133, 157); box-sizing: border-box;">@Override</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">protected</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">void</span> <span class="hljs-title" style="box-sizing: border-box;">onDraw</span>(Canvas canvas) {
        <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// canvas 即为白纸</span>
        <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">super</span>.onDraw(canvas);
    }
}</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li></ul>

b)图案呢?这里的图案就是有图片和文字组成,这个也好说,定义一个Bitmap 成员变量,和一个String的成员变量

<code class="hljs cs has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> Bitmap mBitmap ;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> String mName ;
mName = <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"这里直接赋值"</span>;
mBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.ic_launcher) ;</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li></ul>

图片可以通过资源文件可以拿到。

c)计算位置 
所以最核心的也是我们认为最麻烦的地方就是计算绘制的位置,计算位置就得先测量自身的大小,也就是我们必须掌握的两点中的第一点:需要重写 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {}方法 
先来看一下google写的TextView的onMeasure()方法是如何实现的

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int width;
    int height;

    BoringLayout.Metrics boring = UNKNOWN_BORING;
    BoringLayout.Metrics hintBoring = UNKNOWN_BORING;

    if (mTextDir == null) {
        mTextDir = getTextDirectionHeuristic();
    }

    int des = -1;
    boolean fromexisting = false;

    if (widthMode == MeasureSpec.EXACTLY) {
        // Parent has told us how big to be. So be it.
        width = widthSize;
    } else {
        if (mLayout != null && mEllipsize == null) {
            des = desired(mLayout);
        }

        if (des < 0) {
            boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
            if (boring != null) {
                mBoring = boring;
            }
        } else {
            fromexisting = true;
        }

        if (boring == null || boring == UNKNOWN_BORING) {
            if (des < 0) {
                des = (int) FloatMath.ceil(Layout.getDesiredWidth(mTransformed, mTextPaint));
            }
            width = des;
        } else {
            width = boring.width;
        }

        final Drawables dr = mDrawables;
        if (dr != null) {
            width = Math.max(width, dr.mDrawableWidthTop);
            width = Math.max(width, dr.mDrawableWidthBottom);
        }

        if (mHint != null) {
            int hintDes = -1;
            int hintWidth;

            if (mHintLayout != null && mEllipsize == null) {
                hintDes = desired(mHintLayout);
            }

            if (hintDes < 0) {
                hintBoring = BoringLayout.isBoring(mHint, mTextPaint, mTextDir, mHintBoring);
                if (hintBoring != null) {
                    mHintBoring = hintBoring;
                }
            }

            if (hintBoring == null || hintBoring == UNKNOWN_BORING) {
                if (hintDes < 0) {
                    hintDes = (int) FloatMath.ceil(Layout.getDesiredWidth(mHint, mTextPaint));
                }
                hintWidth = hintDes;
            } else {
                hintWidth = hintBoring.width;
            }

            if (hintWidth > width) {
                width = hintWidth;
            }
        }

        width += getCompoundPaddingLeft() + getCompoundPaddingRight();

        if (mMaxWidthMode == EMS) {
            width = Math.min(width, mMaxWidth * getLineHeight());
        } else {
            width = Math.min(width, mMaxWidth);
        }

        if (mMinWidthMode == EMS) {
            width = Math.max(width, mMinWidth * getLineHeight());
        } else {
            width = Math.max(width, mMinWidth);
        }

        // Check against our minimum width
        width = Math.max(width, getSuggestedMinimumWidth());

        if (widthMode == MeasureSpec.AT_MOST) {
            width = Math.min(widthSize, width);
        }
    }

    int want = width - getCompoundPaddingLeft() - getCompoundPaddingRight();
    int unpaddedWidth = want;

    if (mHorizontallyScrolling) want = VERY_WIDE;

    int hintWant = want;
    int hintWidth = (mHintLayout == null) ? hintWant : mHintLayout.getWidth();

    if (mLayout == null) {
        makeNewLayout(want, hintWant, boring, hintBoring,
                      width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
    } else {
        final boolean layoutChanged = (mLayout.getWidth() != want) ||
                (hintWidth != hintWant) ||
                (mLayout.getEllipsizedWidth() !=
                        width - getCompoundPaddingLeft() - getCompoundPaddingRight());

        final boolean widthChanged = (mHint == null) &&
                (mEllipsize == null) &&
                (want > mLayout.getWidth()) &&
                (mLayout instanceof BoringLayout || (fromexisting && des >= 0 && des <= want));

        final boolean maximumChanged = (mMaxMode != mOldMaxMode) || (mMaximum != mOldMaximum);

        if (layoutChanged || maximumChanged) {
            if (!maximumChanged && widthChanged) {
                mLayout.increaseWidthTo(want);
            } else {
                makeNewLayout(want, hintWant, boring, hintBoring,
                        width - getCompoundPaddingLeft() - getCompoundPaddingRight(), false);
            }
        } else {
            // Nothing has changed
        }
    }

    if (heightMode == MeasureSpec.EXACTLY) {
        // Parent has told us how big to be. So be it.
        height = heightSize;
        mDesiredHeightAtMeasure = -1;
    } else {
        int desired = getDesiredHeight();

        height = desired;
        mDesiredHeightAtMeasure = desired;

        if (heightMode == MeasureSpec.AT_MOST) {
            height = Math.min(desired, heightSize);
        }
    }

    int unpaddedHeight = height - getCompoundPaddingTop() - getCompoundPaddingBottom();
    if (mMaxMode == LINES && mLayout.getLineCount() > mMaximum) {
        unpaddedHeight = Math.min(unpaddedHeight, mLayout.getLineTop(mMaximum));
    }

    /*
     * We didn't let makeNewLayout() register to bring the cursor into view,
     * so do it here if there is any possibility that it is needed.
     */
    if (mMovement != null ||
        mLayout.getWidth() > unpaddedWidth ||
        mLayout.getHeight() > unpaddedHeight) {
        registerForPreDraw();
    } else {
        scrollTo(0, 0);
    }

    setMeasuredDimension(width, height);
}
<code class="hljs parser3 has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;"><span class="xml" style="box-sizing: border-box;">
哇!好长!而且方法中还嵌套方法,如果真要算下来,代码量不会低于</span><span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">500</span><span class="xml" style="box-sizing: border-box;">行,看到这么多代码,头都大了,我想这也是我们为什么在学习Android自定义View的时候觉得如此困难的原因。大多数情况下,因为我们是自定义的View,可以说是根据我们的需求定制的View,所以很多里面的功能我们完全没必要,只需要几十行代码就能搞定。看到几十行代码就能搞定,感觉顿时信心倍增(</span><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">^.</span><span class="xml" style="box-sizing: border-box;">^)
在重写这个方法之前,得先了解一个类 MeasureSpec ,如果不了解,没关系,下面就一起来了解一下这个类。先把代码贴出来,膜拜一下
</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li></ul>
public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    public static final int EXACTLY     = 1 << MODE_SHIFT;
    public static final int AT_MOST     = 2 << MODE_SHIFT;
    public static int makeMeasureSpec(int size, int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }
    public static int getMode(int measureSpec) {
        return (measureSpec & MODE_MASK);
    }
    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }
}
<code class="hljs java has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">
这里我把里面一些我认为没必要的代码都去掉了,只留了以上几行代码,这样看起来很清晰,也非常容易理解。
我们先做个转化,把上面几个成员变量转化成二进制

这个就不需要转化了,这里代表的只是一个移动的位置,也就是一个单纯的数字
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> MODE_SHIFT = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">30</span>;
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0x3</span> 就是 <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">11</span> 左移<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">30</span>位 ,就是补<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">30</span>个<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>;
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">private</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> MODE_MASK  = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1100</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> ;
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">00</span> 左移<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">30</span>位
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> UNSPECIFIED = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> ;
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">01</span> 左移<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">30</span>位
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> EXACTLY     = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0100</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> ;
<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span> 左移<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">30</span>位
<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">final</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> AT_MOST     = <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0000</span> ;

你就会问了,这样写有什么好处呢? 细心的人看了上面这几个方法就明白了,每个方法中都有一个 & 的操作,所以我们接下来看看这集几个方法的含义是什么,先从下往上看,先易后难
 <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>、       <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> <span class="hljs-title" style="box-sizing: border-box;">getSize</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> measureSpec) {
            <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> (measureSpec & ~MODE_MASK);
        }
 顾名思义,通过measureSpec这个参数,获取size ,两个都是<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>类型,怎么通过一个<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>类型的数获取另一个<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>类型的数。我们在学习java的时候知道,一个<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>类型是<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">32</span>位,任何<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>类型的数都是有<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">32</span>位,比如一个<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>类型的数值<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3</span>,它也是占有<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">32</span>位,只是高<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">30</span>位全部为<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">0</span>。google 也是利用这一点,让这个<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>类型的measureSpec数存了两个信息,一个就是size,保存在<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>类型的低<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">30</span>位,另一个就是mode,保存在<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span>类型的高<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>位。前面我们看到了有几个成员变量,UNSPECIFIED,EXACTLY,AT_MOST
 者就是mode的三种选择,目前也只有这三种选择,所以只需要<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>位就能实现。
 <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>、      ` <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> <span class="hljs-title" style="box-sizing: border-box;">getMode</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> measureSpec) {
                <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> (measureSpec & MODE_MASK);
        }`
  这也好理解,获取模式,但这些模式有啥用处呢?
  <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">1</span>)、EXACTLY 模式: 准确的、精确的;这种模式,是最容易理解和处理的,可以理解为大小固定,比如在定义layout_width的时候,定义为固定大小 <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">10</span>dp,<span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">20</span>dp,或者match_parent(此时父控件是固定的)这时候,获取出来的mode就是EXACTLY
  <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">2</span>)、AT_MOST 模式: 最大的;这种模式稍微难处理些,不过也好理解,就是View的大小最大不能超过父控件,超过了,取父控件的大小,没有,则取自身大小,这种情况一般都是在layout_width设为warp_content时。
  <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3</span>)、UNSPECIFIED 模式:不指定大小,这种情况,我们几乎用不上,它是什么意思呢,就是View的大小想要多大,就给多大,不受父View的限制,几个例子就好理解了,ScrollView控件就是。

  <span class="hljs-number" style="color: rgb(0, 102, 102); box-sizing: border-box;">3</span>、        `<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> <span class="hljs-title" style="box-sizing: border-box;">makeMeasureSpec</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> size, <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">int</span> mode) {
              <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">if</span> (sUseBrokenMakeMeasureSpec) {
                  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> size + mode;
              } <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">else</span> {
                  <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">return</span> (size & ~MODE_MASK) | (mode & MODE_MASK);
              }
          }`
  这个方法也好理解,封装measureSpec的值,在定义一个View的大小时,我们只是固定了大小,你下次想要获取mode的时候,肯定无法拿到,所以就得自己把模式添加进去,这个方法,在自定义View中,也基本不需要用到,他所使用的场所,是在设置子View的大小的时候需要用到,所以如果是自定义ViewGroup的话,就需要用到。

  感觉讲了这么多,还是不知道怎么使用,接下来就来重写onMeasure()方法,写完之后,你就明白了,这里把注解下载代码里头。
</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li><li style="box-sizing: border-box; padding: 0px 5px;">15</li><li style="box-sizing: border-box; padding: 0px 5px;">16</li><li style="box-sizing: border-box; padding: 0px 5px;">17</li><li style="box-sizing: border-box; padding: 0px 5px;">18</li><li style="box-sizing: border-box; padding: 0px 5px;">19</li><li style="box-sizing: border-box; padding: 0px 5px;">20</li><li style="box-sizing: border-box; padding: 0px 5px;">21</li><li style="box-sizing: border-box; padding: 0px 5px;">22</li><li style="box-sizing: border-box; padding: 0px 5px;">23</li><li style="box-sizing: border-box; padding: 0px 5px;">24</li><li style="box-sizing: border-box; padding: 0px 5px;">25</li><li style="box-sizing: border-box; padding: 0px 5px;">26</li><li style="box-sizing: border-box; padding: 0px 5px;">27</li><li style="box-sizing: border-box; padding: 0px 5px;">28</li><li style="box-sizing: border-box; padding: 0px 5px;">29</li><li style="box-sizing: border-box; padding: 0px 5px;">30</li><li style="box-sizing: border-box; padding: 0px 5px;">31</li><li style="box-sizing: border-box; padding: 0px 5px;">32</li><li style="box-sizing: border-box; padding: 0px 5px;">33</li><li style="box-sizing: border-box; padding: 0px 5px;">34</li><li style="box-sizing: border-box; padding: 0px 5px;">35</li><li style="box-sizing: border-box; padding: 0px 5px;">36</li><li style="box-sizing: border-box; padding: 0px 5px;">37</li><li style="box-sizing: border-box; padding: 0px 5px;">38</li><li style="box-sizing: border-box; padding: 0px 5px;">39</li><li style="box-sizing: border-box; padding: 0px 5px;">40</li></ul>
  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     //这里方法套路都是一样,不管三七 二十一,上来就先把mode 和 size 获取出来。

      int widthMode = MeasureSpec.getMode(widthMeasureSpec);
      int heightMode = MeasureSpec.getMode(heightMeasureSpec);
      int widthSize = MeasureSpec.getSize(widthMeasureSpec);
      int heightSize = MeasureSpec.getSize(heightMeasureSpec);
      //View 真正需要显示的大小
      int width = 0, height = 0;
      //这里是去测量字体大小
      measureText();
      //字体宽度加图片宽度取最大宽度,这里因为字体和图片是上下排列
      int contentWidth = Math.max(mBoundText.width(), mIconNormal.getWidth());
     // 我们渴望得到的宽度
      int desiredWidth = getPaddingLeft() + getPaddingRight() + contentWidth;
      //重点来了,判断模式,这个模式哪里来的呢,就是在编写xml的时候,设置的layout_width
      switch (widthMode) {
      //如果是AT_MOST,不能超过父View的宽度
          case MeasureSpec.AT_MOST:
              width = Math.min(widthSize, desiredWidth);
              break;
              //如果是精确的,好说,是多少,就给多少;
          case MeasureSpec.EXACTLY:
              width = widthSize;
              break;
              //这种情况,纯属在这里打酱油的,可以不考虑
          case MeasureSpec.UNSPECIFIED://我是路过的
              width = desiredWidth;
              break;
      }
      int contentHeight = mBoundText.height() + mIconNormal.getHeight();
      int desiredHeight = getPaddingTop() + getPaddingBottom() + contentHeight;
      switch (heightMode) {
          case MeasureSpec.AT_MOST:
              height = Math.min(heightSize, desiredHeight);
              break;
          case MeasureSpec.EXACTLY:
              height = heightSize;
              break;
          case MeasureSpec.UNSPECIFIED:
              height = contentHeight;
              break;
      }
      //最后不要忘记了,调用父类的测量方法
      setMeasuredDimension(width, height);
  }
<code class="hljs vbscript has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">
  到这里,就算View的大小就已经完成了,自定义View的计算过程和以上方法基本类似。接着就是计算需要显示的图标和字体的位置。这里希望图片和字体垂直排列,并居中显示在View当中,因为当前的View的宽高已经测量好了,接下来的计算也就非常简单了,这里就放在onDraw()方法中计算


d)绘制图标和字体
绘制图标,可以用canvas.drawBitmap(Bitmap bitmap, <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">int</span> <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">left</span>, <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">int</span> top ,Paint paint)方法,bitmap 已经有了,如果不需要对图片作特殊处理 paint 可以传入<span class="hljs-literal" style="color: rgb(0, 102, 102); box-sizing: border-box;">null</span>表示原图原样的绘制在白纸上,所以就差绘制的位置 <span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">left</span> ,top前面已经分析过了,需要把图绘制在View的中间,当然这里还需包含字体,所以可以这样计算<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">left</span> 和top。</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li></ul>

int left = (mViewWidth - mIconNormal.getWidth())/2 ; 
int top = (mViewHeight - mIconNormal.getHeight() - mBoundText.height()) /2 ;

<code class="hljs coffeescript has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">
mViewWidth --<span class="hljs-function" style="box-sizing: border-box;">-></span>View的宽度,mIconNormal --<span class="hljs-function" style="box-sizing: border-box;">-></span>图片的宽度, mBoundText.height() --<span class="hljs-function" style="box-sizing: border-box;">-></span>字体的高度;绘制字体,绘制字体,就比绘制图片稍微麻烦点,因为绘制字体需要用到画笔Paint ,这里定义一个画笔Paint,直接<span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">new</span> 一个出来</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul>
    mTextPaintNormal = new Paint();
    //设置字体大小
    mTextPaintNormal.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, mTextSize, getResources().getDisplayMetrics()));
    //设置画笔颜色,也就是字体颜色
    mTextPaintNormal.setColor(mTextColorNormal);
    //设置抗锯齿
    mTextPaintNormal.setAntiAlias(true);
<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">        这里也是调用Canvas的方法 canvas<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.drawText</span>(mTextValue,<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">x</span>,<span class="hljs-built_in" style="color: rgb(102, 0, 102); box-sizing: border-box;">y</span>, mTextPaintNormal)<span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">;mTextValue需要绘制的字体内容, mTextPaintNormal画笔,x,y需要绘制的位置</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul>
    float x = (mViewWidth - mBoundText.width())/2.0f ;
    float y = (mViewHeight + mIconNormal.getHeight() + mBoundText.height()) /2.0F ;

    整体来说代码还是相当少的。下面把onDraw的代码也贴出来
    @Override
    protected void onDraw(Canvas canvas) {
        drawBitmap(canvas) ;
        drawText(canvas) ;
    }
    private void drawBitmap(Canvas canvas) {
        int left = (mViewWidth - mIconNormal.getWidth())/2 ;
        int top = (mViewHeight - mIconNormal.getHeight() - mBoundText.height()) /2 ;
        canvas.drawBitmap(mIconNormal, left, top ,null);
    }
    private void drawText(Canvas canvas) {
        float x = (mViewWidth - mBoundText.width())/2.0f ;
        float y = (mViewHeight + mIconNormal.getHeight() + mBoundText.height()) /2.0F ;
        canvas.drawText(mTextValue,x,y, mTextPaintNormal);
    }

“` 
总结: 
onMeasure() 方法只要了解了 MeasureSpec 类就不是什么问题,而MeasureSpec 也很简单,onDraw() 方法就需要了解Canvas 类的绘制方法,并且通过简单的Api查询,就基本能实现我们所需的要求。对于自定义View,如果你会重写 测量 和 onDraw 方法,那么就具备了此技能,而如果需要了解更深,自定义有个性,更绚丽的View,就还得深入了解Canvas 、Paint等方法,

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值