Android进阶知识:绘制流程

1、View基础
1.1 View是什么?
View
是Android
中所有控件的基类,无论是TextView
、ImageView
还是Button
、CheckBox
都是继承自View
,可以说我们的应用界面就是由各式各样的View
组成的。
//ImageView继承了View
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {
}
//ImageView继承了View
public class ImageView extends View {
}
//ImageView继承了TextView
public class Button extends TextView {
}
//ImageView继承了CompoundButton
public class CheckBox extends CompoundButton {
}
//ImageView继承了Button
public abstract class CompoundButton extends Button implements Checkable {
}
1.2 ViewGroup是什么?
ViewGroup
从名字就可以看出来表示一组View
,它可以包含多个View
。平时常用的LinearLayout
、RelativeLayout
、FrameLayout
等都是继承自ViewGroup
,并且ViewGroup
也是继承自View
。
//LinearLayout继承了ViewGroup
public class LinearLayout extends ViewGroup {
}
//RelativeLayout继承了ViewGroup
public class RelativeLayout extends ViewGroup {
}
//FrameLayout继承了ViewGroup
public class FrameLayout extends ViewGroup {
}
//ViewGroup继承了View
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
}
又因为不同的应用界面都由不同的View
或ViewGroup
组成,所以最终的结构会形成一个树,如下图。

2、坐标系
Android
中有两种坐标系,一个Android
坐标系,一个是View
坐标系。Android
坐标系以屏幕左上角为原点,向右为x轴正方向,向下为y轴正方向。View
坐标系以父View
的左上角为原点,同样向右为x轴正方向,向下为y轴正方向。Android
中也提供了获取所在坐标的方法。在View
中,有mLeft
、mTop
、mRight
、mBottom
四个成员变量并且提供对应的get
获取方法,这四个值就存储了这个View
相对于父布局的所在位置坐标。除此之外,在View
的事件响应方法onTouchEvent
中会返回一个MotionEvent
对象,这个对象提供了两对方法getX
、getY
和getRawX
、getRawY
,分别获得当前触摸点在所在View
内的坐标和当前触摸点在Android
坐标系内的坐标。具体可以看下面这张图。

-
View中的方法:
getLeft(): 获取View的左边到其父布局左边的距离。
getTop(): 获取View上边到其父布局上边的距离。
getRight(): 获取View右边到其父布局左边的距离。
getBottom(): 获取View下边到其父布局上边的距离。 -
MotionEvent中的方法:
getX(): 获取触摸点距离所在View左边的距离。
getY(): 获取触摸点距离所在View上边的距离。
getRawX(): 获取触摸点到整个屏幕左边的距离。
getRawY(): 获取触摸点到整个屏幕顶边的距离。
3、MeasureSpec类
MeasureSpec
是View
类里的一个内部类,表示测量规格,它的作用是封装了从父布局传递到子级的布局需求。每个MeasureSpec
代表宽度或高度的要求。MeasureSpec
由测量尺寸size
和测量模式mode
组成。

