这是年假最后一篇笔记了,本篇文章的内容主要来自《android开发艺术探索》,在文章的最后有这本书的网上版本。

项目源码

目录

  • MeasureSpec

    • SpecMode分类

      • UNSPECIFIED
      • EXACTLY
      • AT_MOST
    • MeasureSpec和LayoutParams对应关系
  • measure过程

    • View的measure过程

1. MeasureSpec

MeasureSpec代表的是一个32位的int类型的数值,31 ~ 30为测量模式(SpecMode),29 ~ 0(SpecSize) 为宽高的实际大小。一个完整的MeasureSpec是由SpecMode+SpecSize组合而成,可通过makeMeasureSpec()得到MeasureSpec、通过getMode()得到SpecMode、通过getSize()得到SpecSize。

//打包生成MeasureSpec
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
//解包得到SpecMode
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
//解包得到SpecSize
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}

SpecMode分类

模式 二进制数位 描述
UNSPECIFIED 00 父容器不对View有任何限制,要多大给多大,一般用于系统内部表示一种测量状态
EXACTLY 01 表示父控件已经测量出View的大小。View的最终大小就是SpecSize指定的大小;它对应两种模式第一种对应LayoutParams的match_parent,另一种是具体的数值
AT_MOST 10 父容器指定一个SpecSize,View的大小不能不能超过这个值。对应的是LayoutParams的wrap_content

MeasureSpec和LayoutParams对应关系

LayoutParams配合父容器的MeasureSpec用于约束View的大小,他们两个共同作用下 生成最终的View的MeasureSpec,从而确定View的宽高。需要注意的是顶层View和普通View的测量有所不同。DecorView的MeasureSpec是由窗口的尺寸和自身LayoutParams共同作用生成,普通View是由父容器的MeasureSpec和自身LayoutParams共同作用生成。

顶层view的MeasureSpec生成过程:

……
//获取顶层View的宽高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
……
//生成顶层View的MeasureSpec
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT:
// 精度模式,顶层View的大小就是窗口的大小
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// 最大模式,大小不确定,但顶层View的大小不能超过窗口的大小
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// 精度模式,顶层View的大小为LayoutParams的大小
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}

对于普通View的measure,是由ViewGroup的measure发起的。ViewGroup会调用他的measureChild()来测量子View的宽高在该方法内部会调用getChildMeasureSpec()获取View的MeasureSpec。

下面为measureChild()代码:

protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
//获取子View宽度MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
//获取子View高度MeasureSpec
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

下面为getChildMeasureSpec()代码:

//子View的大小会受父容器的MeasureSpec、自身的LayoutParams、View的padding以及margin影响。
public static int getChildMeasureSpec(int spec, int padding, int childDimension){
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
//子元素可用大小为父容器的尺寸减去padding
int size = Math.max(0, specSize - padding); int resultSize = 0;
int resultMode = 0;
//校验父容器是那种模式
switch (specMode) {
// 父容器为具体精度
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
//子控件的宽或高大于0,代表其设置了具体的宽高值
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子元素为精度模式,占满父容器的剩余空间
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//当子控件为WRAP_CONTENT的时候不管父控件是精度模式还是最大
//化模式,View的模式总是最大化,并且不会超过父容器的剩余空间
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 父容器为最大化模式
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// 子控件设置了具体的值
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子view为精度模式
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 子元素为最大化模式
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 父元素为不受限制模式
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

从上面的代码可以知道,返回View的MeasureSpec大致可以分为一下机制情况:

  • 子View为具体的宽/高,那么View的MeasureSpec都为LayoutParams中大小。
  • 子View为match_parent,父元素为精度模式(EXACTLY),那么View的MeasureSpec也是精准模式他的大小不会超过父容器的剩余空间。
  • 子View为wrap_content,不管父元素是精准模式还是最大化模式(AT_MOST),View的MeasureSpec总是为最大化模式并且大小不超过父容器的剩余空间。
  • 父容器为UNSPECIFIED模式主要用于系统多次Measure的情形,一般我们不需要关心。

2. measure过程

View的measure过程

view测量的过程是由measure()方法完成。该方法不能被重写(是final类型方法),在该方法内部调用了onMeasure()方法用于测量View的大小:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//设置view的宽/高
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

从上面的代码中我们可以知道getDefaultSize()为获取view测量后的大小,下面为getDefaultSize()的源码:

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;
}

getDefaultSize()放回的大小两种,第一种为当specMode为AT_MOST、EXACTLY情况下View的大小为specSize也就是测量后的大小,View的最终大小是在layout阶段确认下来的,不过view的测量大小和最终大小,几乎所有情况下都是相等的。

第二种情况为specMode为UNSPECIFIED,这种模式一般用于系统内部的测量过程,该模式下View的大小为传入getDefaultSize()方法的第一个参数size,从上面的代码可以知道,Size为getSuggestedMinimumWidth()或getSuggestedMinimumHeight()返回的大小。

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

getSuggestedMinimumWidth()和getSuggestedMinimumHeight一样,我们只需要分析一个即可,下面以getSuggestedMinimumWidth()为例:

返回的大小与有没有设置背景有关,当View没有设置背景,返回的为mMinHeight。该值为android.minWidth指定的值(默认为0),如果View指定了背景,view返回的值为max(mMinWidth, mBackground.getMinimumWidth())。

public int getMinimumWidth() {
//获取Drawable的原始高度,如果没有原始高度返回的为-1。如:ShapeDrawable无原始高度,BitmapDrawable有原始高度。
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}

总结:

一般我们在自定View的时候需要重写onMeasure()方法,因为从上面的图表中我们可以知道,当我们指定的属性为warp_content的时候系统返回的是父容器剩余空间的大小,这样就和指定的match_parent给的大小一致了。下面为解决这个问题的方式:

private int mWidth = 200;
private int mHeight = 200; @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, mHeight);
} else if (widthMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, heightSize);
} else if (heightMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSize, mHeight);
}
}

参考

Android开发艺术探索完结篇——天道酬勤

自定义View,有这一篇就够了

View的绘制流程的更多相关文章

  1. 深入理解 Android 之 View 的绘制流程

    概述 本篇文章会从源码(基于Android 6.0)角度分析Android中View的绘制流程,侧重于对整体流程的分析,对一些难以理解的点加以重点阐述,目的是把View绘制的整个流程把握好,而对于特定 ...

  2. 自定义控件(视图)1期笔记02:View的绘制流程

    1. 引言: 来自源码的3个方法: (1)public final void measure():测量,用来控制控件的大小,final不建议覆写 (2)public void layout():布局, ...

  3. Android探究之View的绘制流程

    Android中Activity是作为应用程序的载体存在,代表着一个完整的用户界面,提供了一个窗口来绘制各种视图,当Activity启动时,我们会通过setContentView方法来设置一个内容视图 ...

  4. Android的自定义View及View的绘制流程

    目标:实现Android中的自定义View,为理清楚Android中的View绘制流程“铺路”. 想法很简单:从一个简单例子着手开始编写自定义View,对ViewGroup.View类中与绘制View ...

  5. 【转】深入理解Android之View的绘制流程

    概述 本篇文章会从源码(基于Android 6.0)角度分析Android中View的绘制流程,侧重于对整体流程的分析,对一些难以理解的点加以重点阐述,目的是把View绘制的整个流程把握好,而对于特定 ...

  6. 自定义view:view的绘制流程

    1.view的绘制流程 当 Activity 接收到焦点的时候,它会被请求绘制布局,该请求由 Android framework 处理.绘制是从根节点开始,对布局树进行 measure 和 draw. ...

  7. 深入了解View的绘制流程

    1.  ViewRoot ViewRoot是连接WindowManager与DecorView的纽带,View的整个绘制流程的三大步(measure.layout.draw)都是通过ViewRoot完 ...

  8. 每日一问:简述 View 的绘制流程

    Android 开发中经常需要用一些自定义 View 去满足产品和设计的脑洞,所以 View 的绘制流程至关重要.网上目前有非常多这方面的资料,但最好的方式还是直接跟着源码进行解读,每日一问系列一直追 ...

  9. Android之View的绘制流程

    本篇文章会从源码(基于Android 6.0)角度分析Android中View的绘制流程,侧重于对整体流程的分析,对一些难以理解的点加以重点阐述,目的是把View绘制的整个流程把握好,而对于特定实现细 ...

  10. Android View的绘制流程

    写得太好了,本来还想自己写的,奈何肚里墨水有限,直接转吧.正所谓前人种树,后人乘凉.. View的绘制和事件处理是两个重要的主题,上一篇<图解 Android事件分发机制>已经把事件的分发 ...

