Android 自定义View使用示例(三)

转载
http://www.cnblogs.com/crashmaker/p/3549365.html
From crash_coder linguowu
[email protected]

前言:

  通过Android 自定义ViewAndroid 自定义View使用示例(二),我们知道了如何使用自定义的View,以及Android绘制View的理论基础,其包含三个过程,测量View大小(通过onMeasure()方法实现),计算View位置(通过onLayout()方法实现),最后开始绘制(通过onDraw()方法实现),本篇,我们将结合Android 4.4.2_r1源码详细分析测量过程的具体实现.

  在第一篇里,我们提供了一个自定义的View的源代码,现在引用一下该代码与测量相关的部分:

 1  @Override
 2     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 3         setMeasuredDimension(measureWidth(widthMeasureSpec),
 4                 measureHeight(heightMeasureSpec));
 5     }
 6
 7     /**
 8      * Determines the width of this view
 9      *
10      * @param measureSpec
11      *            A measureSpec packed into an int
12      * @return The width of the view, honoring constraints from measureSpec
13      */
14     private int measureWidth(int measureSpec) {
15         int result = 0;
16         int specMode = MeasureSpec.getMode(measureSpec);
17         int specSize = MeasureSpec.getSize(measureSpec);
18
19         if (specMode == MeasureSpec.EXACTLY) {
20             // We were told how big to be
21             result = specSize;
22         } else {
23             // Measure the text
24             result = (int) mTextPaint.measureText(mText) + getPaddingLeft()
25                     + getPaddingRight();
26             if (specMode == MeasureSpec.AT_MOST) {
27                 // Respect AT_MOST value if that was what is called for by
28                 // measureSpec
29                 result = Math.min(result, specSize);
30             }
31         }
32
33         return result;
34     }
35
36     /**
37      * Determines the height of this view
38      *
39      * @param measureSpec
40      *            A measureSpec packed into an int
41      * @return The height of the view, honoring constraints from measureSpec
42      */
43     private int measureHeight(int measureSpec) {
44         int result = 0;
45         int specMode = MeasureSpec.getMode(measureSpec);
46         int specSize = MeasureSpec.getSize(measureSpec);
47
48         mAscent = (int) mTextPaint.ascent();
49         if (specMode == MeasureSpec.EXACTLY) {
50             // We were told how big to be
51             result = specSize;
52         } else {
53             // Measure the text (beware: ascent is a negative number)
54             result = (int) (-mAscent + mTextPaint.descent()) + getPaddingTop()
55                     + getPaddingBottom();
56             if (specMode == MeasureSpec.AT_MOST) {
57                 // Respect AT_MOST value if that was what is called for by
58                 // measureSpec
59                 result = Math.min(result, specSize);
60             }
61         }
62         return result;
63     }

我们可以看到:protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)是一个override的方法,它接收两个参数,通过字面意思,我们知道,这两个参数分别为宽度测量规格,高度测量规格,此时,我们会有一个疑问,这两个参数是从哪里来的?这个疑问咱们先记下来,给它编个号:Q01,暂时略过,到本文下一部分,我们就知道它的来龙去脉了.接着,我们来看onMeasure方法在本地的实现:
setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));

我们跟进setMeasuredDimension(int,int)方法,看看它到底都做了些什么事情:

因为我们自定义的View是继承自View,所以我们进入View.java(源码位置:/frameworks/base/core/java/android/view/View.java)去看看有没有这个方法:

16575    /**
16576     * <p>This method must be called by {@link #onMeasure(int, int)} to store the
16577     * measured width and measured height. Failing to do so will trigger an
16578     * exception at measurement time.</p>
16579     *
16580     * @param measuredWidth The measured width of this view.  May be a complex
16581     * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
16582     * {@link #MEASURED_STATE_TOO_SMALL}.
16583     * @param measuredHeight The measured height of this view.  May be a complex
16584     * bit mask as defined by {@link #MEASURED_SIZE_MASK} and
16585     * {@link #MEASURED_STATE_TOO_SMALL}.
16586     */
16587    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
16588        boolean optical = isLayoutModeOptical(this);
16589        if (optical != isLayoutModeOptical(mParent)) {
16590            Insets insets = getOpticalInsets();
16591            int opticalWidth  = insets.left + insets.right;
16592            int opticalHeight = insets.top  + insets.bottom;
16593
16594            measuredWidth  += optical ? opticalWidth  : -opticalWidth;
16595            measuredHeight += optical ? opticalHeight : -opticalHeight;
16596        }
16597        mMeasuredWidth = measuredWidth;
16598        mMeasuredHeight = measuredHeight;
16599
16600        mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
16601    }

