View的绘制过程

主要记录学习《Android开发艺术探索》

1.

View的绘制过程:

ActivityThread(handlerResumeActivity())--->WindwowManagerImpl(addView())--->WindowManageGlobal(addView())--->ViewRootImpl(requestLayout())--->ViewRootImpl(scheduleTraversals())--->ViewRootImpl(doTraversal())--->ViewRootImpl(preformTraversals());

ViewRoot对应ViewRootImpl类,它是连接WindowManager和DecorView的纽带,在ActivityThread中,当Activity对象被创建完毕后,会将DecorView 添加到
Window中,同时会创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联

root=new ViewRootImpl(view.getContext(),display);
root.setView(view,panelParentView);

View的绘制流程从ViewRootImpl的 performTraversals()开始
依次调用以下三个方法
1.performMeasure()
此方法调用View的measure()的方法--->调用View的onMeasure()方法

2.performLayout()
此方法调用View的layout()的方法--->
调用setFrame(l, t, r, b)确定自身在父控件的位置
调用View的onLayout()方法(ViewGroup)会确定子控件在自身位置
3.performDraw()

Measure过程结束后可通过getMeasuredWidth和getMeasuredHeight方法获取View的测量宽高。
Layout过程结束后可通过getTop,getBottom,getLeft,getRight来拿到View的四个点的坐标,并可通过getWidth和getHeight方法获取到View的最终宽高。
Draw过程结束后View的内容才最终显示在屏幕上。

DecorView是一个FrameLayout 内部一般包含一个LinearLayout,这个LinearLayout里面有上下两个部分(具体情况和Android版本和主题有关)上面是标题栏,下面是内容栏(内容栏为FrameLayout),内容栏的id为content。

MeasureSpec 是一个32位的int值,高2位代表SpecMode,低30位代表SpecSize。
SpecMode 指测量模式共有三类
1.UNSPECIFIED:表示父容器对View不做任何限制,要多大给多大。
2.EXACTLY:表示父容器已检测出View的所需确切大小,这时候View的最终大小就是SpecSize所指定的值。对应LayoutParams中match_parent和具体的数值这两种模式
3.AT_MOST:指定了一个可用大小的即SpecSize,View的大小不能大于这个值。具体要看不同的View的具体实现。它对应于LayoutParams中的wrap_content.

DecorView的MeasureSpec有屏幕尺寸和自身的LayoutParams共同决定。
普通的View的MeasureSpec需要父容器和自身的LayoutParams一起来决定。
View的measure过程是由ViewGroup测量过程传递过来的。

getChildMeasureSpec()

测量规则如下:
parentSize为父控件去除padding的可使用大小。
1.若View指定了确切的尺寸childSize。View的MeasureSpec就是(EXACTLY,childSize)
2.若View是match_parent的
2.1父容器的SpecMode为EXACTLY 。View的MeasureSpec就是(EXACTLY, parentSize)
2.2父容器的SpecMode为AT_MOST。View的MeasureSpec就是(AT_MOST,parentSize)
3.若View是wrap_content的
3.1父容器的SpecMode为EXACTLY 。View的MeasureSpec就是(AT_MOST, parentSize)
3.2父容器的SpecMode为AT_MOST。View的MeasureSpec就是(AT_MOST,parentSize)

2

View的工作流程

1)measure过程:
1.View的measure过程
View的measure方法是final无法重写,但是View的measure方法会调用View的onMeasure的方法。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
 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;
// AT_MOST和EXACTLY两种模式没有区别大小都为父控件可用大小
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }
// 若没有设置背景就为mMinWidth,若设置了背景就为背景大小和mMinWidth的中最大值
 protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

public int getMinimumHeight() {    return mMinHeight;}

从getDefaultSize方法的实现来看,直接继承View的自定义控件需要重写onMesasure的方法。否则的在布局中使用wrap_content相当于使用match_parent。

private int mWidth;
private int mHeight;
  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSpecMode=MeasureSpec.getMode(widthMeasureSpec);
        int heightSpecMode=MeasureSpec.getMode(heightMeasureSpec);
        int widthSpecSize=MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecSize=MeasureSpec.getSize(heightMeasureSpec);
        if (widthSpecMode==MeasureSpec.AT_MOST && heightSpecMode==MeasureSpec.AT_MOST)
        {
            setMeasuredDimension(mWidth,mHeight);
        }else if (widthSpecMode==MeasureSpec.AT_MOST)
        {
            setMeasuredDimension(mWidth,heightSpecSize);
        }else if (heightSpecMode==MeasureSpec.AT_MOST)
        {
            setMeasuredDimension(widthSpecSize,mHeight);
        }
    }

我们只需要给View设定一个默认的内部宽高(mWidth和mHeight)并在wrap_content时进行设置此宽高即可,至于如何设定,可根据具体情况设置

2.ViewGroup的measure过程
ViewGroup除了测量自身外还会遍历去调用所有子元素的measure方法,各个子元素再去递归去执行这个过程。ViewGroup是一个抽象类并没有重写onMeasure方法,但它提供了一个measureChildren的方法

 protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

 protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }


ViewGroup并没有定义测量的具体过程,ViewGroup是一个抽象类,其测量过程需要各个子类自己实现。不做统一实现是因为不同的ViewGroup子类具有不同的布局特性,测量细节各不相同。
获取view的测量宽高
1.在onWindowFocusChanged方法中View已经初始化完毕,当Activity的窗口失去焦点或得到焦点的时候均会被调用

 @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if (hasFocus)
        {
            int width=view.getMeasuredWidth();
            int height=view.getMeasuredHeight();
        }
    }
 
  1. post方法可以把一个Runnable投递到消息队列的尾部,Looper取出消息调用Runnable的时候View已经初始化好了。