随机推荐

  1. 【组合数学】Bzoj2916 [Poi1997]Monochromatic Triangles

    Description 空间中有n个点,任意3个点不共线.每两个点用红线或者蓝线连接,如果一个三角形的三边颜色相同,那么称为同色三角形.给你一组数据,告诉你哪些点间有一条红线,计算同色三角形的总数. ...

  2. BZOJ_4439_[Swerc2015]Landscaping_最小割

    BZOJ_4439_[Swerc2015]Landscaping_最小割 Description FJ有一块N*M的矩形田地,有两种地形高地(用‘#’表示)和低地(用‘.’表示) FJ需要对每一行田地 ...

  3. 我的新纪元Day01

    在我刚刚开通博客园后,想了好久.不知道第一次随笔该写点什么,想写些自己学到的知识,但技术上还是菜鸟的我完全不知道我能向别人分享什么,想到这里有些沮丧. 但万事开头难,只要我入了门,广阔的编程语言的世界 ...

  4. HTML——元素

    HTML 元素 HTML 文档由 HTML 元素定义. HTML 元素 开始标签 * 元素内容 结束标签 * <p> 这是一个段落 </p> <a href=" ...

  5. 并发的核心:CAS 是什么?Java8是如何优化 CAS 的?

    大家可能都听说说 Java 中的并发包,如果想要读懂 Java 中的并发包,其核心就是要先读懂 CAS 机制,因为 CAS 可以说是并发包的底层实现原理. 今天就带大家读懂 CAS 是如何保证操作的原 ...

  6. 【递归打卡2】求两个有序数组的第K小数

    [题目] 给定两个有序数组arr1和arr2,已知两个数组的长度分别为 m1 和 m2,求两个数组中的第 K 小数.要求时间复杂度O(log(m1 + m2)). [举例] 例如 arr1 = [1, ...

  7. 强化学习(十五) A3C

    在强化学习(十四) Actor-Critic中,我们讨论了Actor-Critic的算法流程,但是由于普通的Actor-Critic算法难以收敛,需要一些其他的优化.而Asynchronous Adv ...

  8. SQL优化 MySQL版 - 避免索引失效原则(二)

    避免索引失效原则(二) 注:继上一篇文章继续讲解: 避免索引失效原则(一)https://www.cnblogs.com/StanleyBlogs/p/10482048.html#4195062 作者 ...

  9. 简易版本vue的实现

    用了Vue也有两年时间了,一直以来都是只知其然,不知其所以然,为了能更好的使用Vue不被Vue所奴役,学习一下Vue底层的基本原理. Vue官网有一段这样的介绍:当你把一个普通的JavaScript对 ...

  10. Service Fabric service 根据环境变量读取配置文件

    前言 一个服务或者产品,往往需要三个环境:一个开发环境(Development),一个测试环境(Staging),一个生产环境(Production), 这就不可避免的需要多个配置文件来匹配相应的环境 ...