果然,我们在View.java中找到了这个方法的具体实现,通过方法说明,得知此方法必须被onMeasure()方法调用 ,来保存测量到的宽度和高度,否则的话,会在测量时引发异常.通过代码主线 ,我们知道它将传进去的两个参数赋给本地的mMeasuredWidth和mMeasuredHeight变量,以便在View类中使用;好了,此时我们该抽离出来,回到我们出发的地方:

setMeasuredDimension(measureWidth(widthMeasureSpec),measureHeight(heightMeasureSpec));

有了上面的分析过程,我们知道这个方法中的measureWidth(widthMeasureSpec)是作为测量到的宽度,measureHeight(heightMeasureSpec)是作为测量到的高度,而这两个是需要我们在自定义的View中去实现的,由于测量宽度与高度的过程类似,我们在此文中仅分析measureWidth()的过程,很自然地,我们看看本地的measureWidth()是如何实现的:

 1 /**
 2      * Determines the width of this view
 3      *
 4      * @param measureSpec
 5      *            A measureSpec packed into an int
 6      * @return The width of the view, honoring constraints from measureSpec
 7      */
 8     private int measureWidth(int measureSpec) {
 9         int result = 0;
10         int specMode = MeasureSpec.getMode(measureSpec);
11         int specSize = MeasureSpec.getSize(measureSpec);
12
13         if (specMode == MeasureSpec.EXACTLY) {
14             // We were told how big to be
15             result = specSize;
16         } else {
17             // Measure the text
18             result = (int) mTextPaint.measureText(mText) + getPaddingLeft()
19                     + getPaddingRight();
20             if (specMode == MeasureSpec.AT_MOST) {
21                 // Respect AT_MOST value if that was what is called for by
22                 // measureSpec
23                 result = Math.min(result, specSize);
24             }
25         }
26
27         return result;
28     }

该方法用来确定我们自定义的这个View的宽度,它接收onMeasure()的widthMeasureSpec参数,接着

         int specMode = MeasureSpec.getMode(measureSpec);
         int specSize = MeasureSpec.getSize(measureSpec);
MeasureSpec.getMode(measureSpec),getMode()?我们在上一篇文章中的最后,有如下描述:

MeasureSpec:

  该对象封装了父容器传递给子元素的布局要求,它有三种模式:

1)
UNSPECIFIED:父容器对子元素没有要求,子元素可以得到任意值;
2)
EXACTLY:父窗口决定子元素的大小,子元素将被限定在给定的边界里而忽略它本身大小;
3)
AT MOST:子元素至多达到父窗口指定的大小,子元素不能超过这个边界;

所以我们会想,getMode()方法,应该就是获取上述这三种模式之一吧?我们跟进源码,看看getMode()都做了哪些事情:

18341        /**
18342         * Extracts the mode from the supplied measure specification.
18343         *
18344         * @param measureSpec the measure specification to extract the mode from
18345         * @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
18346         *         {@link android.view.View.MeasureSpec#AT_MOST} or
18347         *         {@link android.view.View.MeasureSpec#EXACTLY}
18348         */
18349        public static int getMode(int measureSpec) {
18350            return (measureSpec & MODE_MASK);
18351        }

由此方法的文字描述部分,我们得知,该方法从接收的参数measureSpec中,获取到对应的三种模式之一,即返回measureSpec & MODE_MASK,这里的MODE_MASK又是个什么东西呢?在View.java中,我们找到在View这个类中,有个内部类MeasureSpec类

18289    public static class MeasureSpec {
18290        private static final int MODE_SHIFT = 30;
18291        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;        ..............................................................

 18297 public static final int UNSPECIFIED = 0 << MODE_SHIFT;
 18298
  18299 /**
  18300 * Measure specification mode: The parent has determined an exact size
  18301 * for the child. The child is going to be given those bounds regardless
  18302 * of how big it wants to be.
  18303 */
  18304 public static final int EXACTLY = 1 << MODE_SHIFT;
  18305
  18306 /**
  18307 * Measure specification mode: The child can be as large as it wants up
  18308 * to the specified size.
  18309 */
  18310 public static final int AT_MOST = 2 << MODE_SHIFT;

        ................................      }

