以前写过一篇Android进阶知识:事件分发与滑动冲突,主要研究的是关于Android
中View
事件分发与响应的流程。关于View
除了事件传递流程还有一个很重要的就是View
的绘制流程。一个Activity
界面从启动到绘制完成出如今眼前,这中间经历了哪些过程。每个View
的大小、位置、形状是怎么肯定的。这些也都是自定义View
的必备知识,因此也是颇有必要来学习一下。由于要从Window
初始化一直到子View
绘制结束,涉及的内容有点多因此分红几篇来写。这第一篇先是一些基础知识,主要包括View
基础,Android
中的坐标系,MeasureSpec
类和Window
相关知识总结。bash
View
是Android
中全部控件的基类,不管是TextView
、ImageView
仍是Button
、CheckBox
都是继承自View
,能够说咱们的应用界面就是由各式各样的View
组成的。app
//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 {
}
复制代码
ViewGroup
从名字就能够看出来表示一组View
,它能够包含多个View
。平时经常使用的LinearLayout
、RelativeLayout
、FrameLayout
等都是继承自ViewGroup
,而且ViewGroup
也是继承自View
。ide
//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
组成,因此最终的结构会造成一个树,以下图。布局
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
坐标系内的坐标。具体能够看下面这张图。post
View中的方法:学习
getLeft(): 获取View的左边到其父布局左边的距离。
getTop(): 获取View上边到其父布局上边的距离。
getRight(): 获取View右边到其父布局左边的距离。
getBottom(): 获取View下边到其父布局上边的距离。ui
MotionEvent中的方法:this
getX(): 获取触摸点距离所在View左边的距离。
getY(): 获取触摸点距离所在View上边的距离。
getRawX(): 获取触摸点到整个屏幕左边的距离。
getRawY(): 获取触摸点到整个屏幕顶边的距离。spa
MeasureSpec
是View
类里的一个内部类,表示测量规格,它的做用是封装了从父布局传递到子级的布局需求。每一个MeasureSpec
表明宽度或高度的要求。MeasureSpec
由测量尺寸size
和测量模式mode
组成。3d
如上图,MeasureSpec
里表明了一个32位的int
类型。高两位表示测量模式,低30位表示测量大小。其中测量模式分为如下三种:
UNSPECIFIED
模式下,父View
不会约束子View
大小,通常用于系统内部例如ListView
、ScrollView
等。EXACTLY
模式下,父View
为子View
测量出所须要的大小,通常对应match_parent
属性,强制大小充满父布局和父布局同样大,或者具体数值,好比100dp
。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
的内容就是这些,具体的运用等到看到绘制流程时再说。
Window
是一个抽象的窗体的概念,每一个Activity
初始化默认会建立一个Window
,界面上全部的View
都会添加到这个Window
上。Android
中的Window
类也是个抽象类,它的实现类是PhoneWindow
。关于Window
的知识点不少,这里就简单介绍下和Window
有关的概念,了解下与Window
有关的类的做用,主要是帮助理解后面View
加载到Window
过程。
与Window
相关的有这几个类和他们的做用:
Window
又由
WindowManager
来管理,进而会经过
ViewRootImpl
中的
IWindowSession
进行
Binder
通讯,最终经过
WMS
把窗口
Surface
进行绘制到屏幕上。下面这张图简单描述了这个逻辑。
这一篇内容主要是梳理了一些绘制流程中要用到的基础知识,比较简单,为的是以后在看具体流程代码的时候更加顺利。下一篇就开始看具体绘制流程了。