一、ViewRoot 和 DecorView
ViewRoot
1.ViewRoot的实现类: ViewRootImpl 类
2.作用:连接WindowManager 和 DecorView 的纽带,View 三大流程均是通过ViewRoot来完成的。
当Activity对象被创建完毕后,会将DecorView 添加到Window中,同时会创建ViewRootImpl 对象,并将ViewRootImpl对象和DecorView建立关联。
DecorView
1.DecorView:继承ViewGroup
2.包含一个LinearLayout,里面有一个title和content。 可以通过ViewGroup content = (ViewGroup)findViewById(android.R.id.content) 获取content, 通过content.getChildAt(0)可以获得设置的View。
二、View的绘制流程
View的绘制流程是从ViewRoot 的 performTraversals方法开始的,它经过measure、layout 和 draw 三个过程才能最终绘制一个View出来。
performTraversals 会依次调用performMeasure 、 performLayout、 performDraw三个方法,这三个方法分别完成顶级View的measure、layout 和 draw 这三大流程。 其中在performMeasure中会调用measure方法,在measure方法中会调用onMeasure方法,在onMeasure方法中则会对所有的子元素进行measure过程,这个时候measure流程就从父容器传递到子元素中了,这样就完成了一次measure过程。接着子元素会重复父容器的measure过程,如此反复就完成了整个View树的遍历。 同理, performLayout 和 performDraw的传递流程和performMeasure 是类似的,唯一不同的是performDraw 的传递过程是在draw方法中通过dispatchDraw来实现的。
measure过程决定了View的宽 和 高,Measure 完成以后, 可以通过 getMeasuredWidth 和 getMeasuredHeight 方法来获取到View 测量后的宽和高。
layout过程决定了View的四个顶点的坐标和实际的View的宽高,完成以后,可以通过getTop, getBottom, getLeft, getRight来拿到View的四个顶点的位置,并且通过getWidth 和 getHeight 方法来拿到View的最终宽高。
draw过程则决定了View的显示,只有draw方法完成以后View的内容才能在屏幕上呈现。
三、理解MeasureSpec
MeasureSpec很大程度上决定了一个View的尺寸规格,父容器会影响View的MeasureSpec的创建过程。在测量过程中,系统会将View的LayoutParams 根据父容器所施加的规则转换成对应的MeasureSpec, 然后再根据这个MeasureSpec来测量出View的宽高。下面看一下MeasureSpec内部的一些常量定义:
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/** @hide */
@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
@Retention(RetentionPolicy.SOURCE)
public @interface MeasureSpecMode {}
/**
* Measure specification mode: The parent has not imposed any constraint
* on the child. It can be whatever size it wants.
*/
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
/**
* Measure specification mode: The parent has determined an exact size
* for the child. The child is going to be given those bounds regardless
* of how big it wants to be.
*/
public static final int EXACTLY = 1 << MODE_SHIFT;
/**
* Measure specification mode: The child can be as large as it wants up
* to the specified size.
*/
public static final int AT_MOST = 2 << MODE_SHIFT;
/**
* Creates a measure specification based on the supplied size and mode.
*
* The mode must always be one of the following:
* <ul>
* <li>{@link android.view.View.MeasureSpec#UNSPECIFIED}</li>
* <li>{@link android.view.View.MeasureSpec#EXACTLY}</li>
* <li>{@link android.view.View.MeasureSpec#AT_MOST}</li>
* </ul>
*
* <p><strong>Note:</strong> On API level 17 and lower, makeMeasureSpec's
* implementation was such that the order of arguments did not matter
* and overflow in either value could impact the resulting MeasureSpec.
* {@link android.widget.RelativeLayout} was affected by this bug.
* Apps targeting API levels greater than 17 will get the fixed, more strict
* behavior.</p>
*
* @param size the size of the measure specification
* @param mode the mode of the measure specification
* @return the measure specification based on size and mode
*/
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
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);
}
/**
* Extracts the mode from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the mode from
* @return {@link android.view.View.MeasureSpec#UNSPECIFIED},
* {@link android.view.View.MeasureSpec#AT_MOST} or
* {@link android.view.View.MeasureSpec#EXACTLY}
*/
@MeasureSpecMode
public static int getMode(int measureSpec) {
//noinspection ResourceType
return (measureSpec & MODE_MASK);
}
/**
* Extracts the size from the supplied measure specification.
*
* @param measureSpec the measure specification to extract the size from
* @return the size in pixels defined in the supplied measure specification
*/
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
MeasureSpec 代表一个32 位int 值, 高2位代表SpecMode,指测量模式; 低30 位代表SpecSize, 指在某种测量模式下的规格大小。需要注意的是这里提到的MeasureSpec是指所代表的int值,而非其本身。
SpecMode 有三类,每一类都表示特殊含义:
UNSPECIFIED
父容器不对View有任何限制,要多大给多大,一般用于系统内部。
EXACTLY
父容器已经检测出View所需要的精确大小,View的最终大小是SpecSize所指定的值。 对应于LayoutParams中的match_parent 和 具体的数值这两种模式。
AT_MOST
父容器指定一个可用大小即SpecSize, View 的大小不能大于这个值。 对应于LayoutParams 中的wrap_content.
MeasureSpec 和 LayoutParams的对应关系
对于DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同确定; 对于普通View,其MeasureSpec由父容器的MeasureSpec 和自身LayoutParams 来共同决定。
对于DecorView 来说,在ViewRootImpl中的measureHierarchy方法中有如下一段代码,展示了DecorView的MeasureSpec的创建过程。
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
接着看getRootMeasureSpec方法的实现。
/**
* Figures out the measure spec for the root view in a window based on it's
* layout params.
*
* @param windowSize
* The available width or height of the window
*
* @param rootDimension
* The layout params for one dimension (width or height) of the
* window.
*
* @return The measure spec to use to measure the root view.
*/
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
通过上面的代码,DecorView 的MeasureSpec的产生过程就很明确了,遵循如下规则,根据LayoutParams中的宽高的参数来划分。
LayoutParams.MATCH_PARENT : 精确模式,大小就是窗口的大小
LayoutParams.WRAP_CONTENT : 最大模式,大小不定, 但是不能超过窗口的大小
固定大小: 精确模式, 大小为LayoutParams中指定的大小
二、自定义View