所以,MODE_MASK的值为0x3左移了MODE_SHIFT(30)位,那么,用32位的二进制来表示的话,MODE_MASK为:1100 0000 0000 0000 0000 0000 0000 0000;如果非要探究此时的measureSpec & MODE_MASK后的值是多少,那么我们不妨用Debug模式调试一下我们的代码来获取getMode方法中传进来的参数measureSpec是什么值, 首先,从上面的源码中,可以知道三种MeasureSpec三种模式的值:

UNSPECIFIED = 0 << MODE_SHIFT;即:UNSPECIFIED为:0000 0000 0000 0000 0000 0000 0000 0000

其实我们也可以参阅官方文档对此值的定义:

public static final int UNSPECIFIED

Added in API level 1

Measure specification mode: The parent has not imposed any constraint on the child. It can be whatever size it wants.

Constant Value: 0 (0x00000000)


注:只不过官方文档此处用十六进制表示而已,以下两个模式也都用十六进制表示而已.
 

EXACTLY = 1 << MODE_SHIFT;即 EXACTLY为:0100 0000 0000 0000 0000 0000 0000 0000

public static final int EXACTLY


Added in API level 1


Measure specification mode: 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.

Constant Value: 1073741824 (0x40000000)

 

AT_MOST = 2 << MODE_SHIFT;即 AT_MOST为:1000 0000 0000 0000 0000 0000 0000 0000

public static final int AT_MOST




Added in API level 1




Measure specification mode: The child can be as large as it wants up to the specified size.

Constant Value: -2147483648 (0x80000000)


MODE_MASK为:1100 0000 0000 0000 0000 0000 0000 0000

好,我们来看一下debug前,自定义的View在布局文件中的layout_width的配置及我所调试的设备的屏幕像素为480*800,也就是我的显示屏宽为480像素;

<com.project.summary.customview.CustomView
android:id="@+id/customView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:colorValue="@color/textRed"
app:textSize="20sp"
app:textString="This the Custom View1!!!" />

调试结果出来,此时传入的measureSpec的值是-2147483648,到了这里,我们又会产生一个疑问 ,为什么是它?为什么是这个值?我们先把这个疑问做个标记:Q02;到了文章最后,这个疑问就能解开了,这里先把思路跳出来,继续分析我们的measureWidth()这个本地方法的代码;

 1 /**
 2      * Determines the width of this view
 3      *
 4      * @param measureSpec
 5      *            A measureSpec packed into an int
 6      * @return The width of the view, honoring constraints from measureSpec
 7      */
 8     private int measureWidth(int measureSpec) {
 9         int result = 0;
10         int specMode = MeasureSpec.getMode(measureSpec);
11         int specSize = MeasureSpec.getSize(measureSpec);
12
13         if (specMode == MeasureSpec.EXACTLY) {
14             // We were told how big to be
15             result = specSize;
16         } else {
17             // Measure the text
18             result = (int) mTextPaint.measureText(mText) + getPaddingLeft()
19                     + getPaddingRight();
20             if (specMode == MeasureSpec.AT_MOST) {
21                 // Respect AT_MOST value if that was what is called for by
22                 // measureSpec
23                 result = Math.min(result, specSize);
24             }
25         }
26
27         return result;
28     }

上面我们已经分析到第10行,由于第11行是获取传入的measureSpec的大小,过程与获取传入的measureSpec的模式类似,这里暂时先略过,接下来看第13行代码,这里要对获取到的模式进行判断,由上一篇文章,我们知道,如果自定义的View在布局文件中指定固定大小,那么,它的模式就是属于MeasureSepc.EXACTLY,此时,measureWidth()这个本地方法就返回11行所得的大小,否则进入另外一个分支,因为本系列中我们实现的实现上是一个类似于TextView的自定义控件,那么,这个View的大小就应该由它所绘制的文字长度来决定,此时,我们先计算出文字的宽度,然后再对其模式进行判断,如果模式是属于measureSpec.AT_MOST,我们通过数学运算,比较文字长度与通过传入的measureSpec所包含的大小,它们之中更小的那个做为我们控件的宽度.

  文章开头的相关代码中,本地方法:getMeasureHeight()的过程与本地方法getMeasureWidth()类似,在此不再分析.

  在此总结一下,文章开头引用的代码是我们在编写自定义View时,在重写onMeasure()这个方法时的一般步骤,那么,本文中的分析过程中还留有两个疑问:

Q01:protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)是一个override的方法,它接收两个参数,通过字面意思,我们知道,这两个参数分别为宽度测量规格,高度测量规格,此时,我们会有一个疑问,这两个参数是从哪里来的?
Q02:调试结果出来,此时传入的measureSpec的值是-2147483648,到了这里,我们又会产生一个疑问 ,为什么是它?为什么是这个值?

要探究这两个疑问,我们在本系列第二篇文章中,曾经提过Android绘制View的理论基础,从那篇文章中,我们明白,Android要绘制View的时候,必须要先遍历View的树形结构,并且先从最顶端的结点开始遍历,通过查找官方文档,我们进入

ViewRootImpl.java(文件位于:/frameworks/base/core/java/android/view/ViewRootImpl.java),一起找出上面的那两个疑问.........

/*********************************友情提醒:开始下面的探究前,最好先休息一下*********************************/

我们先大致浏览一下ViewRootImpl.java,这个文件代码有6707行有没有,不用怕,我们先找到一个叫performtraversals()的方法,看这字面意思,它是要开始遍历的节奏啊,果断跟进去看一下,顺便找找几个有用的干货:

private void performTraversals()
{
.......................................
1122     WindowManager.LayoutParams lp = mWindowAttributes;//详见分析PERFORMTRAVERSALS()点1

.........................................................
1155 Rect frame = mWinFrame;//详见分析PERFORMTRAVERSALS()点2
.......................................................
1563 if (mWidth != frame.width() || mHeight != frame.height()) {
1564 mWidth = frame.width();
1565 mHeight = frame.height();
1566 }
1567
.......................................................................
PERFORMTRAVERSALS()点3:
1634 if (!mStopped) {
1635 boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
1636 (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
1637 if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
1638 || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
1639 int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);//详见getRootMeasureSpec()方法的分析
1640 int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
1641
1642 if (DEBUG_LAYOUT) Log.v(TAG, "Ooops, something changed! mWidth="
1643 + mWidth + " measuredWidth=" + host.getMeasuredWidth()
1644 + " mHeight=" + mHeight
1645 + " measuredHeight=" + host.getMeasuredHeight()
1646 + " coveredInsetsChanged=" + contentInsetsChanged);
1647
1648 // Ask host how big it wants to be
1649 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}

/************************************分析PERFORMTRAVERSALS()点1 开始**********************************/

这里的lp用得还挺多,也许对我们有用,

因为

WindowManager.LayoutParams lp = mWindowAttributes;

所以我们分析一下这个mWindowAttributes是何方神圣:
分析PERFORMTRAVERSALS()点1:mWindowAttributes相关代码:

final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();

我们进入WindowManager类的内部类LayoutParams的构造方法

1 public LayoutParams() {
2 super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
3 type = TYPE_APPLICATION;
4 format = PixelFormat.OPAQUE;
5 }

其中有这么一句:注意两个参数都为LayoutParams.MATCH_PARENT

super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);

因为WindowManager类的内部类LayoutParams继承自ViewGroup.LayoutParams,所以进入ViewGroup的内部类LayoutParams看一下
/frameworks/base/core/java/android/view/ViewGroup.java:

5829 public static class LayoutParams {
5830 /**
5831 * Special value for the height or width requested by a View.
5832 * FILL_PARENT means that the view wants to be as big as its parent,
5833 * minus the parent‘s padding, if any. This value is deprecated
5834 * starting in API Level 8 and replaced by {@link #MATCH_PARENT}.
5835 */
5836 @SuppressWarnings({"UnusedDeclaration"})
5837 @Deprecated
5838 public static final int FILL_PARENT = -1;

..........................................

5918 public LayoutParams(int width, int height) {
5919 this.width = width;
5920 this.height = height;
5921 }

分析总结:这里的width与height,都被赋为LayoutParams.MATCH_PARENT,所以这里的lp的宽与高,都为LayoutParams.MATCH_PARENT

/************************************分析PERFORMTRAVERSALS()点1 结束**********************************/

######################################################################################################################

/************************************分析PERFORMTRAVERSALS()点2 开始**********************************/

1563 if (mWidth != frame.width() || mHeight != frame.height()) {
1564 mWidth = frame.width();
1565 mHeight = frame.height();
1566 })

