Android开发之自定义控件(一)---onMeasure详解

通过本篇博客你将学到以下知识点:

①自定义控件onMeasure的过程

②彻底理解MeasureSpec

③了解View的绘制流程

④对测量过程中需要的谷歌工程师给我们准备好的其它的一些方法的源码深入理解。

为了响应文章的开头,我们从一个“Hello World!”的小例子说起,这个例子我们自定义一个View让它显示“Hello World!”非常简单,代码如下

[java] view plain copy

  1. package com.example.customviewpractice;
  2. import android.content.Context;
  3. import android.graphics.Canvas;
  4. import android.graphics.Color;
  5. import android.graphics.Paint;
  6. import android.util.AttributeSet;
  7. import android.view.View;
  8. public class CustomView1 extends View {
  9. private Paint mPaint;
  10. private String str = "Hello World!";
  11. public CustomView1(Context context, AttributeSet attrs) {
  12. super(context, attrs);
  13. init();
  14. }
  15. private void init() {
  16. // 实例化一个画笔工具
  17. mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
  18. // 设置字体大小
  19. mPaint.setTextSize(50);
  20. // 设置画笔颜色
  21. mPaint.setColor(Color.RED);
  22. }
  23. // 重写onMeasure方法
  24. @Override
  25. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  26. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  27. }
  28. // 重写onDraw方法
  29. @Override
  30. protected void onDraw(Canvas canvas) {
  31. super.onDraw(canvas);
  32. /**
  33. * getWidth() / 2 - mPaint.measureText(str) / 2让文字在水平方向居中
  34. */
  35. canvas.drawText(str, getWidth() / 2 - mPaint.measureText(str) / 2,
  36. getHeight()/2, mPaint);
  37. }
  38. }

它的布局文件如下

[html] view plain copy

  1. <LinearLayout
  2. xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:tools="http://schemas.android.com/tools"
  4. android:layout_width="match_parent"
  5. android:layout_height="match_parent"
  6. android:orientation="vertical" >
  7. <com.example.customviewpractice.CustomView1
  8. android:id="@+id/cus_textview"
  9. android:layout_width="wrap_content"
  10. android:layout_height="wrap_content"
  11. android:background="@android:color/darker_gray" />
  12. </LinearLayout>

运行结果如下

这样一个大大的"Hello World!"呈现在我们面前,可能有的人会问到底怎样去自定义一个控件呢?别急我们慢慢的,一点一点的去学习,首先你可以想象一下,假如我要求你去画一个空心的圆,你会怎么做,首先你要拿张白纸,然后你会问我圆的半径多大?圆的位置在哪?圆的线条颜色是什么?圆的线条粗细是多少?等我把这些问题都告诉你之后,你就会明白要求,并按照这个要求去画一个圆。我们自定义控件呢,也是这样需要下面三个步骤:

①重写onMeasure(获得半径的大小)

②重写onLayout(获得圆的位置)

③重写onDraw(用实例化的画笔包括:颜色,粗细等去绘画)

待这三个方法都重写完后我们的自定义控件就完成了,为了讲的能够详细我们这一篇专门来讲解onMeasure以及和其相关的方法,首先我们需要明白的是Android给我提供了可以操纵控件测量的方法是onMeasure()方法,在上面的自定义控件中我们采用了其默认的实现

[java] view plain copy

  1. @Override
  2. rotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  3. super.onMeasure(widthMeasureSpec, heightMeasureSpec);

看到这,可能大部分人都要问,这里的widthMeasureSpec和heightMeasureSpec是从何处来?要到哪里去?其实这两个参数是由View的父容器传递过来的测量要求,在上述自定义控件中也就是我们的LinearLayout,为什么这么说?这么说是有依据的我们都知道在Activity中可以调用其setContentView方法为界面填充一个布局

[java] view plain copy

  1. protected void onCreate(Bundle savedInstanceState) {
  2. super.onCreate(savedInstanceState);
  3. setContentView(R.layout.activity_main);
  4. }

在setContentView方法中做了哪些事情呢?我们看看他的源码

