当前位置:   article > 正文

Android从源码解析三:View绘制流程_ondrawforeground

ondrawforeground

相信大家都知道我们所看到的控件都是直接或者间接的继承View类,下面我们看一下它是如何绘制在我们视野里的。
要知道,每一个视图的绘制过程都必须经历三个最主要的阶段,即onMeasure()、onLayout()和onDraw(),下面我们逐个对这三个阶段展开进行探讨。
首先,我们要知道 遍历View树最终的入口函数为调用ViewRootImpl的performTraversals函数;所以我们来分析ViewRootImple中的源码(在编译器里面我们看不到这个类,要去下载的sdk里面的source里面找),观察performTraversals()方法(这个方法801行,我就不列出来了吧)可以发现如下代码:

private void performTraversals() {
 // 记录ViewRootImpl管理的View树的根节点,final修饰,避免运行过程中修改
 // cache mView since it is used so much below...
    final View host = mView;
    /** View树遍历的主要三个步骤measure、layout、draw **/
    ......
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ......
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    ......
    performDraw();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

一. onMeasure()
onMeasure()方法顾名思义就是用于测量视图的大小的。
源码如下:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
  • 1
  • 2
  • 3
  • 4

就一句话,设置默认的高度和宽度,可是我们更想知道widthMeasureSpec,heightMeasureSpec这两个参数哪里来的?这就需要去分析ViewRootImple中的源码了,再仔细观察performTraversals()方法,可以发现如下代码:

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec=getRootMeasureSpec(mHeight,lp.height);
  • 1
  • 2

可以看到,这里调用了getRootMeasureSpec()方法去获取widthMeasureSpec和heightMeasureSpec的值,注意方法中传入的参数,其中lp.width和lp.height在创建ViewGroup实例的时候就被赋值了,它们都等于MATCH_PARENT。然后看下getRootMeasureSpec()方法中的代码,如下所示:

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {
        case ViewGroup.LayoutParams.MATCH_PARENT:
 // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

可以看到,我们使用MeasureSpec.makeMeasureSpec来组装一个MeasureSpec的值,当rootDimension参数等于MATCH_PARENT的时候,MeasureSpec的specMode就等于EXACTLY,当rootDimension等于WRAP_CONTENT的时候,MeasureSpec的specMode就等于AT_MOST。并且MATCH_PARENT和WRAP_CONTENT时的specSize都是等于windowSize的,也就意味着根视图总是会充满全屏的。
百科一下EXACTLY、 AT_MOST、 UNSPECIFIED:

MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。specMode一共有三种类型,如下所示:
1. EXACTLY
表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
2. AT_MOST
表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
3. UNSPECIFIED
表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

介绍了这么多MeasureSpec相关的内容,接下来我们看下View的measure()方法里面的代码吧,如下所示:

 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }

        // Suppress sign extension for the low bytes
        long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
        if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);

        if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {

            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;

            resolveRtlPropertiesIfNeeded();

            int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
                    mMeasureCache.indexOfKey(key);
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } 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);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("View with id " + getId() + ": "
                        + getClass().getName() + "#onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;

        mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
                (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

注意:此方法是final的,因此子类无法重写,也就是说无法改变View的measure框架,然后我们看到里面调用了onMeasure()方法,这里才是真正去测量并设置View大小的地方,默认会调用getDefaultSize()方法来获取视图的大小,如下所示:

 public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

二、onLayout()
measure过程结束后,视图的大小就已经测量好了,接下来就是确定视图的位置。就是layout的过程,
ViewRoot的performTraversals()方法的layout会在measure结束后继续执行,如下所示:

  performLayout(lp, desiredWindowWidth, desiredWindowHeight);
  • 1
并调用View的layout()方法来执行此过程,
  • 1
 host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
  • 1

我们来看下View里面的layout方法:

     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;

        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);
            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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

这里会根据mPrivateFlags3 和 PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT 的值判断需不需要再执行

     onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
  • 1

然后再调用isLayoutModeOptical()方法来判断视图的大小是否发生过变化,以确定有没有必要对当前的视图进行重绘。
接下来才会调用onLayout()方法,我们看一下它的代码:

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }
  • 1
  • 2