此时的mWidth为ViewRootImpl的变量,在这里使它的值为frame.width()的值;
frame又是从哪里来的呢?在performTraversals()方法中,1155行,原来它只是个局部变量,

1155        Rect frame = mWinFrame;

到了这里,关键就是找出mWinFrame了,继续找mWinFrame:
在ViewRootImpl的变量声明中:

256 final Rect mWinFrame; // frame given by window manager.

在ViewRootImpl这个类的构造方法中:

360 mWinFrame = new Rect();

frame given by window manager?那大概就是说mWinFrame是由窗口管理类来赋值的了,那么这么里mWinFrame应该就是屏幕的窗口大小了.我们这里先这么假设,后续文章再进行验证.

/**********************************************分析PERFORMTRAVERSALS()点2 结束**************************************/

/**********************************************分析PERFORMTRAVERSALS()点3开始**************************************/
PERFORMTRAVERSALS()点3:performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
1,两个参数:childWidthMeasureSpec,childHeightMeasureSpec分析
a)childWidthMeasureSpec:
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
先分析getRootMeasureSpec的两个参数:
1)mWidth:
见分析点2(
1563 if (mWidth != frame.width() || mHeight != frame.height()) {
1564 mWidth = frame.width();
1565 mHeight = frame.height();
1566 })
所以猜想mWidth就是窗口的初始宽度(本文暂未验证)
2)lp.width:这里的lp就是分析点1中的 WindowManager.LayoutParams lp = mWindowAttributes;即:lp.width为LayoutParams.MATCH_PARENT;
由以上1)和2),我们先搞定了getRootMeasureSpec(mWidth,lp.width)这个方法的两个参数的意义,接下来,我们进入getRootMeasureSpec(mWidth,lp.width)这个方法

b)childHeightMeasureSpec:
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
先分析getRootMeasureSpec的两个参数:
1)mHeight:类似上述的猜想,这里的mHeight就是窗口的初始高度
2)lp.height:这里的lp就是分析点1中的 WindowManager.LayoutParams lp = mWindowAttributes;即:lp.height为LayoutParams.MATCH_PARENT;

因为上述a)与b)的调用过程类似,只不过a)是获取宽度的规格,b)是获取高度的规格,所以以下分析只以获取宽度规格的过程来分析
*******************************************************************进入getRootMeasureSpec()方法的分析**********************************************

1924 private static int getRootMeasureSpec(int windowSize, int rootDimension) {
1925 int measureSpec;
1926 switch (rootDimension) {
1927
1928 case ViewGroup.LayoutParams.MATCH_PARENT:
1929 // Window can‘t resize. Force root view to be windowSize.
1930 measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
1931 break;
1932 case ViewGroup.LayoutParams.WRAP_CONTENT:
1933 // Window can resize. Set max size for root view.
1934 measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
1935 break;
1936 default:
1937 // Window wants to be an exact size. Force root view to be that size.
1938 measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
1939 break;
1940 }
1941 return measureSpec;
1942 }

此方法接收的第二个参数rootDimension,就是lp.width,通过上面的分析,lp.width=LayoutParams.MATCH_PARENT,所以,进入第一个switch分支
此方法的返回值measureSpec=MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
所以,分析此方法,我们也知道,当我们的自定义View的layout_width/layout_height设置成MATCH_PARENT时,MODE 为MeasureSpec.EXACTLY;当设置成WRAP_CONTENT时,MODE为MeasureSpec.AT_MOST;

接下来我们分析1938行:

1938 measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);

***********************************************************************************************************************************************************

*******************************************************************进入MeasureSpec.makeMeasureSpec()方法的分析**********************************************

17245 /**
17246 * Creates a measure specification based on the supplied size and mode.
17247 *
17248 * The mode must always be one of the following:
17249 * <ul>
17250 * <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li>
17251 * <li>{@link android.view.View.MeasureSpec#EXACTLY}</li>
17252 * <li>{@link android.view.View.MeasureSpec#AT_MOST}</li>
17253 * </ul>
17254 *
17255 * @param size the size of the measure specification
17256 * @param mode the mode of the measure specification
17257 * @return the measure specification based on size and mode
17258 */
17259 public static int makeMeasureSpec(int size, int mode) {
17260 return size + mode;
17261 }