[java] view plain copy

  1. public void setContentView(int layoutResID) {
  2. getWindow().setContentView(layoutResID);
  3. }

我们看到它调用了getWindow方法,没什么可说的,跟着步骤去看getWindow方法的源码

[java] view plain copy

  1. public Window getWindow() {
  2. return mWindow;
  3. }

这里返回一个Window实例,其本质是继承Window的PhoneWindow,所以在Acitivity中的setContentView中getWindow.setContentView()getWindow.setContentView()其实就是PhoneWindow.setContentView()我们来Look Look它的代码

[java] view plain copy

  1. public void setContentView(int layoutResID) {
  2. if (mContentParent == null) {
  3. installDecor();
  4. } else {
  5. mContentParent.removeAllViews();
  6. }
  7. mLayoutInflater.inflate(layoutResID, mContentParent);
  8. final Callback cb = getCallback();
  9. if (cb != null) {
  10. cb.onContentChanged();
  11. }
  12. }

该方法首先会判断是否是第一次调用setContentView方法,如果是第一次调用则调用installDecor()方法,否则将mContentParent中的所有View移除掉

然后调用LayoutInflater将我们的布局文件加载进来并添加到mContentParent视图中。跟上节奏我们来看看installDecor()方法的源码

[java] view plain copy

  1. private void installDecor() {
  2. if (mDecor == null) {
  3. //mDecor为空,则创建一个Decor对象
  4. mDecor = generateDecor();
  5. mDecor.setIsRootNamespace(true);
  6. }
  7. if (mContentParent == null) {
  8. //generateLayout()方法会根据窗口的风格修饰,选择对应的修饰布局文件
  9. //并且将id为content(android:id="@+id/content")的FrameLayout赋值给mContentParent
  10. mContentParent = generateLayout(mDecor);
  11. 。。。省略部分代码。。。
  12. }
  13. }

可以发现在这个方法中首先会去判断mDecor是否为空如果为空会调用generateDecor方法,它干了什么呢?

[java] view plain copy

  1. protected DecorView generateDecor() {
  2. return new DecorView(getContext(), -1);
  3. }

可以看到它返回了一个DecorView,DecorView类是FrameLayout的子类,是一个内部类存在于PhoneWindow类中,这里我们知道它是FrameLayout的子类就ok了。

在installDecor方法中判断了mDecor是否为空后,接着会在该方法中判断mContentParent是否为空,如果为空就会调用generateLayout方法,我们来看看它做了什么。。。

[java] view plain copy

  1. protected ViewGroup generateLayout(DecorView decor) {
  2. 。。。省略部分代码。。。
  3. View in = mLayoutInflater.inflate(layoutResource, null);
  4. decor.addView(in, new ViewGroup.LayoutParams(FILL_PARENT, FILL_PARENT));
  5. ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
  6. 。。。省略部分代码。。。
  7. return contentParent;
  8. }

根据窗口的风格修饰类型为该窗口选择不同的窗口布局文件(根视图),这些窗口修饰布局文件指定一个用来存放Activity自定义布局文件的ViewGroup视图,一般为FrameLayout 其id 为: android:id="@android:id/content",并将其赋给mContentParent,到这里mContentParent和mDecor均已生成,而我们xml布局文件中的布局则会被添加至mContentParent。接着对上面的过程做一个简单的总结如下图

我们用张图来说明下层次结构

注:此图引自http://blog.csdn.net/qinjuning/article/details/7226787这位大神的博客。

所以说实际上我们在写xml布局文件的时候我们的根布局并不是我们能在xml文件中能看到的最上面的那个,而是FrameLayout,我们再用谷歌给我提供的hierarchyviewer这个工具来看看我们最开始的那个小例子的布局情况,看完你就明白了


