//ViewRootImpl
privatevoidperformLayout(WindowManager.LayoutParams lp, intdesiredWindowWidth,intdesiredWindowHeight){ finalView host = mView; host.layout( 0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); } //ViewGroup
//尽管ViewGroup也重写了layout方法
//但是本质上还是会通过super.layout()调用View的layout()方法
@Override
publicfinalvoidlayout(intl, intt, intr, intb){ if(!mSuppressLayout && (mTransition == null|| !mTransition.isChangingLayout())) { //如果无动画,或者动画未运行super.layout(l, t, r, b); } else{ //等待动画完成时再调用requestLayout()mLayoutCalledWhileSuppressed = true; } } //View
publicvoidlayout(intl, intt, intr, intb){ intoldL = mLeft; intoldT = mTop; intoldB = mBottom; intoldR = mRight; //如果布局有变化,通过setFrame重新布局booleanchanged = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if(changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { //如果这是一个ViewGroup,还会遍历子View的layout()方法//如果是普通View,通知具体实现类布局变更通知onLayout(changed, l, t, r, b); //清除PFLAG_LAYOUT_REQUIRED标记mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; ...... //布局监听通知} //清除PFLAG_FORCE_LAYOUT标记mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; }
通过代码可以看到尽管 ViewGroup 也重写了 layout() 方法,但是本质上还是会走 View 的 layout()。
在 View 的 layout() 方法里,首先通过 setFrame()(setOpticalFrame() 也走 setFrame())将 l、t、r、b 分别设置到 mLeft、mTop、mRight 和 mBottom,这样就可以确定 子View 在父容器的位置了,上面也说过了,这些位置是相对父容器的。
然后调用 onLayout() 方法,使具体实现类接收到布局变更通知。如果此类是 ViewGroup,还会遍历 子View 的 layout() 方法使其更新布局。如果调用的是 onLayout() 方法,这会导致 子View 无法调用 setFrame(),从而无法更新控件坐标信息。
//View
protectedvoidonLayout(booleanchanged, intl, intt, intr, intb){} //ViewGroup
//abstract修饰,具体实现类必须重写该方法
@Override
protectedabstractvoidonLayout(booleanchanged,intl, intt, intr, intb);
对于普通 View 来说,onLayout() 方法是一个空实现,主要是具体实现类重写该方法后能够接收到布局坐标更新信息。
对于 ViewGroup 来说,和 measure 一样,不同实现类有它不同的布局特性,在 ViewGroup 中 onLayout() 方法是 abstract 的,具体实现类必须重写该方法,以便接收布局坐标更新信息后,处理自己的 子View 的坐标信息。有兴趣的童鞋可以看 FrameLayout 或者 LinearLayout 的 onLayout() 方法。
小结
对比测量 measure 和布局 layout 两个过程有助于加深对它们的理解。(摘自《深入理解Android卷III》)
measure 确定的是控件的尺寸,并在一定程度上确定了子控件的位置。而布局则是针对测量结果来实施,并最终确定子控件的位置。
measure 结果对布局过程没有约束力。虽说子控件在 onMeasure() 方法中计算出了自己应有的尺寸,但是由于 layout() 方法是由父控件调用,因此控件的位置尺寸的最终决定权掌握在父控件手中,测量结果仅仅只是一个参考。
因为 measure 过程是后根遍历(DecorView 最后 setMeasureDiemension()),所以子控件的测量结果影响父控件的测量结果。
而 Layout 过程是先根遍历(layout() 一开始就调用 setFrame() 完成 DecorView 的布局),所以父控件的布局结果会影响子控件的布局结果。
完成 performLayout() 后,空间树的所有控件都已经确定了其最终位置,就剩下绘制了。
draw
我们先纯粹的看 View 的 draw 过程,因为这个过程相对上面 measure 和 layout 比较简单。
View 的 draw 过程遵循如下几步 :
绘制背景 drawBackground();
绘制自己 onDraw();
如果是 ViewGroup 则绘制 子View,dispatchDraw();
绘制装饰(滚动条)和前景,onDrawForeground();
ddd
//View
publicvoiddraw(Canvas canvas){ finalintprivateFlags = mPrivateFlags; //检查是否是"实心(不透明)"控件。(后面有补充)finalbooleandirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && (mAttachInfo == null|| !mAttachInfo.mIgnoreDirtyState); mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */// Step 1, draw the background, if neededintsaveCount; //非"实心"控件,将会绘制背景if(!dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5 if possible (common case)finalintviewFlags = mViewFlags; booleanhorizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; booleanverticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; //如果控件不需要绘制渐变边界,则可以进入简便绘制流程if(!verticalEdges && !horizontalEdges) { // Step 3, draw the contentif(!dirtyOpaque) onDraw(canvas); //非"实心",则绘制控件本身// Step 4, draw the childrendispatchDraw(canvas); //如果当前不是ViewGroup,此方法则是空实现// Overlay is part of the content and draws beneath Foregroundif(mOverlay != null&& !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas); //绘制装饰和前景// we're done...return; } ...... }

至此 View 的工作流程的大致整体已经描述完毕了,是否感觉意犹未尽,我们再补充2个知识点作为餐后甜点。
invalidate
我们知道 invalidate() (在主线程)和 postInvalidate() (可以在子线程)都是用于请求 View 重绘的方法,那么它是如何实现的呢?
invalidate() 方法必须在主线程执行,而 scheduleTraversals() 引发的遍历也是在主线程执行。所以调用 invalidate() 方法并不会使得遍历立即开始,因为在调用 invalidate() 的方法执行完毕之前(准确的说是主线程的Looper处理完其他消息之前),主线程根本没有机会处理 scheduleTraversals() 所发出的消息。
这种机制带来的好处是 : 在一个方法里可以连续调用多个控件的 invalidate() 方法,而不用担心会由于多次重绘而产生的效率问题。
另外多次调用 invalidate() 方法会使得 ViewRootImpl 多次接收到设置脏区域的请求,ViewRootImpl 会将这些脏区域累加到 mDirty 中,进而在随后的遍历中,一次性的完成所有脏区域的重绘。
窗口第一次绘制时候,ViewRootImpl 的 mFullRedrawNeeded 成员将会被设置为 true,也就是说 mDirty 所描述的区域将会扩大到整个窗口,进而实现完整重绘。View的脏区域和”实心”控件
增加两个知识点,能够更好的理解 View 的重绘过程。
为了保证绘制的效率,控件树仅对需要重绘的区域进行绘制。这部分区域成为”脏区域” Dirty Area。
当一个控件的内容发生变化而需要重绘时,它会通过 View.invalidate() 方法将其需要重绘的区域沿着控件树自下而上的交给 ViewRootImpl,并保存在 ViewRootImpl 的 mDirty 成员中,最后通过 scheduleTraversals() 引发一次遍历,进而进行重绘工作,这样就可以保证仅位于 mDirty 所描述的区域得到重绘,避免了不必要的开销。
View的isOpaque() 方法返回值表示此控件是否为”实心”的,所谓”实心”控件,是指在 onDraw() 方法中能够保证此控件的所有区域都会被其所绘制的内容完全覆盖。对于”实心”控件来说,背景和子元素(如果有的话)是被其 onDraw() 的内容完全遮住的,因此便可跳过遮挡内容的绘制工作从而提升效率。
简单来说透过此控件所属的区域无法看到此控件下的内容,也就是既没有半透明也没有空缺的部分。因为自定义 ViewGroup 控件默认是”实心”控件,所以默认不会调用 drawBackground() 和 onDraw() 方法,因为一旦 ViewGroup 的 onDraw() 方法,那么就会覆盖住它的子元素。但是我们仍然可以通过调用 setWillNotDraw(false) 和 setBackground() 方法来开启ViewGroup 的 onDraw() 功能。
下面我们从View的invalidate方法,自下(View)而上(ViewRootImpl)的分析。
invalidate : 使无效; damage : 损毁;dirty : 脏;
View//View
publicvoidinvalidate(){ invalidate( true); } voidinvalidate(booleaninvalidateCache){ invalidateInternal( 0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); } voidinvalidateInternal(intl, intt, intr, intb, booleaninvalidateCache, booleanfullInvalidate){ //如果VIew不可见,或者在动画中if(skipInvalidate()) { return; } //根据mPrivateFlags来标记是否重绘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) { //上面传入为true,表示需要全部重绘mLastIsOpaque = isOpaque(); //mPrivateFlags &= ~PFLAG_DRAWN; //去除绘制完毕标记。} //添加标记,表示View正在绘制。PFLAG_DRAWN为绘制完毕。mPrivateFlags |= PFLAG_DIRTY; //清除缓存,表示由当前View发起的重绘。if(invalidateCache) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } //把需要重绘的区域传递给父ViewfinalAttachInfo ai = mAttachInfo; finalViewParent p = mParent; if(p != null&& ai != null&& l < r && t < b) { finalRect damage = ai.mTmpInvalRect; //设置重绘区域(区域为当前View在父容器中的整个布局)damage.set(l, t, r, b); p.invalidateChild( this, damage); } ...... } }
上述代码中,会设置一系列的标记位到 mPrivateFlags 中,并且通过父容器的 invalidateChild 方法,将需要重绘的脏区域传给父容器。(ViewGroup 和 ViewRootImpl 都继承了 ViewParent类,该类中定义了子元素与父容器间的调用规范。)
ViewGroup//ViewGroup
@Override
publicfinalvoidinvalidateChild(View child, finalRect dirty){ ViewParent parent = this; finalAttachInfo attachInfo = mAttachInfo; if(attachInfo != null) { RectF boundingRect = attachInfo.mTmpTransformRect; boundingRect.set(dirty); ...... //父容器根据自身对子View的脏区域进行调整transformMatrix.mapRect(boundingRect); dirty.set(( int) Math.floor(boundingRect.left), ( int) Math.floor(boundingRect.top), ( int) Math.ceil(boundingRect.right), ( int) Math.ceil(boundingRect.bottom)); // 这里的do while方法,不断的去调用父类的invalidateChildInParent方法来传递重绘请求//直到调用到ViewRootImpl的invalidateChildInParent(责任链模式)do { View view = null; if(parent instanceofView) { view = (View) parent; } if(drawAnimation) { if(view != null) { view.mPrivateFlags |= PFLAG_DRAW_ANIMATION; } elseif(parent instanceofViewRootImpl){ ((ViewRootImpl) parent).mIsAnimating = true; } } //如果父类是"实心"的,那么设置它的mPrivateFlags标识// If the parent is dirty opaque or not dirty, mark it dirty with the opaque// flag coming from the child that initiated the invalidateif(view != null) { if((view.mViewFlags & FADING_EDGE_MASK) != 0&& view.getSolidColor() == 0) { opaqueFlag = PFLAG_DIRTY; } if((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) { view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag; } } //***往上递归调用父类的invalidateChildInParent***parent = parent.invalidateChildInParent(location, dirty); //设置父类的脏区域//父容器会把子View的脏区域转化为父容器中的坐标区域if(view != null) { // Account for transform on current parentMatrix m = view.getMatrix(); if(!m.isIdentity()) { RectF boundingRect = attachInfo.mTmpTransformRect; boundingRect.set(dirty); m.mapRect(boundingRect); dirty.set(( int) Math.floor(boundingRect.left), ( int) Math.floor(boundingRect.top), ( int) Math.ceil(boundingRect.right), ( int) Math.ceil(boundingRect.bottom)); } } } while(parent != null); } } ViewRootImpl
我们先验证一下最上层 ViewParent 为什么是 ViewRootImpl
//ViewRootImpl
publicvoidsetView(View view, WindowManager.LayoutParams attrs, View panelParentView){ view.assignParent( this); } //View
voidassignParent(ViewParent parent){ if(mParent == null) { mParent = parent; } elseif(parent == null){ mParent = null; } else{ thrownewRuntimeException("view "+ this+ " being added, but"+ " it already has a parent"); } }
在 ViewRootImpl 的 setView 方法中,由于传入的 View 正是 DecorView,所以最顶层的 ViewParent 即 ViewRootImpl。另外 ViewGroup 在 addView 方法中,也会调用 assignParent() 方法,设定子元素的父容器为它本身。
由于最上层的 ViewParent 是 ViewRootImpl,所以我们可以查看 ViewRootImpl 的 invalidateChildInParent 方法即可。
//ViewRootImpl
@Override
publicViewParent invalidateChildInParent(int[] location, Rect dirty){ //检查线程,这也是为什么invalidate一定要在主线程的原因checkThread(); if(dirty == null) { invalidate(); //有可能需要绘制整个窗口returnnull; } elseif(dirty.isEmpty()&& !mIsAnimating){ returnnull; } ..... invalidateRectOnScreen(dirty); returnnull; } //设置mDirty并执行View的工作流程
privatevoidinvalidateRectOnScreen(Rect dirty){ finalRect localDirty = mDirty; if(!localDirty.isEmpty() && !localDirty.contains(dirty)) { mAttachInfo.mSetIgnoreDirtyState = true; mAttachInfo.mIgnoreDirtyState = true; } // Add the new dirty rect to the current onelocalDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom); //在这里,mDirty的区域就变为方法中的dirty,即要重绘的脏区域...... if(!mWillDrawSoon && (intersected || mIsAnimating)) { scheduleTraversals(); //执行View的工作流程} }
什么?执行 invalidate() 方法居然会引起 scheduleTraversals()!
那么也就是说 invalidate() 会导致 perforMeasure()、performLayout()、perforDraw() 的调用了???
这个 scheduleTraversals() 很眼熟,我们一出场就在 requestLayout() 中见过,并且我们还说了 mLayoutRequested 用来表示是否 measure 和 layout。
//ViewRootImpl
@Override
publicvoidrequestLayout(){ if(!mHandlingLayoutInLayoutRequest) { checkThread(); //检查是否在主线程mLayoutRequested = true; //mLayoutRequested 是否measure和layout布局。scheduleTraversals(); } } privatevoidperformTraversals(){ booleanlayoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw); if(layoutRequested) { measureHierarchy(```); //measure} finalbooleandidLayout = layoutRequested && (!mStopped || mReportNextDraw); if(didLayout) { performLayout(lp, mWidth, mHeight); //layout} booleancancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible; if(!cancelDraw && !newSurface) { performDraw(); //draw} }
因为我们 invalidate 的时候,并没有设置 mLayoutRequested,所以放心,它只走 performDraw() 流程,并且在 draw() 流程中会清除 mDirty 区域。
并且只有设置了标识为的 View 才会调用 draw 方法进而调用 onDraw(),减少开销。「源码工程师各方面的考虑肯定比一般人更周到,我们写的是代码,他们写的是艺术。」

requestLayout
看完了 invalidate() 流程之后,requestLayout() 流程就比较好上手了。我们在 measure 阶段提到过 :
在 view.measure() 的方法里,仅当给与的 MeasureSpec 发生变化时,或要求强制重新布局时,才会进行测量。
强制重新布局 : 控件树中的一个子控件内容发生变化时,需要重新测量和布局的情况,在这种情况下,这个子控件的父控件(以及父控件的父控件)所提供的 MeasureSpec 必定与上次测量时的值相同,因而导致从 ViewRootImpl 到这个控件的路径上,父控件的 measure() 方法无法得到执行,进而导致子控件无法重新测量其布局和尺寸。(在父容器 measure 中遍历子元素)
解决途径 : 因此,当子控件因内容发生变化时,从子控件到父控件回溯到 ViewRootImpl,并依次调用父控件的 requestLayout() 方法。这个方法会在 mPrivateFlags 中加入标记 PFLAG_FORCE_LAYOUT,从而使得这些父控件的 measure() 方法得以顺利执行,进而这个子控件有机会进行重新布局与测量。这便是强制重新布局的意义所在。
下面我们看 View 的 requestLayout() 方法
//View
@CallSuper
publicvoidrequestLayout(){ if(mMeasureCache != null) mMeasureCache.clear(); ...... // 增加PFLAG_FORCE_LAYOUT标记,在measure时会校验此属性mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; // 父类不为空&&父类没有请求重新布局(是否有PFLAG_FORCE_LAYOUT标志)//这样同一个父容器的多个子View同时调用requestLayout()就不会增加开销if(mParent != null&& !mParent.isLayoutRequested()) { mParent.requestLayout(); } }
因为上面说过了,最顶层的 ViewParent 是 ViewRootImpl。
//ViewRootImpl
@Override
publicvoidrequestLayout(){
if(!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); }}
同样,requestLayout() 方法会调用 scheduleTraversals();,因为设置了 mLayoutRequested =true 标识,所以在 performTraversals() 中调用 performMeasure(),performLayout(),但是由于没有设置 mDirty,所以不会走 performDraw() 流程。
但是,requestLayout() 方法就一定不会导致 onDraw() 的调用吗?
在上面 layout() 方法中说道 :
在 View 的 layout() 方法里,首先通过 setFrame()(setOpticalFrame() 也走 setFrame())将 l、t、r、b 分别设置到 mLeft、mTop、mRight 和 mBottom,这样就可以确定 子View 在父容器的位置了,上面也说过了,这些位置是相对父容器的。//View --> layout()
protectedbooleansetFrame(intleft, inttop, intright, intbottom){ booleanchanged = false; if(mLeft != left || mRight != right || mTop != top || mBottom != bottom) { //布局坐标改变了changed = true; intoldWidth = mRight - mLeft; intoldHeight = mBottom - mTop; intnewWidth = right - left; intnewHeight = bottom - top; booleansizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight); // Invalidate our old positioninvalidate(sizeChanged); //调用invalidate重新绘制视图if(sizeChanged) { sizeChange(newWidth, newHeight, oldWidth, oldHeight); } ...... } returnchanged; }
看完代码我们就很清晰的知道,如果 layout 布局有变化,那么它也会调用 invalidate() 重绘自身。
下面再借用 Idtk 绘制的 layout 流程图

总结
至此,View 的工作流程分析完毕,文章如果有错误或者不妥之处,还望评论提出。
理清整体流程对我们android的布局,绘制,自定义View,和分析bug都有一个提升。返回搜狐,查看更多