一、基本概念:
1、Activity、Window、PhoneWindow、DecorView之间的关系:
Activity:很熟悉了,我们所能见到页面,不过多阐述;
Window:每个Activity都包含一个Window,也可以说每个Activity都包含一个Window的对象。Window是一个抽象基类,是Activity和View的交互接口,且只有一个实现类PhoneWindow。我们可以将其理解为一个载体,各种View都在这个载体上显示。
public class Activity extends ContextThemeWrappe{
private Window mWindow;
}
PhoneWindow:继承于Window类,是Window的具体实现。在PhoneWindow中持有着一个很重要的View对象,DecorView。
public class PhoneWindow extends Window{
private DecorView mDecor;
}
DecorView:是所有窗口的根View,继承于FrameLayout,并对其进行扩展修饰,比如添加TitleBar等。
2、MeasureSpc:
① 在 Google 官方文档中是这么定义:
A MeasureSpec encapsulates the layout requirements passed from parent to child. Each MeasureSpec represents a requirement for either the width or the height. A MeasureSpec is comprised of a size and a mode.
大概意思是:MeasureSpec 封装了从父View 传递给到子View的布局需求,每个MeasureSpec代表了view的宽度或高度的测量要求,即size(大小)和mode(模式)。
② MeasureSpc是一个32位的int值,高2位代表SpecMode(测量模式),低30位代表了SpecSize(测量大小)。
③ SpecMode分为三类:
· unSpecified:父容器不对子View有任何限制,要多大给多大,这种情况一般用于系统内部,表示一种测量的状态。(可忽略)
· Exactly:父容器已经测量出子View的大小。对应是view的LayoutParams的match_parent或者具体数值。
· At_most:父容器指定了一个可用大小的SpecSize,view的大小不能够大于这个值,对应wrap_content。
④ 重点:View的MeasureSpec,由父 view 的MeasureSpec加上子 View 的自己的 LayoutParams,通过相应的规则转化,共同决定。
从父View的角度分析MeasureSpec的转化规则:
- 当父View的SpecMode是Exactly的时候,即父View的大小是确定的种情况:
①若子View宽/高为match_parent,则子View的SpecMode也是Exactly模式,且其大小是父容器的剩余空间。
②若子View宽/高为wrap_parent,则子View的SpecMode为At_most模式,且其大小不超过父容器的剩余空间。
③若子View宽/高为具体数值,则子View的SpecMode为Exactly模式,大小就是该具体数值。 - 当父View的SpecMode是At_most的时候,即父View的大小是不确定的情况:
①若子View宽/高为match_parent,则子View的SpecMode也是At_most模式,且其大小不超过父容器的剩余空间。
②若子View宽/高为wrap_parent,则子View的SpecMode为At_most模式,且其大小不超过父容器的剩余空间。
③若子View宽/高为固定宽高,则子View的SpecMode为Exactly模式,大小就是该固定宽高。
从子View的角度分析MeasureSpec的转化规则:
- 当子View的宽/高采用固定宽高,无论父容器的SpecMode是什么,子View的SpecMode都是Exactly,其大小遵循LayoutParams的大小。
- 当子VIew的宽/高采用match_parent:
①若父容器的SpecMode为Exactly,那么子View的SpecMode也为Exactly,且其大小是父容器的剩余空间。
②若父容器的SpecMode为At_most,那么子View的SpecMode也为At_most,且其大小是父容器的剩余空间。 - 当子View的宽/高采用wrap_content,无论父容器是什么模式,子View的模式总是At_most,且大小不能超过父容器的剩余空间。
二、流程概述 :
view的绘制流程概括的描述为三部曲:measure -> layout -> draw
三、流程详述 :
1、ViewRootImpl (绘制入口):
Window的添加过程需要通过WindowManager的addView来实现。在addView时会创建一个ViewRootImpl对象,然后通过该对象可的setView完成Window的添加,在setView中调用requestLayout()完成异步刷新请求,在requestLayout中调用performTraversals来完场view的绘制。
总结:WindowManagerImpl -> addView -> new ViewRootImpl -> setView -> requestLayout -> performTraversals
绘制流程三部曲的核心入口就在 ViewRootImpl 类的 performTraversals() 方法中。
private void performTraversals() {
......
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
......
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
......
mView.draw(canvas);
......
}
2、measure(测量):
作用:measure 主要功能就是测量设置 View 的大小。
该过程由view的measure方法来完成,该方法是 final 类型,子类不能覆盖,在measure方法中会调用 onMeasure()方法,我们也可以复写 onMeasure()方法去测量设置 View 的大小。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
.....
onMeasure(widthMeasureSpec, heightMeasureSpec);
.....
}
onMeasure(这段代码就是measure过程的核心代码),接下来对onMeasure重点刨析:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)
);
}
① onMeasure#getSuggestedMinimumWidth()
protected int getSuggestedMinimumWidth() {
//返回View的建议最小宽度值,即xml布局中用设置的属性minWidth或背景大小。
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
同理可得 onMeasure#getSuggestedMinimumHeight()。
② onMeasure#getDefaultSize()
作用:根据View的建议最小值,结合父View传递的measureSpec,返回SpecSize,而这个值基本就是view测量后的大小。
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
//获取父View传递过来的模式
int specMode = MeasureSpec.getMode(measureSpec);
//获取父View传递过来的大小
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;//View的大小父View未定,设置为建议最小值
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
getDefaultSize 的逻辑跟我们之前分析的 MeasureSpec 转化规则非常相似。就是根据specMode设置大小。
③ onMeasure#setMeasuredDimension()
作用:保存测量好的宽跟高
注意:在onMeasure中必须调用这个方法,不然就会抛出 IllegalStateException 异常。
至此,View的measure的过程已经介绍完毕。
注: ViewGroup(即父View)的measure过程:
ViewGroup除了完成自己的measure的过程以外,还要遍历取调用子View的measure方法,各个子View再去递归执行这个过程,从而达到了测量目的。和View不同的是,ViewGroup是个抽象类,他的测量过程并没有像View一样,在onMeasure方法中做统一处理,而是由其子类(比如,LinearLayout、RelativeLayout等)各自实现或其内部方法measureChildren方法实现。在measureChildren方法中,遍历子View,调用measureChild方法,测量每个子View。在measureChild中,首先获取子View的LayoutParams,通过该值获取子View的WidthMeasureSpec和HeightMeasureSpec,最后调用每个子View的measure方法,将WidthMeasureSpec和HeightMeasureSpec作为参数传入,进行测量,这样又回到了上述View的绘制流程中。对于LinearLayout和RelativeLayout等ViewGroup的子类,其实现流程大致相同,也是遍历子View,然后调用各自内部相应的方法,进而调用子View的measure方法。
3、layout(布局):
作用:确定ViewGroup和子View的位置
对View进行布局排版,要从ViewGroup说起,ViewGroup的layout方法:
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
LayoutTransition是用于处理ViewGroup增加和删除子视图的动画效果,也就是说如果当前ViewGroup未添加LayoutTransition动画,或者LayoutTransition动画此刻并未运行,那么调用super.layout(l, t, r, b),即调用View的layout方法,否则将mLayoutCalledWhileSuppressed设置为true,等待动画完成时再调用requestLayout()。
public void layout(int l, int t, int r, int b) {
...
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
...
}
首先通过调用setFrame方法来设定四个顶点的位置,接下来调用onLayout方法,用于确定子View的位置。然而在View和ViewGroup中,均没有真正实现onLayout,因为onLayout的实现在ViewGroup的各子类中,如下是LinearLayout和RelativeLayout的onLayout代码:
//LinearLayout的onLayout方法
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else {
layoutHorizontal(l, t, r, b);
}
}
//layoutVertical
void layoutVertical(int left, int top, int right, int bottom) {
...
final int count = getVirtualChildCount();
...
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
if (hasDividerBeforeChildAt(i)) {
childTop += mDividerHeight;
}
childTop += lp.topMargin;
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
i += getChildrenSkipCount(child, i);
}
}
}
//setChildFrame
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
// RelativeLayout的onLayout方法
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// The layout has actually already been performed and the positions
// cached. Apply the cached values to the children.
final int count = getChildCount();
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() != GONE) {
RelativeLayout.LayoutParams st =
(RelativeLayout.LayoutParams) child.getLayoutParams();
child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
}
}
}
ViewGroup子类的方法中原理大致相似,都是遍历所有子View,最后调用子View的layout方法来指定子View的位置。
总结:View的layout过程较measure过程相对简单,首先ViewGroup的layout的方法会被调用,通过setFrame来确定父View的位置,然后调用onLayout来确定子View的布局,onLayout的具体实现在ViewGroup的子类中,遍历所有子View,最终调用子View的layout方法来确定自己的位置,这样一层层的传递下去,完成整个View树的layout过程。
4、draw(绘制):
Draw过程就相对更简单了,其作用是将View绘制到屏幕上面,其主要的绘制流程在如下源码中注释:
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
int saveCount;
if (!dirtyOpaque) {
// 绘制背景,如果需要。
drawBackground(canvas);
}
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// 绘制自己
if (!dirtyOpaque) onDraw(canvas);
// 绘制子View
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// 绘制装饰
onDrawForeground(canvas);
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
...
}
主要的绘制步骤总结如下:
1.绘制背景 (drawBackground(canvas))
2.绘制自己 (onDraw(canvas);自定义View的绘制在这里进行,该方法是一个空方法,具体实现在View的继承类中,自己复写实现内容绘制)
3.绘制子View (dispatchDraw(canvas))
4.绘制装饰 (onDrawForeground(canvas); 如ScrollBar等)