如上图,MeasureSpec
里代表了一个32位的int
类型。高两位表示测量模式,低30位表示测量大小。其中测量模式分为以下三种:
-
UNSPECIFIED 模式
UNSPECIFIED
模式下,父View
不会约束子View
大小,一般用于系统内部例如ListView
、ScrollView
等。 -
EXACTLY 模式
EXACTLY
模式下,父View
为子View
测量出所需要的大小,一般对应match_parent
属性,强制大小充满父布局和父布局一样大,或者具体数值,比如100dp
。 -
AT_MOST 模式
AT_MOST
模式下,父View
为子View
提供一个最大的尺寸大小,子View
大小可以任意由自己决定,但是最大不能超过这个尺寸,一般对应wrap_content
属性,自适应大小。
MeasureSpec
类的源码不是很多,具体如下。
public static class MeasureSpec {
//移位大小
private static final int MODE_SHIFT = 30;
//0x3二进制为11,11 << 30 结果为:11 00000000 00000000 00000000 000000 (30个0)用于后面做与运算
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
//UNSPECIFIED模式:父View没有对子View施加任何约束。它可以是任意大小。
//0 << 30 结果为:00 00000000 00000000 00000000 000000
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
//EXACTLY模式:父View已经为子View确定了确切的大小。不管子View想要多大,他都会得到这些界限。
//1 << 30 结果为:01 00000000 00000000 00000000 000000
public static final int EXACTLY = 1 << MODE_SHIFT;
//AT_MOST模式:子View可以任意大,但不能超过父View的大小
//2 << 30 结果为:10 00000000 00000000 00000000 000000
public static final int AT_MOST = 2 << MODE_SHIFT;
/**
* 根据size和mode创建一个MeasureSpec
*/
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
//sUseBrokenMakeMeasureSpec的值API小于17位true大于17位false
if (sUseBrokenMakeMeasureSpec) {
//兼容API17以前的,通过size+mode获得一个32位int的MeasureSpec
return size + mode;
} else {
//API17以后更加严格,采用位运算,防止溢出
//例如: size:4 mode:AT_MOST
//~MODE_MASK为: 00 11111111 11111111 11111111 111111
//size & ~MODE_MASK:00 00000000 00000000 00000000 000100
//mode : 10 00000000 00000000 00000000 000000
//MODE_MASK: 11 00000000 00000000 00000000 000000
//mode & MODE_MASK: 10 00000000 00000000 00000000 000000
//(size & ~MODE_MASK) | (mode & MODE_MASK) 最终结果:
// 10 00000000 00000000 00000000 000100
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
/**
* Like {@link #makeMeasureSpec(int, int)}, but any spec with a mode of UNSPECIFIED
* will automatically get a size of 0\. Older apps expect this.
*
* @hide internal use only for compatibility with system widgets and older apps
*/
public static int makeSafeMeasureSpec(int size, int mode) {
if (sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {
return 0;
}
return makeMeasureSpec(size, mode);
}
/**
* 从MeasureSpec中获取mode
*/
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
/**
* 从MeasureSpec中获取size
*/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
static int adjust(int measureSpec, int delta) {
final int mode = getMode(measureSpec);
int size = getSize(measureSpec);
if (mode == UNSPECIFIED) {
// No need to adjust size for UNSPECIFIED mode.
return makeMeasureSpec(size, UNSPECIFIED);
}
size += delta;
if (size < 0) {
Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +
") spec: " + toString(measureSpec) + " delta: " + delta);
size = 0;
}
return makeMeasureSpec(size, mode);
}
/**
* Returns a String representation of the specified measure
* specification.
*
* @param measureSpec the measure specification to convert to a String
* @return a String with the following format: "MeasureSpec: MODE SIZE"
*/
public static String toString(int measureSpec) {
int mode = getMode(measureSpec);
int size = getSize(measureSpec);
StringBuilder sb = new StringBuilder("MeasureSpec: ");
if (mode == UNSPECIFIED)
sb.append("UNSPECIFIED ");
else if (mode == EXACTLY)
sb.append("EXACTLY ");
else if (mode == AT_MOST)
sb.append("AT_MOST ");
else
sb.append(mode).append(" ");
sb.append(size);
return sb.toString();
}
}
源码里已经加了注释了,这里再来说一下,首先是几个成员变量。
-
MODE_SHIFT
表示移位大小。 -
MODE_MASK
是进行位运算的遮罩。 -
UNSPECIFIED
、EXACTLY
、AT_MOST
分别对应三种测量模式。
下图是这几个值的二进制表示。

接着来看makeMeasureSpec
、getSize
、getMode
这几个主要的方法。
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
//sUseBrokenMakeMeasureSpec的值API小于17位true大于17位false
if (sUseBrokenMakeMeasureSpec) {
//兼容API17以前的,通过size+mode获得一个32位int的MeasureSpec
return size + mode;
} else {
//API17以后更加严格,采用位运算,防止溢出
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
makeMeasureSpec
方法作用是根据size
和mode
创建一个MeasureSpec
,可以看到根据API
等级不同,实现也不同,API17
之前是直接将size
和mode
相加,API17
之后是采用位运算的方式,位运算集体看下面这张图。

public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
getSize
、getMode
这俩方法分别是从测量规格MeasureSpec
中获取到对应测量大小和测量模式,具体计算看下图。

关于MeasureSpec
的内容就是这些,具体的运用等到看到绘制流程时再说。
4、Window相关
Window
是一个抽象的窗体的概念,每个Activity
初始化默认会创建一个Window
,界面上所有的View
都会添加到这个Window
上。Android
中的Window
类也是个抽象类,它的实现类是PhoneWindow
。关于Window
的知识点很多,这里就简单介绍下和Window
有关的概念,了解下与Window
有关的类的作用,主要是帮助理解后面View
加载到Window
过程。
与Window
相关的有这几个类和他们的作用:
- Window:窗体抽象类。
- PhoneWinow:Window的具体实现类,对View进行管理。
- WindowManager:是个接口,用来管理Window。
- WindowManagerImpl:WindowManager的实现类,包含对Window各种操作(添加、删除、更新)的方法。
- WindowManagerService(WMS):WindowManager的管理者,负责对窗口的管理、Surface的管理等。
- ViewRootImpl:所有View的根,将Window和View联系起来。
-
DecorView:顶级View。
由上面这张图可以清楚的看出应用界面的层级,其中Window
又由WindowManager
来管理,进而会通过ViewRootImpl
中的IWindowSession
进行Binder
通信,最终通过WMS
把窗口Surface
进行绘制到屏幕上。下面这张图简单描述了这个逻辑。

最后
如果你看到了这里,觉得文章写得不错就给个赞呗!欢迎大家评论讨论!如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足,定期免费分享技术干货。谢谢!
