DecorView是一个应用窗口的根容器,它本质上是一个FrameLayout.
DecorView有唯一一个子View,是一个垂直的LinearLayout,包含两个子元素:TitleView(ActionBar的容器) 和 ContentView(窗口内容的容器).
ContentView是一个FrameLayout(android.R.id.content),我们平时用的setContentView就是设置它的子View.
上图还表达了每个Activity都与一个Window(具体来说是PhoneView)相关联,用户界面则由Window所承载.
Window即窗口,这个概念在Android Framework中的实现为android.view.Window
这个抽象类,这个抽象类是对Android系统中的窗口的抽象.
实际上 , 窗口是一个宏观的思想 ,它是屏幕上用于绘制各种UI元素和响应用户输入事件的一个矩形区域.
特点:
在分析setContentView()
方法前,我们需要明确:这个方法只是完成了Activity的ContentView的创建,而并没有执行View的绘制流程.
当我们自定义Activity继承自android.app.Activity时候,调用的setContentView()方法是Activity类的,源码如下:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
. . .
}
getWindow()
会返回Activity所关联的PhoneWindow , 所以实际上调用的是PhoneWindow的setContentView()方法.
View的绘制是由ViewRoot来负责的.每个应用窗口的decorView都有一个与之关联的ViewRoot对象,这种关联关系是由WindowManager来维护的 , 是在Activity启动时建立的.ActivityThread.handleResumeActivity()方法建立了二者的关联关系.
当建立了decorView和ViewRoot的关联关系后,ViewRoot的requestLayout()方法会被调用,以完成应用程序用户界面的初次布局.
实际被调用的是ViewRootImpl类的requestLayout()方法.
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// 检查发起布局请求的线程是否为主线程
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
scheduleTraversals()
会向主线程发送一个"遍历"消息,最终会导致ViewRootImpl的performTraversals()方法被调用.
View的绘制流程开始于ViewRoot的performTraversals()方法
performTraversals()依次调用performMeasure()、performLayout()和performDraw()三个方法,分别完成顶级View的绘制。其中performMeasure()会调用measure(),measure()中又调用onMeasure(),实现对其所有子元素的measure过程,这样就完成了一次measure过程;接着子元素会重复父容器的measure过程,如此反复至完成整个View树的遍历(layout和draw同理)。
View的整个绘制流程可以分为以下3个阶段:
此阶段的目的是计算出控件树中的各个控件要显示其内容的话,需要多少尺寸.
起点是ViewRootImpl的measureHierarchy()
方法:
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp, final Resources res,
final int desiredWindowWidth, final int desiredWindowHeight) {
// 传入的desiredWindowXxx为窗口尺寸
int childWidthMeasureSpec;
int childHeightMeasureSpec;
boolean windowSizeMayChange = false;
. . .
boolean goodMeasure = false;
if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
return windowSizeMayChange;
}
measure源码:
/**
* 调用这个方法来算出一个View应该为多大。参数为父View对其宽高的约束信息。
* 实际的测量工作在onMeasure()方法中进行
*/
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
. . .
// 判断是否需要重新布局
// 若mPrivateFlags中包含PFLAG_FORCE_LAYOUT标记,则强制重新布局
// 比如调用View.requestLayout()会在mPrivateFlags中加入此标记
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
|| heightMeasureSpec != mOldHeightMeasureSpec;
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final boolean needsLayout = specChanged
&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
// 需要重新布局
if (forceLayout || needsLayout) {
. . .
// 先尝试从缓从中获取,若forceLayout为true或是缓存中不存在或是
// 忽略缓存,则调用onMeasure()重新进行测量工作
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
. . .
} else {
// 缓存命中,直接从缓存中取值即可,不必再测量
long value = mMeasureCache.valueAt(cacheIndex);
// Casting a long to int drops the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
. . .
}
. . .
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
从measure()方法的源码中我们可以知道,只有以下两种情况之一,才会进行实际的测量工作:
对于decorView来说,实际执行测量工作的是FrameLayout的onMeasure()方法,该方法的源码如下:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int count = getChildCount();
. . .
int maxHeight = 0;
int maxWidth = 0;
int childState = 0;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (mMeasureAllChildren || child.getVisibility() != GONE) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
maxWidth = Math.max(maxWidth,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
maxHeight = Math.max(maxHeight,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
childState = combineMeasuredStates(childState, child.getMeasuredState());
. . .
}
}
// Account for padding too
maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
// Check against our minimum height and width
maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Check against our foreground's minimum height and width
final Drawable drawable = getForeground();
if (drawable != null) {
maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
}
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
resolveSizeAndState(maxHeight, heightMeasureSpec,
childState << MEASURED_HEIGHT_STATE_SHIFT));
. . .
}
在单一View的测量过程中实际起主要作用的方法有两个:
getDefaultSize():获取View的实际测量宽高;
setMeasuredDimension():存储View的实际测量宽高;
ViewGroup的测量过程除了完成自身的测量之外,还会遍历去调用子View的measure()方法。ViewGroup是一个抽象类,没有重写View的onMeasure()方法,所以需要子类去实现onMeasure()方法规定具体的测量规则。
ViewGroup子类复写onMeasure()方法有3个步骤:
ViewGroup中提供了**measureChildren()方法,该方法主要遍历所有的子View并调用其measureChild()**方法.
在Activity启动时,如何正确获取一个View的宽高:由于View的measure过程和Activity的生命周期是不同步的,所以无法保证Activity的onCreate()或者onResume()方法执行时某个View已经测量完毕,可以通过以下方法来解决:
(1)在onWindowFocusChanged()方法中获取View的宽高,该方法可能会被频繁调用;
(2)通过ViewTreeObserver的OnGlobalLayoutListener监听接口,当View树的状态发生改变或者View树内部的View的可见性发生改变时,就会回调onGlobalLayout()方法,在该方法中可以准确获取View的实际宽高.
View的Layout过程主要是确定View的四个顶点位置,从而确定其在容器中的位置,具体的layout过程和measure过程大致相似。
对于单一View的layout过程,首先调用View的layout()方法,在该方法中通过setFrame()方法来设定View的四个顶点的位置,即 初始化mLeft、mTop、mRight、mBottom这四个值,View的四个顶点一旦确定,那么View在父容器的位置也就确定了。接着会调用**onLayout()**方法确定所有子View在父容器中的位置,由于是单一View的layout过程,所以 onLayout()方法为空实现,因为没有子View(如果是ViewGroup需要子类实现 onLayout()方法)。
ViewGroup的layout过程首先会调用自身layout()方法,但和View的layout过程不一样的是,ViewGroup需要子类实现onLayout()方法,循环遍历所有的子View并调用其layout()方法确定子View的位置,从而最终确定ViewGroup在父容器的位置。
在onLayout()中主要遍历所有子View并调用setChildFrame(),在setChildFrame()中调用子View的layout()来确定每个子View的位置,从而最终确定自身的位置。
Draw过程主要是绘制View的过程,也分为单一View的绘制和ViewGroup的绘制。
View的draw过程都是从调用draw()方法开始的,该方法主要完成如下工作流程:
(1) drawBackground():绘制背景;
(2) 保存当前的canvas层(不是必须的);
(3) onDraw(): 绘制View的内容,这是一个空实现,需要子View根据要绘制的颜色、线条等样式去具体实现,所以要在子View里重写该方法;
(4) dispatchDraw(): 对所有子View进行绘制;单一View的dispatchDraw()方法是一个空方法,因为单一View没有子View,不需要实现dispatchDraw ()方法,而ViewGroup就不一样了,它实现了dispatchDraw()方法去遍历所有子View进行绘制;
(5) onDrawForeground():绘制装饰,比如滚动条.
1、View的draw过程
2、ViewGroup的draw过程
draw两个容易混淆的方法,两者都是刷新View的方法:
invalidate(): 不会经过measure和layout过程,只会调用draw过程;
requestLayout() :会调用measure和layout过程重新测量大小和确定位置,不会调用draw过程;
所以当我们进行View更新时,若仅View的显示内容发生改变且新显示内容不影响View的大小、位置,则只需调用invalidate方法;若View宽高、位置发生改变且显示内容不变,只需调用requestLayout方法;若两者均发生改变,则需调用两者,按照View的绘制流程,推荐先调用requestLayout方法再调用invalidate方法。