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


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容