此方法在/frameworks/base/core/java/android/view/View.java中的内部类MeasureSpec中的方法,该方法返回两个参数size+mode之和,参数size对应我们传进来的windowSize,即:窗口的初始宽度(当传进来的是mHeight时,为窗口的初始高度);
参数mode对应我们传进来的MeasureSpec.EXACTLY

**************************************************************************************************************************************************************************
有了上面这些分析之后,我们可以进入performMeasure(childWidthMeasureSpec, childHeightMeasureSpec)的分析了:

1913 private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
1914 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
1915 try {
1916 mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
1917 } finally {
1918 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
1919 }
1920 }

*************************************mView.measure(childWidthMeasureSpec, childHeightMeasureSpec)的分析************************************************************

16450    /**
16451     * <p>
16452     * This is called to find out how big a view should be. The parent
16453     * supplies constraint information in the width and height parameters.
16454     * </p>
16455     *
16456     * <p>
16457     * The actual measurement work of a view is performed in
16458     * {@link #onMeasure(int, int)}, called by this method. Therefore, only
16459     * {@link #onMeasure(int, int)} can and must be overridden by subclasses.
16460     * </p>
16461     *
16462     *
16463     * @param widthMeasureSpec Horizontal space requirements as imposed by the
16464     *        parent
16465     * @param heightMeasureSpec Vertical space requirements as imposed by the
16466     *        parent
16467     *
16468     * @see #onMeasure(int, int)
16469     */
16470    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
         .....................................................
16496                // measure ourselves, this should set the measured dimension flag back
16497                onMeasure(widthMeasureSpec, heightMeasureSpec);
16498                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;

16522    }

这里的measure()方法是个final方法,结合该方法的说明,

The actual measurement work of a view is performed in onMeasure()

并且measure的两个参数同时传入onMeasure()中,

所以,才有了文章开头时引用的代码,在自定义的View中,重写onMeasure()方法,那么,本文上部分遗留下来的两个问题,至此就有了答案:

Q01:protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)是一个override的方法,它接收两个参数,通过字面意思,我们知道,这两个参数分别为宽度测量规格,高度测量规格,此时,我们会有一个疑问,这两个参数是从哪里来的?
通过:1639 int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);//详见getRootMeasureSpec()方法的分析,onMeasure的第一个参数widthMeasureSpec就是这里的childWidthMeasureSpec,heightMeasureSpec对应 childHeightMeasureSpec;
Q02:调试结果出来,此时传入的measureSpec的值是-2147483648,到了这里,我们又会产生一个疑问 ,为什么是它?为什么是这个值?

那么这里的measureSpec就是MeasureSpec.makeMeasureSpec()方法的分析中,返回的size+mode;size是手机显示屏的像素宽或者高,文章上半部分中,我调试的手机像素宽是480,而且在自定义的View的布局文件中,layout_width设置成wrap_content,通过上面的分析,当设置成wrap_content时,模式为AT_MOST模式,通过文档描述,它的十进制值是-2147483648,那么size+mode就是480+(-2147483648)=-2147483168,也就是我们调试出来时,所得到的值-2147483648
)

另外,我们或许还会有一个疑问 :为什么MODE_MASK是1100 0000 0000 0000 0000 0000 0000 0000?EXACTLY为:0100 0000 0000 0000 0000 0000 0000 0000?

AT_MOST为:1000 0000 0000 0000 0000 0000 0000 0000?

其实对于这个问题,我们想,既然android规定了MODE必须是EXACTLY,AT_MOST,UNSPECIFIED这三种模式之一,那么,就可以用32位二进制的最高两位来表示,它有00,01,10,11这四种情况,那么它的MODE_MASK取值为

1100 0000 0000 0000 0000 0000 0000 0000就能很方便地取到它的模式了,由getMode()的实现:

return (measureSpec & MODE_MASK);

我们就可以取到它的最高两位,由此来确定它是哪种模式;同理对于getSize():

  public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

对于屏幕宽度,再大的屏幕也用不了32位二进制来表示其尺寸,所以才有measureSpec & ~MODE_MASK,这样就能取到它的值了.

时间: 2024-11-06 17:25:52

Android 自定义View使用示例(三)的相关文章

Android自定义View(三、深入解析控件测量onMeasure)

