1、前言
之前写过一篇Android进阶知识:事件分发与滑动冲突,主要研究的是关于Android中View事件分发与响应的流程。关于View除了事件传递流程还有一个很重要的就是View的绘制流程。一个Activity界面从启动到绘制完成出现在眼前,这中间经历了哪些过程。每一个View的大小、位置、形状是怎么确定的。这些也都是自定义View的必备知识,所以也是很有必要来学习一下。因为要从Window初始化一直到子View绘制结束,涉及的内容有点多所以分成几篇来写。这第一篇先是一些基础知识,主要包括View基础,Android中的坐标系,MeasureSpec类和Window相关知识总结。
2、View基础
2.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 {
}
2.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组成,所以最终的结构会形成一个树,如下图。
3、坐标系
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(): 获取触摸点到整个屏幕顶边的距离。
4、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的内容就是这些,具体的运用等到看到绘制流程时再说。
5、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进行绘制到屏幕上。下面这张图简单描述了这个逻辑。
6、总结
这一篇内容主要是梳理了一些绘制流程中要用到的基础知识,比较简单,为的是之后在看具体流程代码的时候更加顺利。下一篇就开始看具体绘制流程了。