view.post(new Runnable() {
            @Override
            public void run() {
                int width=view.getMeasuredWidth();
                int height=view.getMeasuredHeight();
                Log.d(TAG, "run: " +width);
            }
        });

3.ViewTreeObserver当View树的状态发生改变或者View树内部的View的可见性发生改变是 onGlobalLayout方法会回调。

 ViewTreeObserver observer=view.getViewTreeObserver();
        observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                int width=view.getMeasuredWidth();
                int height=view.getMeasuredHeight();
            }
        });

2)layout过程:
layout方法确定自身在父控件的中位置,若为ViewGroup要重新来onLayout确定子控件的位置。
layout方法通过调用setFrame确定mLeft,mTop,mBottom,mRight这四个值,四个顶点确定,那么View自身在父控件的位置就确定了。
layout在View和ViewGroup一般不用重新。
但自定义ViewGroup的时候要重写onLayout方法 来确定子View的摆放位置,onLayout一般也是遍历子View并调用子View的layout方法来确定摆放位置。

3)draw过程
绘制背景 drawBackground(canvas);
绘制自己 onDraw(canvas);
绘制children dispatchDraw(canvas);
绘制装饰 onDrawForeground(canvas);

 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;
        }

        /*
         * Here we do the full fledged routine...
         * (this is an uncommon case where speed matters less,
         * this is why we repeat some of the tests that have been
         * done above)
         */

        boolean drawTop = false;
        boolean drawBottom = false;
        boolean drawLeft = false;
        boolean drawRight = false;

        float topFadeStrength = 0.0f;
        float bottomFadeStrength = 0.0f;
        float leftFadeStrength = 0.0f;
        float rightFadeStrength = 0.0f;

        // Step 2, save the canvas' layers
        int paddingLeft = mPaddingLeft;

        final boolean offsetRequired = isPaddingOffsetRequired();
        if (offsetRequired) {
            paddingLeft += getLeftPaddingOffset();
        }

        int left = mScrollX + paddingLeft;
        int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
        int top = mScrollY + getFadeTop(offsetRequired);
        int bottom = top + getFadeHeight(offsetRequired);

        if (offsetRequired) {
            right += getRightPaddingOffset();
            bottom += getBottomPaddingOffset();
        }

        final ScrollabilityCache scrollabilityCache = mScrollCache;
        final float fadeHeight = scrollabilityCache.fadingEdgeLength;
        int length = (int) fadeHeight;

        // clip the fade length if top and bottom fades overlap
        // overlapping fades produce odd-looking artifacts
        if (verticalEdges && (top + length > bottom - length)) {
            length = (bottom - top) / 2;
        }

        // also clip horizontal fades if necessary
        if (horizontalEdges && (left + length > right - length)) {
            length = (right - left) / 2;
        }

        if (verticalEdges) {
            topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
            drawTop = topFadeStrength * fadeHeight > 1.0f;
            bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
            drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
        }

        if (horizontalEdges) {
            leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
            drawLeft = leftFadeStrength * fadeHeight > 1.0f;
            rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
            drawRight = rightFadeStrength * fadeHeight > 1.0f;
        }

        saveCount = canvas.getSaveCount();

        int solidColor = getSolidColor();
        if (solidColor == 0) {
            final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;

            if (drawTop) {
                canvas.saveLayer(left, top, right, top + length, null, flags);
            }

            if (drawBottom) {
                canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
            }

            if (drawLeft) {
                canvas.saveLayer(left, top, left + length, bottom, null, flags);
            }

            if (drawRight) {
                canvas.saveLayer(right - length, top, right, bottom, null, flags);
            }
        } else {
            scrollabilityCache.setFadeColor(solidColor);
        }

        // Step 3, draw the content
        if (!dirtyOpaque) onDraw(canvas);

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

        // Step 5, draw the fade effect and restore layers
        final Paint p = scrollabilityCache.paint;
        final Matrix matrix = scrollabilityCache.matrix;
        final Shader fade = scrollabilityCache.shader;

        if (drawTop) {
            matrix.setScale(1, fadeHeight * topFadeStrength);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left, top, right, top + length, p);
        }

        if (drawBottom) {
            matrix.setScale(1, fadeHeight * bottomFadeStrength);
            matrix.postRotate(180);
            matrix.postTranslate(left, bottom);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left, bottom - length, right, bottom, p);
        }

        if (drawLeft) {
            matrix.setScale(1, fadeHeight * leftFadeStrength);
            matrix.postRotate(-90);
            matrix.postTranslate(left, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(left, top, left + length, bottom, p);
        }

        if (drawRight) {
            matrix.setScale(1, fadeHeight * rightFadeStrength);
            matrix.postRotate(90);
            matrix.postTranslate(right, top);
            fade.setLocalMatrix(matrix);
            p.setShader(fade);
            canvas.drawRect(right - length, top, right, bottom, p);
        }

        canvas.restoreToCount(saveCount);

        // 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);
    }

设置不绘制自身,View默认关闭,ViewGroup默认开启。

    public void setWillNotDraw(boolean willNotDraw) {
        setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
    }


你可能感兴趣的:(View的绘制过程)