没有任何的实现内容,而是让子类去重写,比如说LinearLayout、RelativeLayout等布局,都是重写了这个方法,然后在内部按照各自的规则对子视图进行布局的。
补充一点:我们在自定义布局的时候都重写了这个方法,会用到getWidth()、getHeight();他们和getMeasureWidth、getMeasureHeight的区别是啥?
getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。
三. onDraw()
measure和layout的过程都结束后,接下来就进入到draw的过程了。在这里才真正地开始对视图进行绘制。
在ViewRootImpl中跟踪到:

private void performDraw() {  
      ......  
      draw(fullRedrawNeeded);  
      ......  
}  
  • 1
  • 2
  • 3
  • 4
  • 5

继续

private void draw(boolean fullRedrawNeeded) {  
        Surface surface = mSurface;    //原始图像缓冲区(raw buffer)的一个句柄
        ........  
         mAttachInfo.mTreeObserver.dispatchOnDraw();
         ......
        if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {  
               return;  
        }  
        .......  
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这里的drawSoftware方法就是绘画了。(我们有看到优化app的内存的时候有这种做法:将背景图片放在非UI线程绘制,提升APP的效率,解决方案是将背景图片通过SurfaceView来绘制,这里的SurfaceView提供了一个专门用于绘制的surface)

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff,  
        boolean scalingRequired, Rect dirty) {  
   .......  
   canvas = mSurface.lockCanvas(dirty);    //在指定区域拿到canvas画笔  
   .......  
   mView.draw(canvas);  //开始绘制我们的View  
   .......  
   surface.unlockCanvasAndPost(canvas);    //回收surface  
   .......  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

然后调用View的draw()方法来执行具体的绘制工作。draw()方法内部的绘制过程总共可以分为六步,其中第二步和第五步在一般情况下很少用到,因此这里我们只分析简化后的绘制过程。代码如下所示:

 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;

        /*
         * 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 needed
        int saveCount;

        if (!dirtyOpaque) {
            drawBackground(canvas);
        }

        // skip step 2 & 5 if possible (common case)
        final int viewFlags = mViewFlags;
        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
        if (!verticalEdges && !horizontalEdges) {
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);

            // Step 4, draw the children
            dispatchDraw(canvas);

            // Overlay is part of the content and draws beneath Foreground
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }

            // Step 6, draw decorations (foreground, scrollbars)
            onDrawForeground(canvas);

            // we're done...
            return;
        }
//此处省略n行代码,
        ..........
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

很清晰了解到,这一步的作用是对视图的背景进行绘制。主要是drawBackground()这个方法,我们来看一下这个方法:

  private void drawBackground(Canvas canvas) {
        final Drawable background = mBackground;
        if (background == null) {
            return;
        }
        setBackgroundBounds();
        // Attempt to use a display list if requested.
        if (canvas.isHardwareAccelerated() && mAttachInfo != null
                && mAttachInfo.mHardwareRenderer != null) {
            mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);

            final RenderNode renderNode = mBackgroundRenderNode;
            if (renderNode != null && renderNode.isValid()) {
                setBackgroundRenderNodeProperties(renderNode);
                ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
                return;
            }
        }

        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        if ((scrollX | scrollY) == 0) {
            background.draw(canvas);
        } else {
            canvas.translate(scrollX, scrollY);
            background.draw(canvas);
            canvas.translate(-scrollX, -scrollY);
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

在代码的第2行 获取到当前view的背景,是一个Drawable对象 ,
在代码的第6行 使用setBackgroundBounds()方法设置背景边界,
在代码的第23行 调用刚刚获取的Drawable对象 进行draw(canvas);

第三步:
if (!dirtyOpaque) onDraw(canvas);
一句话 调用onDraw方法绘制view的内容,由于不同的view内容不同,所以需要子类进行重写。
第四步:
dispatchDraw(canvas);
绘制子view,这里仍然需要当前layout的dispatchDraw方法来完成对各子view的绘制。
第六步:
onDrawForeground(canvas);
绘制装饰(前台,滚动条)。
第二步和第五步到底是啥呢,这里解释一下:
第二步是保存画布图层,
第五步是绘制渐变效果和恢复图层。

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号