View 的布局流程
在布局时 layuot() 方法被父View调用,在layout中View保存父View传进来的尺寸和位置,并调用onLayout() 来进行实际的内部布局 ,view和viewgroup的onlayout()有所区别
- View:由于没有子View,什么也不做
- ViewGroup:ViewGroup在onLayout()中会调用自己的所有子View的layout()方法,把它们的尺寸和位置传给它们,让它们完成自我的内部布局。
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//1 调用setFrame()设置View四个顶点ed位置,内部将调用invalidate(isSizeChanged)
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//2 调用onLayout()确定View子元素的位置
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);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
}
关于View.invalidata()
该方法检查mPrivateFlags的DRAWN位与HAS_BOUNDS是否被置1,说明上一次请求执行的UI绘制已经完成了,这个时候才能执行新的UI绘制操作,在执行新的UI绘制操作之前,还会将 这两个标志位置0,然后调用ViewParent.invalidateChild()方法来完成绘制操作,这个ViewParent指向的是ViewRoot对象
public void incalidata(){
invalidate(true);
}
void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,boolean fullInvalidate) {
if (mGhostView != null) {
mGhostView.invalidate(true);
return;
}
if (skipInvalidate()) {// view.visibility != VISIBLE
return;
}
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
|| (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
|| (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
|| (fullInvalidate && isOpaque() != mLastIsOpaque)) {
if (fullInvalidate) {
mLastIsOpaque = isOpaque();
mPrivateFlags &= ~PFLAG_DRAWN;
}
mPrivateFlags |= PFLAG_DIRTY;
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
// Propagate the damage rectangle to the parent view.
final AttachInfo ai = mAttachInfo; //内部有window,handler,rootView,window状态等
final ViewParent p = mParent;
if (p != null && ai != null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
p.invalidateChild(this, damage);
}
// Damage the entire projection receiver, if necessary.
if (mBackground != null && mBackground.isProjected()) {
final View receiver = getProjectionReceiver();
if (receiver != null) {
receiver.damageInParent();
}
}
// Damage the entire IsolatedZVolume receiving this view's shadow.
if (isHardwareAccelerated() && getZ() != 0) {
damageShadowReceiver();
}
}
}
FrameLayout.onLayout(boolean changed, int left, int top, int right, int bottom)
onLayout 由ViewGroup 子类实现,如Framelayout
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int count = getChildCount();
final int parentLeft = mPaddingLeft + mForegroundPaddingLeft;
final int parentRight = right - left - mPaddingRight - mForegroundPaddingRight;
final int parentTop = mPaddingTop + mForegroundPaddingTop;
final int parentBottom = bottom - top - mPaddingBottom - mForegroundPaddingBottom;
mForegroundBoundsChanged = true;
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft = parentLeft;
int childTop = parentTop;
final int gravity = lp.gravity;
if (gravity != -1) {
final int horizontalGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (horizontalGravity) {
case Gravity.LEFT:
childLeft = parentLeft + lp.leftMargin;
break;
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = parentRight - width - lp.rightMargin;
break;
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
}
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}
该方法就会遍历它的每一个子View,并获取它的左上角的坐标位置:childLeft,childTop。这两个位置信息会根据gravity来进行计算。 最后会调用子View的layout()方法循环布局操作,直到所有的布局都完成为止。
ViewRootImpl.performTraversals
View 的三大流程起点在ViewRootImpl 类中,该类在View添加到Window时创建,每个根布局(DecorView)对应一个ViewRootImpl,其它子view共享
performTraversals(){
// cache mView since it is used so much below...
final View host = mView; //mView就是DecorView根布局,记录ViewRootImpl管理的View树的根节点,final修饰,避免运行过程中修改
if (host == null || !mAdded) //判断View绘制的条件是否具备.mAdded是DecorView是否成功添加到
return; //Window的标示位,如果一切顺利的话,这里应该为ture,因此不会return.
mIsInTraversal = true; //是否正在遍历
mWillDrawSoon = true; //是否马上绘制View
boolean windowSizeMayChange = false; //视图的大小可能改变
boolean newSurface = false; //新界面
boolean surfaceChanged = false; //界面改变
WindowManager.LayoutParams lp = mWindowAttributes;
int desiredWindowWidth; //顶层视图DecorView所需要窗口的宽度和高度
int desiredWindowHeight;
final int viewVisibility = getHostVisibility(); //DecorView视图是否可见
boolean viewVisibilityChanged = mViewVisibility != viewVisibility
|| mNewSurfaceNeeded; //视图可见性改变
......
Rect frame = mWinFrame; //将一个全局的Rect对象赋给了局部frame对象,它用来保存Activity窗口当前的宽度和高度
if (mFirst) { //在构造方法中mFirst已经设置为true,表示是否是第一次被请求执行测量、布局和绘制操作,
mFullRedrawNeeded = true; //是否需要全部重绘
mLayoutRequested = true; //是否要求重新Layout界面
//如果窗口的类型是有状态栏的,那么Activity窗口的宽度和高度就是除了状态栏
if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
|| lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {//否则Activity窗口的宽度和高度就是整个屏幕的宽高
DisplayMetrics packageMetrics =
mView.getContext().getResources().getDisplayMetrics();
desiredWindowWidth = packageMetrics.widthPixels;
desiredWindowHeight = packageMetrics.heightPixels;
}
......
} else { //除了第一次被请求执行测量、布局和绘制操作以外,它的当前宽度desiredWindowWidth和高度
//就等于保存在ViewRootImpl类的成员变量mWinFrame/frame中的宽度和高度值,也就是上一次储存的宽高值.
desiredWindowWidth = frame.width(); //以改变后的参数来绘制DecorView的宽高
desiredWindowHeight = frame.height();
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
//mWidth此时代表的是上一次执行该方法的时候的frame.width()值,如果此时两值不相等,那就说明视图改变需要重新测量绘制了。
mFullRedrawNeeded = true; //需要重新绘制标志位
mLayoutRequested = true; //要求重新Layout标志位
windowSizeMayChange = true; //Window的尺寸可能改变
}
}
}
这段代码中主要交代了Activity当前的Window的宽高是如何得出的:
if(Activity窗口是第一次被请求执行测量、布局和绘制操作){
if(如果窗口的类型是有状态栏的){
Activity窗口所需要的宽度和高度就是除了状态栏;
}else{
Activity窗口所需要的宽度和高度就是整个屏幕的宽高;
}
}else{
Activity窗口的宽高为frame成员变量中的保存的上一次测量、布局和绘制时的值;
}
本段代码结尾的地方,关于这个desiredWindowWidth和mWidth的比较,我们需要说一下:首先这个mWidth和mHeight也是用来描述Activity窗口当前宽度和高度的,与desiredWindowWidth和desiredWindowHeight不同,它们的值是由应用程序进程上一次主动请求WindowManagerService服务计算得到的,并且会一直保持不变到应用程序进程下一次再请求WindowManagerService服务来重新计算为止(这个在下文的代码中也有所体现)。
如果Activity窗口不是第一次被请求执行测量、布局和绘制操作,并且Activity窗口主动上一次请求WindowManagerService服务计算得到的宽度mWidth和高度mHeight不等于Activity窗口的当前宽度desiredWindowWidth和当前高度desiredWindowHeight,那么就说明Activity窗口的大小发生了变化,这时候变量windowSizeMayChange的值就会被标记为true,以便接下来可以对Activity窗口的大小变化进行处理。
第二段代码:
......
boolean insetsChanged = false;
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
final Resources res = mView.getContext().getResources();
if (mFirst) {
// make sure touch mode code executes by setting cached value
// to opposite of the added touch mode.
mAttachInfo.mInTouchMode = !mAddedTouchMode;
ensureTouchModeLocally(mAddedTouchMode);
} else {
if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
insetsChanged = true;
}
if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
insetsChanged = true;
}
if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) {
insetsChanged = true;
}
if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: "
+ mAttachInfo.mVisibleInsets);
}
if (!mPendingOutsets.equals(mAttachInfo.mOutsets)) {
insetsChanged = true;
}
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
windowSizeMayChange = true;
if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
|| lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
DisplayMetrics packageMetrics = res.getDisplayMetrics();
desiredWindowWidth = packageMetrics.widthPixels;
desiredWindowHeight = packageMetrics.heightPixels;
}
}
}
// Ask host how big it wants to be
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
首先我们来看看这段代码中出现的几个变量——mAttachInfo实际上是View类中的静态内部类AttachInfo类的对象
final static class AttachInfo {
/**
* For windows that are full-screen but using insets to layout inside
* of the screen areas, these are the current insets to appear inside
* the overscan area of the display.
*/
final Rect mOverscanInsets = new Rect(); //下面三个变量的注释基本类似,就不贴了
......(省略注释)
final Rect mContentInsets = new Rect();
final Rect mVisibleInsets = new Rect();
final Rect mStableInsets = new Rect();
......
这个是View类的一个内部类AttachInfo类的对象,从注释可以看出该类的主要作用就是储存一组当View attach给它的父Window的时候Activity各种属性的信息
这里一共提到了四个变量,事时上,我们看View类的源码就会发现,mContentInsets和mStableInsets都是灰色的代码,也就是没有用到的对象,至于为什么要保留呢?首先我们要知道,这个mOverscanInsets和mContentInsets是WMS用来fitSystemWindows的,我们看老罗的Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析这篇文章会发现,低版本的Android系统是用mContentInsets来计算的,而在6.0的版本中却换成了mOverscanInsets:
protected boolean computeFitSystemWindows(Rect inoutInsets, Rect outLocalInsets) {
if ((mViewFlags & OPTIONAL_FITS_SYSTEM_WINDOWS) == 0
|| mAttachInfo == null
|| ((mAttachInfo.mSystemUiVisibility & SYSTEM_UI_LAYOUT_FLAGS) == 0
&& !mAttachInfo.mOverscanRequested)) {
outLocalInsets.set(inoutInsets);
inoutInsets.set(0, 0, 0, 0);
return true;
} else {
// The application wants to take care of fitting system window for
// the content... however we still need to take care of any overscan here.
final Rect overscan = mAttachInfo.mOverscanInsets;
outLocalInsets.set(overscan);
inoutInsets.left -= overscan.left;
inoutInsets.top -= overscan.top;
inoutInsets.right -= overscan.right;
inoutInsets.bottom -= overscan.bottom;
return false;
}
}
我们再看mPendingOverscanInsets,mPendingContentInsets,mPendingStableInsets,mPendingVisibleInsets四个变量,它们和上面四个变量标示的意义是一样的,只不过它们是由WindowManagerService服务主动请求Activity窗口设置的,但是尚未生效。说的更通俗一点,这个mOverscanInsets是上一次执行performTraversals()函数时保存在mAttachInfo中的值,而mPendingOverscanInsets是这一次请求该函数时还没生效的值,两个值一比较,如果不相等,说明过insets的值发生了改变(insetsChanged = true)。
回到第二段代码中,我们注意到那段代码中的if代码块,如果Activity窗口是第一次被测量,布局和绘制,那么:mAttachInfo.mInTouchMode = !mAddedTouchMode;这里的mInTouchMode是View内部类AttachInfo类的成员变量,作用是"指示View所处的Window当前是否处于触摸模式",而紧接着ensureTouchModeLocally(mAddedTouchMode);则是"确保这个Window的触摸模式已经被设置",并且在这个方法的注释中有对传入参数mAddedTouchMode的解释:"我们是否想处于触摸模式",这个方法的返回值是——进入触摸模式,ture;离开触摸模式,flase。但由于再上一段代码中这个方法的返回值并没有进行局部变量赋值等进一步处理,所以可以暂时先不管这个方法。总之,如果Activity窗口是第一次被测量,布局和绘制这种情况下,只是对于是否处于触摸模式情况的一个判断,除了改变了AttachInfo实体类中相关信息以外,并没有做进一步处理。
接着是else中的情况,也就是Activity窗口不是第一次被测量,布局和绘制,即mFrist成员变量中的值为false。此时先判断一下几个insects的值和上一次相比有没有什么变化,不想等的话就将insetsChanged标志位变为ture。接着判断Activity的窗口情况,如果Activity窗口的宽度被设置为ViewGroup.LayoutParams.WRAP_CONTENT或者高度被设置为ViewGroup.LayoutParams.WRAP_CONTENT,那么就意味着Activity窗口的大小要等于内容区域的大小,但是由于Activity窗口的大小是需要覆盖整个屏幕的,因此,这时候就系统会将Activity窗口的当前宽度desiredWindowWidth和当前高度desiredWindowHeight设置为屏幕的宽度和高度。也就是说,如果我们将Activity窗口的宽度和高度设置为ViewGroup.LayoutParams.WRAP_CONTENT,实际上这个设置是无效的,它最终的宽度和高度还是会等于屏幕的宽度和高度。这种情况也意味着Acitivity窗口的大小发生了变化(从设置的WRAP_CONTENT变为屏幕大小),因此将windowSizeMayChange设为true。紧接着是一个窗口类型的判断,如果窗口的类型是有状态栏的,那么Activity窗口的宽度和高度就是屏幕的宽高除了状态栏,否则就是整个屏幕的大小。
最后一句代码:windowSizeMayChange |= measureHierarchy(host, lp, res,desiredWindowWidth, desiredWindowHeight);,measureHierarchy()这个方法中实际上进行了measure()测量过程,只不过这个测量过程不属于三大流程,而是为了确定Window的大小而打的辅助,只不过最终返回的仍然是widow的size是否改变的boolean值。
if (layoutRequested) {
// Clear this now, so that if anything requests a layout in the
// rest of this function we will catch it and re-run a full
// layout pass. --注释写的很清楚了吧?
mLayoutRequested = false;
}
boolean windowShouldResize = layoutRequested && windowSizeMayChange
&& ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
|| (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.width() < desiredWindowWidth && frame.width() != mWidth)
|| (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.height() < desiredWindowHeight && frame.height() != mHeight));
// Determine whether to compute insets.
// If there are no inset listeners remaining then we may still need to compute
// insets in case the old insets were non-empty and must be reset.
final boolean computesInternalInsets =
mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners()
|| mAttachInfo.mHasNonEmptyGivenInternalInsets;
第一个if语段注释已经写的很清楚了,我们主要看后面两段,这两段代码做了两件事情:
第一件事情是检查是否需要处理Activity窗口的大小变化事件,如果同时满足下面三个条件,就需要处理,将windowShouldResize标志位变为true:
①layoutRequested等于true,说明程序已经发起(正在执行)了一次测量,布局,绘制流程。
②windowSizeMayChange等于true,说明前面已经检测到了Activity窗口的变化。
③上一段代码的最后一句中,我们说过已经做了一次measure()工作,如果测量出来的宽度host.mMeasuredWidth和高度host.mMeasuredHeight和Activity窗口的当前宽度mWidth和高度mHeight一样,那么即使条件1和条件2能满足,那么也是可以认为是Activity窗口的大小是没有发生变化的。换句话说,只有当测量出来的大小和当前大小不一致时,才认为Activity窗口大小发生了变化。另一方面,如果测量出来的大小和当前大小一致,但是Activity窗口的大小被要求设置成WRAP_CONTENT,即设置成和屏幕的宽度desiredWindowWidth和高度desiredWindowHeight一致,但是WindowManagerService服务请求Activity窗口设置的宽度frame.width()和高度frame.height()与它们不一致,而且与Activity窗口上一次请求WindowManagerService服务计算的宽度mWidth和高度mHeight也不一致,那么也是认为Activity窗口大小发生了变化的。 第二件事情是检查Activity窗口是否需要指定有额外的内容边衬区域和可见边衬区域。如果有的话,那么变量attachInfo所指向的一个AttachInfo对象的成员变量mTreeObserver所描述的一个TreeObserver对象的成员函数hasComputeInternalInsetsListerner的返回值ComputeInternalInsets就会等于true。Activity窗口指定额外的内容边衬区域和可见边衬区域是为了放置一些额外的东西。
第四段代码:
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null) {
if (viewVisibility == View.VISIBLE) {
// If this window is giving internal insets to the window
// manager, and it is being added or changing its visibility,
// then we want to first give the window manager "fake"
// insets to cause it to effectively ignore the content of
// the window during layout. This avoids it briefly causing
// other windows to resize/move based on the raw frame of the
// window, waiting until we can finish laying out this window
// and get back to the window manager with the ultimately
// computed insets.
insetsPending = computesInternalInsets && (mFirst || viewVisibilityChanged);
}
......
这段代码以及接下来的两段代码都是在满足下面的条件之一的情况下执行的(都在一个if()里边):
①Activity窗口是第一次执行测量、布局和绘制操作,即ViewRoot类的成员变量mFirst的值等于true。
② 前面得到的变量windowShouldResize的值等于true,即Activity窗口的大小的确是发生了变化。
③ 前面得到的变量insetsChanged的值等于true,即Activity窗口的内容区域边衬发生了变化。
④ Activity窗口的可见性发生了变化,即变量viewVisibilityChanged的值等于true。
⑤ Activity窗口的属性发生了变化,即变量params指向了一个WindowManager.LayoutParams对象。
在满足上述条件之一,并且Activity窗口处于可见状态,即变量viewVisibility的值等于View.VISIBLE,那么就需要检查接下来请求WindowManagerService服务计算大小时,是否要告诉WindowManagerService服务它指定了额外的内容区域边衬和可见区域边衬,但是这些额外的内容区域边衬和可见区域边衬又还有确定。这种情况发生在Activity窗口第一次执行测量、布局和绘制操作或者由不可见变化可见时。因此,当前面得到的变量computesInternalInsets等于true时,即Activity窗口指定了额外的内容区域边衬和可见区域边衬,那么就需要检查mFirst或者变量viewVisibilityChanged的值是否等于true。如果这些条件能满足,那么变量insetsPending的值就会等于true,表示Activity窗口有额外的过扫描区域边衬和可见区域边衬等待指定。
我们接着看代码:
第五段代码:
try {
......
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
......
final boolean overscanInsetsChanged = !mPendingOverscanInsets.equals(
mAttachInfo.mOverscanInsets);
contentInsetsChanged = !mPendingContentInsets.equals(
mAttachInfo.mContentInsets);
final boolean visibleInsetsChanged = !mPendingVisibleInsets.equals(
mAttachInfo.mVisibleInsets);
final boolean stableInsetsChanged = !mPendingStableInsets.equals(
mAttachInfo.mStableInsets);
final boolean outsetsChanged = !mPendingOutsets.equals(mAttachInfo.mOutsets);
if (contentInsetsChanged) {
......
mAttachInfo.mContentInsets.set(mPendingContentInsets);
}
} catch (RemoteException e) {
}
......
mAttachInfo.mWindowLeft = frame.left;
mAttachInfo.mWindowTop = frame.top;
// !!FIXME!! This next section handles the case where we did not get the
// window size we asked for. We should avoid this by getting a maximum size from
// the window session beforehand.
if (mWidth != frame.width() || mHeight != frame.height()) {
mWidth = frame.width();// mWidth和mHeight也是用来描述Activity窗口当前宽度和高度的
mHeight = frame.height(); // 与desiredWindowWidth和desiredWindowHeight不同,它们的值是
//由应用程序进程上一次主动请求WindowManagerService服务计算得到的,并且会一直保持不变到应用程序进
//程下一次再请求WindowManagerService服务来重新计算为止
}
这段代码主要就是调用relayoutWindow()来请求WindowManagerService服务计算Activity窗口的大小以及过扫描区域边衬大小和可见区域边衬大小。计算完毕之后,Activity窗口的大小就会保存在成员变量mWinFrame中,而Activity窗口的内容区域边衬大小和可见区域边衬大小分别保存在ViewRoot类的成员变量mPendingOverscanInsets和mPendingVisibleInsets中。
如果这次计算得到的Activity窗口的内容区域边衬大小mPendingContentInsets和可见区域边衬大小mPendingVisibleInsets与上一次计算得到的不一致,即mAttachInfo所指向的一个AttachInfo对象的成员变量mOverscanInsets和mVisibleInsets所描述的大小不一致,那么变量overscanInsetsChanged和visibleInsetsChanged的值就会等于true,表示Activity窗口的内容过扫描边衬大小和可见区域边衬大小发生了变化。(虽然这段代码中一共提到了四个变量,但是上文我们说过,只有mOverscanInsets和mVisibleInsetsshiy是用到的)。
由于变量frame和mWinFrame引用的是同一个Rect对象,因此,这时候变量frame描述的也是Activity窗口请求WindowManagerService服务计算之后得到的大小。这段代码分别将计算得到的Activity窗口的左上角坐标保存在变量attachInfo所指向的一个AttachInfo对象的成员变量mWindowLeft和mWindowTop中,并且将计算得到的Activity窗口的宽度和高度保存在mWidth和mHeight中。
第六段代码:
......
//mStopped的注释:Set to true if the owner of this window is in the stopped state.如果此窗口的所有者
//(Activity)处于停止状态,则为ture.
if (!mStopped || mReportNextDraw) { //mReportNextDraw Window上报下一次绘制.
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
//mWidth != host.getMeasuredWidth() 表示frame的宽不等于初始DecorView宽.
//getMeasuredWidth()方法可以获取View测量后的宽高,host上面说过为DecorView根布局.
//获得view宽高的测量规格,lp.width和lp.height表示DecorView根布局宽和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//开始执行测量操作 ①
int width = host.getMeasuredWidth();//getMeasuredWidth()所获得View的宽高绝大部分情况下等于View最终的宽高
int height = host.getMeasuredHeight();
boolean measureAgain = false;
//lp.horizontalWeight表示将多少额外空间水平地(在水平方向上)分配给与这些LayoutParam关联的视图。如果
//视图不应被拉伸,请指定0。否则,将在所有权重大于0的视图中分配额外的像素。
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
measureAgain = true; //重新测量标志位
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); //重新测量
}
layoutRequested = true;
}
}
这段代码用来检查是否需要重新测量Activity窗口的大小。如果满足以下条件之一,那么就需要重新测量:
① Activity窗口的触摸模式发生了变化,并且由此引发了Activity窗口当前获得焦点的控件发生了变化,即变量focusChangedDueToTouchMode的值等于true。这个检查是通过调用ensureTouchModeLocally来实现的。
② Activity窗口前面测量出来的宽度host.mMeasuredWidth和高度host.mMeasuredHeight不等于WindowManagerService服务计算出来的宽度mWidth和高度mHeight。
③Activity窗口的内容区域边衬大小和可见区域边衬大小发生了变化,即前面得到的变量contentInsetsChanged的值等于true。
重新计算了一次之后,如果Activity窗口的属性lp表明需要对测量出来的宽度width和高度height进行扩展,即变量lp所指向的一个WindowManager.LayoutParams对象的成员变量horizontalWeight和verticalWeight的值大于0.0,那么就需要对Activity窗口的顶层视图host的最大可用空间进行扩展后再进行一次测量工作。
第七段代码:
} else {
final boolean windowMoved = (mAttachInfo.mWindowLeft != frame.left
|| mAttachInfo.mWindowTop != frame.top);
if (windowMoved) { //如果窗口移动
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWinFrame(frame);
}
mAttachInfo.mWindowLeft = frame.left;
mAttachInfo.mWindowTop = frame.top;
// Update the light position for the new window offsets.
if (mAttachInfo.mHardwareRenderer != null) {
mAttachInfo.mHardwareRenderer.setLightCenter(mAttachInfo);
}
}
}
首先,这里的else接的是“第四段代码”中的那个if,是的,这个if就是这么长。其次,这段代码实际上就是判断Window有没有移动,如果发生移动则会调用translateRectInScreenToAppWinFrame()执行移动动画。 这个mWindowLeft和mWindowTop代表的分别是View所attach的Window的左坐标和顶坐标,回到上一个方法中,我们可以看到,其实在这个方法中,有这么两句:
mAttachInfo.mWindowLeft = frame.left;
mAttachInfo.mWindowTop = frame.top;
也就是说,final boolean windowMoved = (mAttachInfo.mWindowLeft != frame.left|| mAttachInfo.mWindowTop != frame.top);这句实际上就是将上一次调用该方法时Window的顶坐标和左坐标与这一次相比,从而得出Window是否Moved。
第八段代码:
......
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
if (didLayout) { //layoutRequested测量要求标志位
performLayout(lp, desiredWindowWidth, desiredWindowHeight); //开始执行Layout操作 ②
// By this point all views have been sized and positioned
// We can compute the transparent area
......(省略计算透明区域的一大块方法)
}
boolean skipDraw = false;
if (mFirst) {
......
} else if (mWindowsAnimating) { //表示窗口动画正在进行中。
//mRemainingFrameCount:How many frames the app is still allowed to draw when a window animation is happening
if (mRemainingFrameCount <= 0) {
skipDraw = true; //跳过绘制,剩下允许的帧数为0了。
}
mRemainingFrameCount--;
}
mFirst = false;
......
if (!cancelDraw && !newSurface) { //既没有取消绘制,也没有创建新的平面。
if (!skipDraw || mReportNextDraw) { //没有跳过绘制或者已经上报进行下一次绘制
......
performDraw(); //开始执行绘制操作 ③
}
} else {
if (viewVisibility == View.VISIBLE) {
// Try again
scheduleTraversals(); //还记得这个方法是干什么的吧?就是重新来过的意思!不记得的话去看上一篇文章。
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
mIsInTraversal = false;
}
在这段代码中,Activity的Window测量过程已经进行完毕,当满足layoutRequested && (!mStopped || mReportNextDraw);条件时,将进行布局(layout)过程。可以看到“第六段代码”和本段代码中,出现了View绘制的三大流程。
ViewRootImpl.draw(boolean fullRedrawNeeded) //参数表示是否需要全部绘制
private void draw(boolean fullRedrawNeeded) {
//surface用来操作应用窗口的绘图表面
Surface surface = mSurface;
if (!surface.isValid()) {
return;
}
if (!sFirstDrawComplete) { //是否为第一次draw
synchronized (sFirstDrawHandlers) {
sFirstDrawComplete = true;
final int count = sFirstDrawHandlers.size();
for (int i = 0; i< count; i++) {//sFirstDrawHandlers中会启动JIT编译器,如果有的话,是java运行加速的手段
mHandler.post(sFirstDrawHandlers.get(i));
}
}
}
// mAttachInfo.mTreeObserver 内部维持view树的监听列表,通过disptach方法回调列表中的监听方法
scrollToRectOrFocus(null, false);
if (mAttachInfo.mViewScrollChanged) {
mAttachInfo.mViewScrollChanged = false;
mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
}
// 滚动到显示位置
boolean animating = mScroller != null && mScroller.computeScrollOffset();
final int curScrollY;
if (animating) {
curScrollY = mScroller.getCurrY();
} else {
curScrollY = mScrollY;
}
if (mCurScrollY != curScrollY) {
mCurScrollY = curScrollY;
fullRedrawNeeded = true;
if (mView instanceof RootViewSurfaceTaker) {
((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
}
}
final float appScale = mAttachInfo.mApplicationScale;
final boolean scalingRequired = mAttachInfo.mScalingRequired;
int resizeAlpha = 0;
final Rect dirty = mDirty;
if (mSurfaceHolder != null) {
// The app owns the surface, we won't draw.
dirty.setEmpty();
if (animating && mScroller != null) {
mScroller.abortAnimation();
}
return;
}
if (fullRedrawNeeded) {
mAttachInfo.mIgnoreDirtyState = true;
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v(mTag, "Draw " + mView + "/"
+ mWindowAttributes.getTitle()
+ ": dirty={" + dirty.left + "," + dirty.top
+ "," + dirty.right + "," + dirty.bottom + "} surface="
+ surface + " surface.isValid()=" + surface.isValid() + ", appScale:" +
appScale + ", width=" + mWidth + ", height=" + mHeight);
}
mAttachInfo.mTreeObserver.dispatchOnDraw();
int xOffset = -mCanvasOffsetX;
int yOffset = -mCanvasOffsetY + curScrollY;
final WindowManager.LayoutParams params = mWindowAttributes;
final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
if (surfaceInsets != null) {
xOffset -= surfaceInsets.left;
yOffset -= surfaceInsets.top;
// Offset dirty rect for surface insets.
dirty.offset(surfaceInsets.left, surfaceInsets.right);
}
boolean accessibilityFocusDirty = false;
final Drawable drawable = mAttachInfo.mAccessibilityFocusDrawable;
if (drawable != null) {
final Rect bounds = mAttachInfo.mTmpInvalRect;
final boolean hasFocus = getAccessibilityFocusedRect(bounds);
if (!hasFocus) {
bounds.setEmpty();
}
if (!bounds.equals(drawable.getBounds())) {
accessibilityFocusDirty = true;
}
}
mAttachInfo.mDrawingTime =
mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
//描述窗口的脏区域,即需要重新绘制的区域
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) {
// If accessibility focus moved, always invalidate the root.
boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;
mInvalidateRootRequested = false;
// Draw with hardware renderer.
mIsAnimating = false;
if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {
mHardwareYOffset = yOffset;
mHardwareXOffset = xOffset;
invalidateRoot = true;
}
if (invalidateRoot) {
mAttachInfo.mHardwareRenderer.invalidateRoot();
}
dirty.setEmpty();
// Stage the content drawn size now. It will be transferred to the renderer
// shortly before the draw commands get send to the renderer.
final boolean updated = updateContentDrawBounds();
if (mReportNextDraw) {
// report next draw overrides setStopped()
// This value is re-sync'd to the value of mStopped
// in the handling of mReportNextDraw post-draw.
mAttachInfo.mHardwareRenderer.setStopped(false);
}
if (updated) {
requestDrawWindow();
}
mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this);
} else {
// If we get here with a disabled & requested hardware renderer, something went
// wrong (an invalidate posted right before we destroyed the hardware surface
// for instance) so we should just bail out. Locking the surface with software
// rendering at this point would lock it forever and prevent hardware renderer
// from doing its job when it comes back.
// Before we request a new frame we must however attempt to reinitiliaze the
// hardware renderer if it's in requested state. This would happen after an
// eglTerminate() for instance.
if (mAttachInfo.mHardwareRenderer != null &&
!mAttachInfo.mHardwareRenderer.isEnabled() &&
mAttachInfo.mHardwareRenderer.isRequested()) {
try {
mAttachInfo.mHardwareRenderer.initializeIfNeeded(
mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return;
}
mFullRedrawNeeded = true;
scheduleTraversals();
return;
}
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
return;
}
}
}
if (animating) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
}