View的繪制過程

主要記錄學習《Android開發(fā)藝術(shù)探索》

1.

View的繪制過程:

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

ViewRoot對應ViewRootImpl類,它是連接WindowManager和DecorView的紐帶,在ActivityThread中,當Activity對象被創(chuàng)建完畢后,會將DecorView 添加到
Window中,同時會創(chuàng)建ViewRootImpl對象,并將ViewRootImpl對象和DecorView建立關(guān)聯(lián)

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

View的繪制流程從ViewRootImpl的 performTraversals()開始
依次調(diào)用以下三個方法
1.performMeasure()
此方法調(diào)用View的measure()的方法--->調(diào)用View的onMeasure()方法

2.performLayout()
此方法調(diào)用View的layout()的方法--->
調(diào)用setFrame(l, t, r, b)確定自身在父控件的位置
調(diào)用View的onLayout()方法(ViewGroup)會確定子控件在自身位置
3.performDraw()

Measure過程結(jié)束后可通過getMeasuredWidth和getMeasuredHeight方法獲取View的測量寬高。
Layout過程結(jié)束后可通過getTop,getBottom,getLeft,getRight來拿到View的四個點的坐標,并可通過getWidth和getHeight方法獲取到View的最終寬高。
Draw過程結(jié)束后View的內(nèi)容才最終顯示在屏幕上。

DecorView是一個FrameLayout 內(nèi)部一般包含一個LinearLayout,這個LinearLayout里面有上下兩個部分(具體情況和Android版本和主題有關(guān))上面是標題欄,下面是內(nèi)容欄(內(nèi)容欄為FrameLayout),內(nèi)容欄的id為content。

MeasureSpec 是一個32位的int值,高2位代表SpecMode,低30位代表SpecSize。
SpecMode 指測量模式共有三類
1.UNSPECIFIED:表示父容器對View不做任何限制,要多大給多大。
2.EXACTLY:表示父容器已檢測出View的所需確切大小,這時候View的最終大小就是SpecSize所指定的值。對應LayoutParams中match_parent和具體的數(shù)值這兩種模式
3.AT_MOST:指定了一個可用大小的即SpecSize,View的大小不能大于這個值。具體要看不同的View的具體實現(xiàn)。它對應于LayoutParams中的wrap_content.

DecorView的MeasureSpec有屏幕尺寸和自身的LayoutParams共同決定。
普通的View的MeasureSpec需要父容器和自身的LayoutParams一起來決定。
View的measure過程是由ViewGroup測量過程傳遞過來的。

getChildMeasureSpec()

測量規(guī)則如下:
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方法會調(diào)用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兩種模式?jīng)]有區(qū)別大小都為父控件可用大小
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }
// 若沒有設(shè)置背景就為mMinWidth,若設(shè)置了背景就為背景大小和mMinWidth的中最大值
 protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

public int getMinimumHeight() {    return mMinHeight;}

從getDefaultSize方法的實現(xiàn)來看,直接繼承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設(shè)定一個默認的內(nèi)部寬高(mWidth和mHeight)并在wrap_content時進行設(shè)置此寬高即可,至于如何設(shè)定,可根據(jù)具體情況設(shè)置

2.ViewGroup的measure過程
ViewGroup除了測量自身外還會遍歷去調(diào)用所有子元素的measure方法,各個子元素再去遞歸去執(zhí)行這個過程。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是一個抽象類,其測量過程需要各個子類自己實現(xiàn)。不做統(tǒng)一實現(xiàn)是因為不同的ViewGroup子類具有不同的布局特性,測量細節(jié)各不相同。
獲取view的測量寬高
1.在onWindowFocusChanged方法中View已經(jīng)初始化完畢,當Activity的窗口失去焦點或得到焦點的時候均會被調(diào)用

 @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if (hasFocus)
        {
            int width=view.getMeasuredWidth();
            int height=view.getMeasuredHeight();
        }
    }
 
  1. post方法可以把一個Runnable投遞到消息隊列的尾部,Looper取出消息調(diào)用Runnable的時候View已經(jīng)初始化好了。
view.post(new Runnable() {
            @Override
            public void run() {
                int width=view.getMeasuredWidth();
                int height=view.getMeasuredHeight();
                Log.d(TAG, "run: " +width);
            }
        });

3.ViewTreeObserver當View樹的狀態(tài)發(fā)生改變或者View樹內(nèi)部的View的可見性發(fā)生改變是 onGlobalLayout方法會回調(diào)。

 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方法通過調(diào)用setFrame確定mLeft,mTop,mBottom,mRight這四個值,四個頂點確定,那么View自身在父控件的位置就確定了。
layout在View和ViewGroup一般不用重新。
但自定義ViewGroup的時候要重寫onLayout方法 來確定子View的擺放位置,onLayout一般也是遍歷子View并調(diào)用子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);
    }

設(shè)置不繪制自身,View默認關(guān)閉,ViewGroup默認開啟。

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


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容