看到了吧,在LinearLayout的上面是FrameLayout。到这可能有的人会说你这是写的啥?跟自定义控件一点关系都没有,其实只有了解了上面过程我们才能更好的去理解自定义控件
到这里我们回到最初我们提出的问题widthMeasureSpec和heightMeasureSpec是从哪来?我们在上面提到是从其父View传递过来的,那么它的父View的这两个参数又是从哪来,这样一步一步我们就需要知道View绘制的时候是从儿开始的,其实担任此重任的是ViewRootImpl,绘制开始是从ViewRootImpl中的performTraversals()这个方法开始的,我们来看看源码,可能有的人会说又看源码,只有看源码才能学的更透彻,这里我们只看主要的代码,理解其流程即可,其实performTraversals()方法的代码很多,我们省略后如下

[java] view plain copy

  1. private void performTraversals() {
  2. int desiredWindowWidth;
  3. int desiredWindowHeight;
  4. int childWidthMeasureSpec;
  5. int childHeightMeasureSpec;
  6. 。。。省略部分代码。。。
  7. DisplayMetrics packageMetrics =
  8. mView.getContext().getResources().getDisplayMetrics();
  9. desiredWindowWidth = packageMetrics.widthPixels;
  10. desiredWindowHeight = packageMetrics.heightPixels;
  11. 。。。省略部分代码。。。
  12. childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
  13. childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
  14. 。。。省略部分代码。。。
  15. host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  16. 。。。省略部分代码。。。
  17. }

我们清楚的看到在此调用了getRootMeasureSpec方法后会得到childWidthMeasureSpec和childHeightMeasureSpec,得到的这个数据作为参数传给host(这里的host是View)measure方法。在调用getRootMeasureSpec时需要两个参数desiredWindowWidth ,lp.width和desiredWindowHeight  , lp.height这里我们看到desiredWindowWidth 和desiredWindowHeight就是我们窗口的大小而lp.width和lp.height均为MATCH_PARENT,其在mWindowAttributes(WindowManager.LayoutParams类型)将值赋予给lp时就已被确定。参数搞明白后我们来看看getRootMeasureSpec的源码,看看它都是干了个啥。

[java] view plain copy

  1. /**
  2. * Figures out the measure spec for the root view in a window based on it‘s
  3. * layout params.
  4. *
  5. * @param windowSize
  6. *            The available width or height of the window
  7. *
  8. * @param rootDimension
  9. *            The layout params for one dimension (width or height) of the
  10. *            window.
  11. *
  12. * @return The measure spec to use to measure the root view.
  13. */
  14. private int getRootMeasureSpec(int windowSize, int rootDimension) {
  15. int measureSpec;
  16. switch (rootDimension) {
  17. case ViewGroup.LayoutParams.FILL_PARENT:
  18. // Window can‘t resize. Force root view to be windowSize.
  19. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
  20. break;
  21. case ViewGroup.LayoutParams.WRAP_CONTENT:
  22. // Window can resize. Set max size for root view.
  23. measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
  24. break;
  25. default:
  26. // Window wants to be an exact size. Force root view to be that size.
  27. measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
  28. break;
  29. }
  30. return measureSpec;
  31. }

上面三种情况的英文注释很简单自己翻译下即可理解。总之这个方法执行后不管是哪一种情况我们的根视图都是全屏的。在上面中大家看到MeasureSpec这个类有点陌生,MeasureSpec这个类的设计很精妙,对于学习自定义View也非常重要,理解它对于学习自定义控件非常有用接下来我们就花点篇幅来详细的讲解一下这个类,measurespec封装了父类传递给子类的测量要求,每个measurespec代表宽度或者高度的要求以及大小,也就是说一个measurespec包含size和mode。它有三种mode(模式)
 ①UNSPECIFIED:父View没有对子View施加任何约束。它可以是任何它想要的大小。 
 ②EXACTLY:父View已经确定了子View的确切尺寸。子View将被限制在给定的界限内,而忽略其本身的大小。

③AT_MOST:子View的大小不能超过指定的大小

它有三个主要的方法:

getMode(imeasureSpec)它的作用就是根据规格提取出mode,这里的mode是上面的三种模式之一

getSize(int measureSpec)它的作用就是根据规格提取出size,这里的size就是我们所说的大小

makeMeasureSpec(int size, int mode)根据size和mode,创建一个测量要求。

说了这些可能大家仍然是一头雾水接下来我们看看它的源码,MeasureSpec是View的内部类,它的源码如下