转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51490283 本文出自:[openXu的博客] 目录: onMeasure什么时候会被调用 onMeasure方法执行流程 MeasureSpec类 从ViewGroup的onMeasure到View的onMeasure ViewGroup中三个测量子控件的方法 getChildMeasureSpec方法 View的onMeasure setMeasuredDimension ??在上一篇

Android 自定义 view(三)&mdash;&mdash; onDraw

前言: 上一篇已经介绍了用自己定义的属性怎么简单定义一个view<Android 自定义view(二) -- attr 使用>,那么接下来我们继续深究自定义view,下一步将要去简单理解自定义view的两个比较重要的方法 onDraw(Canvas canvas) ,在探究 onDraw方法之前,我们必须先深入了解两个类Paint和Canvas .   第一:认识Paint 在探究onDraw之前首先必须要认识两个类,这里给出非常不错的两个资料参考网站,我也是从这里得到想要知道的东西,简单的说

我的Android进阶之旅------&gt;Android自定义View实现带数字的进度条(NumberProgressBar)

今天在Github上面看到一个来自于 daimajia所写的关于Android自定义View实现带数字的进度条(NumberProgressBar)的精彩案例,在这里分享给大家一起来学习学习!同时感谢daimajia的开源奉献! 第一步.效果展示 图1.蓝色的进度条 图2.红色的进度条 图3.多条颜色不同的进度条 图4.多条颜色不同的进度条 版权声明:本文为[欧阳鹏]原创文章,欢迎转载,转载请注明出处! [http://blog.csdn.net/ouyang_peng/article/deta

Android自定义View(LineBreakLayout-自动换行的标签容器)

??最近一段时间比较忙,都没有时间更新博客,今天公司的事情忙完得空,继续为我的自定义控件系列博客添砖加瓦.本篇博客讲解的是标签自动换行的布局容器,正好前一阵子有个项目中需要,想了想没什么难度就自己弄了.而自定义控件系列文章中对于自定义ViewGroup上次只是讲解了一些基础和步骤 Android自定义ViewGroup(四.打造自己的布局容器),这次就着这个例子我们来完成一个能在项目中使用的自定义布局容器. 1. 初步分析 ??首先我们看一看要完成的效果图: ?????? ??上面红色标示出的就

Android自定义view学习笔记02

Android自定义view学习笔记02 本文代码来自于张鸿洋老师的博客之Android 自定义View (二) 进阶 学习笔记,对代码进行些许修改,并补充一些在coding过程中遇到的问题.学习的新东西. 相关代码 //CustomImageView.java package mmrx.com.myuserdefinedview.textview; import android.content.Context; import android.content.res.TypedArray; im

Android 自定义View、ViewGroup和自定义属性

一.Android自定义view属性 1.在res/values/styles.xml文件里面声明一个我们自定义的属性: <resources> <!--name为声明的"属性集合"名,可以随便取,但是最好是设置为跟我们的View一样的名称--> <declare-styleable name="CircleView"> <!--声明我们的属性,名称为default_size,取值类型为尺寸类型(dp,px等)-->

【朝花夕拾】Android自定义View篇之(六)Android事件分发机制(中)从源码分析事件分发逻辑及经常遇到的一些“诡异”现象

前言 转载请注明,转自[https://www.cnblogs.com/andy-songwei/p/11039252.html]谢谢! 在上一篇文章[[朝花夕拾]Android自定义View篇之(五)Android事件分发机制(上)Touch三个重要方法的处理逻辑][下文简称(五),请先阅读完(五)再阅读本文],我们通过示例和log来分析了Android的事件分发机制.这些,我们只是看到了现象,如果要进一步了解事件分发机制,这是不够的,我们还需要透过现象看本质,去研究研究源码.本文将从源码(基

android自定义View (一)MeasureSpec

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

Android自定义View——圆形进度条式按钮

介绍 今天上班的时候有个哥们问我怎么去实现一个按钮式的进度条,先来看看他需要实现的效果图. 和普通的圆形进度条类似,只是中间的地方有两个状态表示,未开始,暂停状态.而且他说圆形进度的功能已经实现了.那么我们只需要对中间的两个状态做处理就行了. 先来看看实现的效果图: 上面说了我们只需要处理中间状态的变化就可以了,对于进度的处理直接使用了弘洋文章中实现: http://blog.csdn.net/lmj623565791/article/details/43371299 下面开始具体实现. 具体实