本文共 5022 字,大约阅读时间需要 16 分钟。
分类: 2012-07-28 11:07 6038人阅读 经过对View树的measure和layout过程后,接下来将结合前两步得到的结果对View树进行绘制,之前以为measure过程是measure、layout和draw三部曲中最复杂的一步,在仔细分析draw过程后才发现自己之前的论断有失准确性。不过从整体来看,draw过程的逻辑是比较清晰的,和measure和layout过程十分相似,而本文将从整体来介绍draw的整个流程,至于其中的细节可能会在另外一篇文章中介绍。
和measure和layout一样,draw过程也是在ViewRoot的performTraversals()的内部发起的,其调用顺序在measure()和layout()之后,同样的,performTraversals()发起的draw过程最终会调用到mView的draw()函数,这里的mView对于Actiity来说就是PhoneWindow.DecorView。
首先来看下与draw过程相关的函数,之所以先列出相关函数,目的是让大家对这些函数有个印象,以免在介绍具体细节的时候茫然:
- ViewRootImpl.draw(),仅在ViewRootImpl.performTraversals()的内部调用
- DecorView.draw(), 上一步中的ViewRootImpl.draw()会调用到该函数,DecorView.draw()继承自Framelayout,DecorView和FrameLayout,以及FrameLayout的父类ViewGroup都未重载draw(),而ViewGroup的父类是View类,因此DecorView.draw()调用的是View.draw()的默认实现
- View.onDraw(),绘制View本身,自定义View往往会重载该函数来绘制View本身的内容
- View.dispatchDraw(), View中的dispatchDraw默认是空实现,ViewGroup重载了该函数,内部会循环调用View.drawChild()来发起对子视图的绘制,应用程序不应该重载ViewGroup,因为该函数的默认实现代表了View的绘制流程
- ViewGroup.drawChild(),该函数只在ViewGroup中实现,原因就是只有ViewGroup才需要绘制child,drawChild内部又会调用View.draw()函数来完成子视图的绘制(有可能直接调用dispatchDraw)
下面笔者将从源码来展现draw的整体过程,当然源码中只展现关键部分,细节部分将在下一篇文章中具体介绍,首先来看performTraversals(),因为draw过程最先是从这里发起的: - private void performTraversals() {
- final View host = mView;
- ...
- host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
- ...
- host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
- ...
- draw(fullRedrawNeeded);
- }
注意到measure和layout过程直接调用的是mView的measure和layout函数,而draw调用的是ViewRootImpl的内部draw(boolean fullRedrawNeeded)函数,再由draw(boolean fullRedrawNeeded)函数来调用mView.draw()函数,draw(boolean fullRedrawNeeded)包含draw过程的一些前期处理,通过下面的代码可以看出调用关系: - private void draw(boolean fullRedrawNeeded) {
- Surface surface = mSurface;
- if (surface == null || !surface.isValid()) {
- return;
- }
- ...
- try {
- canvas.translate(0, -yoff);
- if (mTranslator != null) {
- <span style="white-space:pre"> </span>mTranslator.translateCanvas(canvas);
- }
- canvas.setScreenDensity(scalingRequired
- ? DisplayMetrics.DENSITY_DEVICE : 0);
- mAttachInfo.mSetIgnoreDirtyState = false;
- mView.draw(canvas);
- } finally {
- if (!mAttachInfo.mSetIgnoreDirtyState) {
-
- mAttachInfo.mIgnoreDirtyState = false;
- }
- }
- ...
- }
下面将转到mView.draw(),之前提到mView.draw()调用的就是View.java的默认实现,View类中的draw函数体现了View绘制的核心流程,因此我们下面重点来看下View.java中draw的调用流程: - public void draw(Canvas canvas) {
- ...
-
-
-
-
-
-
-
-
-
-
-
-
-
- ...
- background.draw(canvas);
- ...
-
- ...
-
- ...
- if (solidColor == 0) {
- final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
-
- if (drawTop) {
- canvas.saveLayer(left, top, right, top + length, null, flags);
- }
- ...
-
- if (!dirtyOpaque) onDraw(canvas);
-
-
- dispatchDraw(canvas);
-
-
-
- if (drawTop) {
- matrix.setScale(1, fadeHeight * topFadeStrength);
- matrix.postTranslate(left, top);
- fade.setLocalMatrix(matrix);
- canvas.drawRect(left, top, right, top + length, p);
- }
- ...
-
- onDrawScrollBars(canvas);
- }
官方源码中对View的绘制流程给了非常详细的注释,笔者将整个绘制过程的关键步骤抽取出来方便了解整个过程,通过阅读上面的代码可以知道整个绘制过程包括View的背景绘制,View本身内容的绘制,子视图的绘制(如果包含子视图),渐变框的绘制以及滚动条的绘制。 我们重点要关注的是View本身内容的绘制和子视图的绘制,即onDraw()和dispatchDraw()函数。 对于View.java和ViewGroup.java,onDraw()默认都是空实现,因为具体View本身长什么样子是由View的设计者来决定的,默认不显示任何东西。 View.java中dispatchDraw()默认为空实现,因为其不包含子视图,而ViewGroup重载了dispatchDraw()来对其子视图进行绘制,通常应用程序不应该对dispatchDraw()进行重载,其默认实现体现了View系统绘制的流程。那么,接下来我们继续分析下ViewGroup中dispatchDraw()的具体流程: - @Override
- protected void dispatchDraw(Canvas canvas) {
- ...
-
- if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
- for (int i = 0; i < count; i++) {
- final View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
- more |= drawChild(canvas, child, drawingTime);
- }
- }
- } else {
- for (int i = 0; i < count; i++) {
- final View child = children[getChildDrawingOrder(count, i)];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
- more |= drawChild(canvas, child, drawingTime);
- }
- }
- }
- ......
- }
dispatchDraw()的核心代码就是通过for循环调用drawChild()对ViewGroup的每个子视图进行绘制,上述代码中如果FLAG_USE_CHILD_DRAWING_ORDER为true,则子视图的绘制顺序通过getChildDrawingOrder来决定,默认的绘制顺序即是子视图加入ViewGroup的顺序,而我们可以重载getChildDrawingOrder函数来更改默认的绘制顺序,这会影响到子视图之间的重叠关系。 drawChild()的核心过程就是为子视图分配合适的cavas剪切区,剪切区的大小正是由layout过程决定的,而剪切区的位置取决于滚动值以及子视图当前的动画。设置完剪切区后就会调用子视图的draw()函数进行具体的绘制,如果子视图的包含SKIP_DRAW标识,那么仅调用dispatchDraw(),即跳过子视图本身的绘制,但要绘制视图可能包含的字视图。 完成了dispatchDraw()过程后,View系统会调用onDrawScrollBars()来绘制滚动条,滚动条绘制的细节这里就不展开介绍了。 总结:以上文字着重于对draw整体流程的介绍,通过对关键函数以及它们之间的调用关系来展示draw的内部过程,从整体上把握整个过程将更有利于后面对细节的理解和分析。 转载地址:http://moomi.baihongyu.com/