[java] view plain copy

  1. public static class MeasureSpec {
  2. private static final int MODE_SHIFT = 30;
  3. //转化为二进制就是11向左移30位,其结果为:11 0000...(11后跟30个0)
  4. private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
  5. /**
  6. * 下面就是MeasureSpec的三种模式
  7. */
  8. //0左移30位变为 :00  0000...(00后跟30个0)
  9. public static final int UNSPECIFIED = 0 << MODE_SHIFT;
  10. //01左移30位变为:01 0000...(01后跟30个0)
  11. public static final int EXACTLY     = 1 << MODE_SHIFT;
  12. //10左移30位变为:10 0000...(10后跟30个0)
  13. public static final int AT_MOST     = 2 << MODE_SHIFT;
  14. //创建一个测量的规格其高位的前两位代表mode,后面30为代表size,即measureSpec=size+mode;
  15. public static int makeMeasureSpec(int size, int mode) {
  16. return size + mode;
  17. }
  18. //与运算获得mode,这里为什么可以得到mode?因为从measureSpec=size+mode,而MODE_MASK=11 0000...(11后跟30个0)
  19. //我们都知道   & 运算的规则是"遇0为0,遇1不变",而MODE_MASK的前两位为11后面30为全为0,这样进行运算后就可以得到measureSpec的前两位,而刚好
  20. //这前两位就代表了mode。
  21. public static int getMode(int measureSpec) {
  22. return (measureSpec & MODE_MASK);
  23. }
  24. //这里的思想跟getMode方法是一样的,首先对MODE_MASK进行取反,得到的结果为00 1111...(00后跟30个1)& 运算的规则是"遇0为0,遇1不变",而此时~MODE_MASK
  25. //的前两位为0后面30为全为1,所以measureSpec&~MODE_MASK得到的结果去后面30位,这后面的30位就是我们的size
  26. public static int getSize(int measureSpec) {
  27. return (measureSpec & ~MODE_MASK);
  28. }
  29. public static String toString(int measureSpec) {
  30. 。。。内容省略。。。
  31. }
  32. }

MeasureSpec这个类的设计是非常巧妙的,用int类型占有32位,它将其高2位作为mode,后30为作为size这样用32位就解决了size和mode的问题

看完的它的源码大家可能似懂非懂,那么我们就举个例子画个图,让你彻底理解它的设计思想。

假如现在我们的mode是EXACTLY,而size=101(5)那么size+mode的值为:

这时候通过size+mode构造除了MeasureSpec对象及测量要求,当需要获得Mode的时候只需要用measureSpec与MODE_TASK相与即可如下图

我们看到得到的值就是上面的mode,而如果想获得size的话只需要只需要measureSpec与~MODE_TASK相与即可如下图

我们看到得到值就是上面的size。关于这个设计思想大家好好的,慢慢的体会下。

好了到这里我们应该对MeasureSpec有了一定的理解。这时返回去看看我们的getRootMeasureSpec方法,你是不是能看懂了?看懂后回到performTraversals方法,通过getRootMeasureSpec方法得到childWidthMeasureSpec和childHeightMeasureSpec后,我们看到在performTraversals方法中会调用host.measure(childWidthMeasureSpec,childHeightMeasureSpec),这样childWidthMeasureSpec和childHeightMeasureSpec这两个测量要求就一步一步的传下去并由当前View与其父容器共同决定其测量大小,在这里View与ViewGroup中的递归调用过程中有几个重要的方法,而对于View是measure方法,接着我们看看host.measure也就是View的measure方法的源码吧

[java] view plain copy

  1. public class View implements ... {
  2. 。。。省略了部分代码。。。
  3. public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  4. //判断是否为强制布局,即带有“FORCE_LAYOUT”标记 以及 widthMeasureSpec或heightMeasureSpec发生了改变
  5. if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
  6. widthMeasureSpec != mOldWidthMeasureSpec ||
  7. heightMeasureSpec != mOldHeightMeasureSpec) {
  8. //清除MEASURED_DIMENSION_SET标记   ,该标记会在onMeasure()方法后被设置
  9. mPrivateFlags &= ~MEASURED_DIMENSION_SET;
  10. if (ViewDebug.TRACE_HIERARCHY) {
  11. ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
  12. }
  13. // 1、 测量该View本身的大小
  14. onMeasure(widthMeasureSpec, heightMeasureSpec);
  15. if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
  16. throw new IllegalStateException("onMeasure() did not set the"
  17. + " measured dimension by calling"
  18. + " setMeasuredDimension()");
  19. }
  20. //下一步是layout了,添加LAYOUT_REQUIRED标记
  21. mPrivateFlags |= LAYOUT_REQUIRED;
  22. }
  23. mOldWidthMeasureSpec = widthMeasureSpec;//保存值
  24. mOldHeightMeasureSpec = heightMeasureSpec;//保存值
  25. }
  26. }

看到了吧,在measure方法中调用了onMeasure方法,你是不是应该笑30分钟?终于见到我们的onMeasure方法了,这里的onMeasure就是我们重写的onMeasure,它接收两个参数widthMeasureSpec和heightMeasureSpec这两个参数由父View构建,表示父View对子View的测量要求。它有它的默认实现,即重写后我们什么都不做直接调用super.onMeasure方法它的默认实现如下

[java] view plain copy

  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  2. setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
  3. getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
  4. }

在onMeasure方法中直接调用setMeasuredDimension方法,在这里它会调用getSuggestedMinimumWidth方法得到的数据传递给getDefaultSize方法,首先来看看getSuggestedMinimunWidth,getDefaultSize以及setMeasuredDimension这三个方法的源码吧

[java] view plain copy

  1. protected int getSuggestedMinimumWidth() {
  2. //获得android:minHeight这个属性的值,一般不设置此属性如果没有设置的话mMinWidth=0
  3. int suggestedMinWidth = mMinWidth;
  4. if (mBGDrawable != null) {
  5. //获得背景的宽度
  6. final int bgMinWidth = mBGDrawable.getMinimumWidth();
  7. //从背景的宽度和minHeight属性中选出一个最大的值作为返回值
  8. if (suggestedMinWidth < bgMinWidth) {
  9. suggestedMinWidth = bgMinWidth;
  10. }
  11. }
  12. return suggestedMinWidth;
  13. }
  14. //在这里这里size是getSuggestedMinimumWidth方法的返回值,这也是默认的大小
  15. //measureSpec是父View传过来的measureSpec,测量要求
  16. public static int getDefaultSize(int size, int measureSpec) {
  17. int result = size;
  18. //获得测量的模式
  19. int specMode = MeasureSpec.getMode(measureSpec);
  20. //获得测量的大小
  21. int specSize =  MeasureSpec.getSize(measureSpec);
  22. switch (specMode) {
  23. //模式为Unspecified及未指定大小
  24. case MeasureSpec.UNSPECIFIED:
  25. //将上面的size作为结果返回
  26. result = size;
  27. break;
  28. case MeasureSpec.AT_MOST://模式为At_Most,此时使用默认的大小size
  29. case MeasureSpec.EXACTLY://模式为Exactly,此时返回测量值
  30. result = specSize;
  31. break;
  32. }
  33. return result;
  34. }
  35. //为View设置宽和高
  36. protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
  37. mMeasuredWidth = measuredWidth;
  38. mMeasuredHeight = measuredHeight;
  39. mPrivateFlags |= MEASURED_DIMENSION_SET;
  40. }

这只是一个自定义View的默认实现,如果想按照我们的要求来进行绘制的话,重写onMeasure需要添加我们自己的逻辑去实现,最终在onMeasure方法中会调用setMeasureDimenSion决定我们的View的大小,这也是我们重写onMeasure方法的最终目的。

上面这些是对于一个View的测量,android中在进行测量时有两种情况,一种是一个View如Button,ImaeView这中,不能包含子View的对于这种测量一下就ok了,另外一种就是ViewGroup像LinearLayout,FrameLayout这种可以包含子View的,对于这种我们就需要循环遍历每一个子View并为其设置大小,在自定义的ViewGroup中重写onMeasure如下的伪代码

[java] view plain copy

  1. //某个ViewGroup类型的视图
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  3. //必须调用super.ononMeasure()或者直接调用setMeasuredDimension()方法设置该View大小,否则会报异常。
  4. super.onMeasure(widthMeasureSpec , heightMeasureSpec)
  5. //setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
  6. //        getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
  7. //一、遍历每个子View
  8. for(int i = 0 ; i < getChildCount() ; i++){
  9. View child = getChildAt(i);
  10. //调用子View的onMeasure,设置他们的大小,
  11. child.onMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  12. }
  13. //二、直接调用ViewGroup中给我们提供好的measureChildren方法、
  14. measureChildren(widthMeasureSpec, heightMeasureSpec);
  15. }

其实ViewGroup已经为我们提供了测量子View的方法,主要有measureChildren,measureChild和getMeasureSpec,下面我们来分别看看这三个方法都是干了个啥?

measureChildren方法的源码如下

[java] view plain copy

  1. //widthMeasureSpec和heightMeasureSpec:父View传过来的测量要求
  2. protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
  3. final int size = mChildrenCount;
  4. final View[] children = mChildren;
  5. //遍历所有的View
  6. for (int i = 0; i < size; ++i) {
  7. final View child = children[i];
  8. //Gone掉的View排除
  9. if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
  10. measureChild(child, widthMeasureSpec, heightMeasureSpec);
  11. }
  12. }
  13. }

可以看到在measureChildren方法中会遍历所有的View然后对每一个View(不包括gone的View)调用measureChild方法,顺其自然我们来看看measureChild方法的源码

[java] view plain copy

  1. protected void measureChild(View child, int parentWidthMeasureSpec,
  2. int parentHeightMeasureSpec) {
  3. // 获取子元素的布局参数
  4. final LayoutParams lp = child.getLayoutParams();
  5. //将父容器的测量规格以及上下和左右的边距还有子元素本身的布局参数传入getChildMeasureSpec方法计算最终测量要求
  6. final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
  7. mPaddingLeft + mPaddingRight, lp.width);
  8. final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
  9. mPaddingTop + mPaddingBottom, lp.height);
  10. // 将计算好的宽高详细测量值传入measure方法,完成最后的测量
  11. child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  12. }

在measureChild方法中通过getChildMeasureSpec得到最终的测量要求,并将这个测量要求传递给childView的measure方法,就会按照View的那一套逻辑运行。在这里看到调用了getChildMeasureSpec方法我们来看看这个方法的源码

[java] view plain copy

  1. public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
  2. //获取父View的测量模式
  3. int specMode = MeasureSpec.getMode(spec);
  4. //获取父View的测量大小
  5. int specSize = MeasureSpec.getSize(spec);
  6. //父View计算出的子View的大小,子View不一定用这个值
  7. int size = Math.max(0, specSize - padding);
  8. //声明变量用来保存实际计算的到的子View的size和mode即大小和模式
  9. int resultSize = 0;
  10. int resultMode = 0;
  11. switch (specMode) {
  12. // Parent has imposed an exact size on us
  13. //如果父容器的模式是Exactly即确定的大小
  14. case MeasureSpec.EXACTLY:
  15. //子View的高度或宽度>0说明其实一个确切的值,因为match_parent和wrap_content的值是<0的
  16. if (childDimension >= 0) {
  17. resultSize = childDimension;
  18. resultMode = MeasureSpec.EXACTLY;
  19. //子View的高度或宽度为match_parent
  20. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  21. // Child wants to be our size. So be it.
  22. resultSize = size;//将size即父View的大小减去边距值所得到的值赋值给resultSize
  23. resultMode = MeasureSpec.EXACTLY;//指定子View的测量模式为EXACTLY
  24. //子View的高度或宽度为wrap_content
  25. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  26. // Child wants to determine its own size. It can‘t be
  27. // bigger than us.
  28. resultSize = size;//将size赋值给result
  29. resultMode = MeasureSpec.AT_MOST;//指定子View的测量模式为AT_MOST
  30. }
  31. break;
  32. // Parent has imposed a maximum size on us
  33. //如果父容器的测量模式是AT_MOST
  34. case MeasureSpec.AT_MOST:
  35. if (childDimension >= 0) {
  36. // Child wants a specific size... so be it
  37. resultSize = childDimension;
  38. resultMode = MeasureSpec.EXACTLY;
  39. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  40. // Child wants to be our size, but our size is not fixed.
  41. // Constrain child to not be bigger than us.
  42. resultSize = size;
  43. // 因为父View的大小是受到限制值的限制,所以子View的大小也应该受到父容器的限制并且不能超过父View
  44. resultMode = MeasureSpec.AT_MOST;
  45. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  46. // Child wants to determine its own size. It can‘t be
  47. // bigger than us.
  48. resultSize = size;
  49. resultMode = MeasureSpec.AT_MOST;
  50. }
  51. break;
  52. // Parent asked to see how big we want to be
  53. //如果父容器的测量模式是UNSPECIFIED即父容器的大小未受限制
  54. case MeasureSpec.UNSPECIFIED:
  55. //如果自View的宽和高是一个精确的值
  56. if (childDimension >= 0) {
  57. // Child wants a specific size... let him have it
  58. //子View的大小为精确值
  59. resultSize = childDimension;
  60. //测量的模式为EXACTLY
  61. resultMode = MeasureSpec.EXACTLY;
  62. //子View的宽或高为match_parent
  63. } else if (childDimension == LayoutParams.MATCH_PARENT) {
  64. // Child wants to be our size... find out how big it should
  65. // be
  66. //resultSize=0;因为父View的大小是未定的,所以子View的大小也是未定的
  67. resultSize = 0;
  68. resultMode = MeasureSpec.UNSPECIFIED;
  69. } else if (childDimension == LayoutParams.WRAP_CONTENT) {
  70. // Child wants to determine its own size.... find out how
  71. // big it should be
  72. resultSize = 0;
  73. resultMode = MeasureSpec.UNSPECIFIED;
  74. }
  75. break;
  76. }
  77. //根据resultSize和resultMode调用makeMeasureSpec方法得到测量要求,并将其作为返回值
  78. return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
  79. }

我们经常说View的大小是由父View以及当前View共同决定的,这一点从上面这个方法也可以看出。但是这只是一个期望的大小,其大小的最终决定权由setMeasureDimenSion方法决定。

所以最终View的大小将受以下几个方面的影响(以下三点摘自:http://blog.csdn.net/qinjuning/article/details/8074262此博客,这是一个大神。。)

1、父View的MeasureSpec属性;

2、子View的LayoutParams属性;

3、setMeasuredDimension()或者其它类似设定 mMeasuredWidth 和 mMeasuredHeight 值的方法。

关于View的测量过程就介绍完了,可能你一遍没有读懂,只要你认真的去看我相信你一定会有收获,如果你一遍就读懂了,千万别告诉我,我会伤心的,哈哈,因为我花了一周的时间才对onMeasure有了点理解。

时间: 2024-08-06 20:06:06

Android开发之自定义控件(一)---onMeasure详解的相关文章

Android开发技巧之viewstub用法详解及实现延迟加载

这一篇是接着上面的include标签的例子来讲的,地址http://blog.csdn.net/jason0539/article/details/26131831 上一篇的布局中间就用了viewstub这个控件,现在来说一下其作用和用法 " ViewStub 是一个不可见的,大小为0的View,最佳用途就是实现View的延迟加载,避免资源浪费,在需要的时候才加载View " 需要注意的是,加载view之后,viewstub本身就会被新加载进来的view替换掉 上代码了,看完就理解了

Android开发教程复选框详解

前面麦子学院的android开发老师给大家介绍过关于Android开发教程单选框详解,今天麦子学院的android开发老师给大家讲android复选框的一些基本内容. ●设置复选框的Check状态的时候,调用setChecked()方法 ●追加Android复选框被选择时处理的时候, 1.调用setOnCheckedChangeListener()方法,并把CompoundButton.OnCheckedChangeListener实例作为参数传入 2.在CompoundButton.OnChe

Android开发数据存储之ContentProvider详解

转载:十二.ContentProvider和Uri详解 一.使用ContentProvider(内容提供者)共享数据 ContentProvider在android中的作用是对外共享数据,也就是说你可以通过ContentProvider把应用中的数据共享给其他应用访问,其他应用可以通过ContentProvider对你应用中的数据进行添删改查.关于数据共享,以前我们学习过文件操作模式,知道通过指定文件的操作模式为Context.MODE_WORLD_READABLE或Context.MODE_W

Android开发5大布局方式详解

Android中常用的5大布局方式有以下几种: 线性布局(LinearLayout):按照垂直或者水平方向布局的组件. 帧布局(FrameLayout):组件从屏幕左上方布局组件. 表格布局(TableLayout):按照行列方式布局组件. 相对布局(RelativeLayout):相对其它组件的布局方式. 绝对布局(AbsoluteLayout):按照绝对坐标来布局组件. 1. 线性布局 线性布局是Android开发中最常见的一种布局方式,它是按照垂直或者水平方向来布局,通过“android:

Android开发之触摸事件处理机制详解

android触碰消息传递机制 用户的每次触碰(onClick,onLongClick,onScroll,etc.)都是由一个ACTION_DOWN+n个ACTION_MOVE+1个ACTION_UP组成的,用户触碰必先有个ACTION_DOWN响应,用户触碰结束必然会有个ACTION_UP.(当然如果在途中被拦截,就可能不会有了!)那么View是如何分发消息和拦截消息呢? 1.View及其子类都会有的两个方法: public boolean dispatchTouchEvent(MotionE

【10.2.3】ArcGIS Runtime for Android搭建开发环境过程中问题详解

一.Visual Studio Ultimate2012安装过程问题 1.问题描述 安装完成后,您将看到一条消息,指示安装程序已完成,但并不是所有的功能具有已正确安装,以及以下警告消息: Microsoft Web Deploy 3.0 所需的证书不在有效期内根据当前系统时钟或签名文件中的时间戳验证时. 2.解决方案 修改电脑系统时间为2013年7月,断网后重新安装,成功后再联网. Visual Studio Ultimate2012激活密钥:RBCXF-CVBGR-382MK-DFHJ4-C6

[转载] Android签名机制之—签名过程详解

本文转载自: http://www.wjdiankong.cn/android%E7%AD%BE%E5%90%8D%E6%9C%BA%E5%88%B6%E4%B9%8B-%E7%AD%BE%E5%90%8D%E8%BF%87%E7%A8%8B%E8%AF%A6%E8%A7%A3/ 一.前言 又是过了好长时间,没写文章的双手都有点难受了.今天是圣诞节,还是得上班.因为前几天有一个之前的同事,在申请微信SDK的时候,遇到签名的问题,问了我一下,结果把我难倒了..我说Android中的签名大家都会熟悉

Android签名机制之---签名过程详解

一.前言 又是过了好长时间,没写文章的双手都有点难受了.今天是圣诞节,还是得上班.因为前几天有一个之前的同事,在申请微信SDK的时候,遇到签名的问题,问了我一下,结果把我难倒了..我说Android中的签名大家都会熟悉的,就是为了安全,不让别人修改你的apk,但是我们真正的有了解多少呢?所以准备两篇文章好好介绍一下Android中签名机制. 在说道Android签名之前,我们需要了解的几个知识点 1.数据摘要(数据指纹).签名文件,证书文件 2.jarsign工具签名和signapk工具签名 3

Android AndroidManifest 清单文件以及权限详解

每个Android应用都需要一个名为AndroidManifest.xml的程序清单文件,这个清单文件名是固定的并且放在每个Android应用的根目录下.它定义了该应用对于Android系统来说一些非常重要的信息.Android系统需要这些信息才能正常运行该应用.Android程序清单文件主要具有下面作用: ·        它给应用程序Java包命名,这个包名作为应用程序唯一标识符. ·        它描述了应用程序中的每个程序组件-Activity,Service,Broadcast Re