View测量,布局,绘制三大流程

Measure过程:

View方法:

1.public final void measure(int widthMeasureSpec, int heightMeasureSpec) {

………..

int cacheIndex = forceLayout ? -1 :mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
    // measure ourselves, this should setthe 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 thehigh 32 bits, no mask needed
    setMeasuredDimensionRaw((int) (value>> 32), (int) value);
    mPrivateFlags3 |=PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}

………….

Measure方法时final,View的子类不允许重写该方法,在这个方法中去调用onMeasure方法

2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(),widthMeasureSpec),
           getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

Measure方法中调用setMeasuredDimension设置测量值

上述两个方法中的参数(int widthMeasureSpec, intheightMeasureSpec)都是由父容传递过来的,父容器根据自己的measureSpec和子View的layoutParams和padding和margin计算子View的measureSpec

ViewGroup方法:

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

该方法测量所有的子View

2.protected void measureChild(View child, intparentWidthMeasureSpec,
        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);
}

该方法测量一个子View,调用子view.measure方法,子View的measureSpec计算方法getChildMeasureSpec

3. protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, intwidthUsed,
        int parentHeightMeasureSpec, intheightUsed) {
    final MarginLayoutParams lp =(MarginLayoutParams) child.getLayoutParams();

    final int childWidthMeasureSpec =getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight+ lp.leftMargin + lp.rightMargin
                    + widthUsed, lp.width);
    final int childHeightMeasureSpec =getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom+ lp.topMargin + lp.bottomMargin
                    + heightUsed,lp.height);

    child.measure(childWidthMeasureSpec,childHeightMeasureSpec);
}

也是测量子View,这个方法考虑到padding和margin,ViewGroup是一个抽象类,没有

重写onMeasure方法,这是因为ViewGroup的子类具有不同的布局特性,每个子类的测量细节也不同,由它的子类,如LinearLayout,RelativeLayout去重写onMeasure方法

LinearLayout方法:

LinearLayout继承自ViewGroup

1. @Override
protected void onMeasure(intwidthMeasureSpec, int heightMeasureSpec) {
    if (mOrientation == VERTICAL) {
        measureVertical(widthMeasureSpec,heightMeasureSpec);
    } else {
       measureHorizontal(widthMeasureSpec, heightMeasureSpec);
    }
}

分为水平方向和竖直方向两种情况,下面看一下竖直方向

2. voidmeasureVertical(int widthMeasureSpec, int heightMeasureSpec) {

………

 

final int usedHeight = totalWeight == 0 ?mTotalLength : 0;
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
        heightMeasureSpec, usedHeight);


final int childHeight = child.getMeasuredHeight()

……

 

 final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
            mPaddingLeft + mPaddingRight+ lp.leftMargin + lp.rightMargin,
            lp.width);
    child.measure(childWidthMeasureSpec,childHeightMeasureSpec);

    // Child may now not fit in verticaldimension.
    childState =combineMeasuredStates(childState, child.getMeasuredState()
            &(MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
}

 

………

maxWidth = Math.max(maxWidth,getSuggestedMinimumWidth());

setMeasuredDimension(resolveSizeAndState(maxWidth,widthMeasureSpec, childState),
        heightSizeAndState);

 

……..

在竖直方向上,先测量子View,根据子View的垂直方向高度情况确定LinearLayout的高度,最后setMeasuredDimension.

3. void measureChildBeforeLayout(View child, int childIndex,
        int widthMeasureSpec, inttotalWidth, int heightMeasureSpec,
        int totalHeight) {
    measureChildWithMargins(child,widthMeasureSpec, totalWidth,
            heightMeasureSpec,totalHeight);
}

调用父类ViewGroup的方法measureChildWithMargins测量子类。

总结Measure过程:

因为view的measure方法是final,子类不能重写,measure中又调用了onMeasure方法,所有的子View(包括ViewGoup)都是先调用measure,在measure方法中调用onMeasure

大致流程:首先是DecorView的measureSpec创建过程:根据窗口尺寸和自身的LayoutParams创建:rootMeasureSpec,,然后由performMeasure开始执行测量流程:先调用父容器的measure,measure中调用自己重写的onMeasure,在onMeasure中去测量子View(调用子View的measure,子View的measure中又调用自己的onMeasure,这样就测量过程就冲),然后根据子View的测量宽和高来计算出自己最终的测量宽高值,自定义View一定要重写onMeasure方法。

 

Layout过程:

View的方法:

1.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);

        if (shouldDrawRoundScrollbar()) {
            if(mRoundScrollbarRenderer ==null) {
                mRoundScrollbarRenderer =new RoundScrollbarRenderer(this);
            }
        } else {
            mRoundScrollbarRenderer = null;
        }

        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;

    if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT)!= 0) {
        mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
        notifyEnterOrExitForAutoFillIfNeeded(true);
    }
}

在layout方法中首先setFrame确定自己的位置,然后调用child.onLayout方法

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

onLayout方法是空的,交给具体的View子类去实现,用来放置子View,即调用子View的layout方法

3. protected boolean setFrame(int left, int top, int right, int bottom) {
    boolean changed = false;

    if (DBG) {
        Log.d("View", this + "View.setFrame(" + left + "," + top + ","
                + right + "," +bottom + ")");
    }

    if (mLeft != left || mRight != right ||mTop != top || mBottom != bottom) {
        changed = true;

        // Remember our drawn bit
        int drawn = mPrivateFlags & PFLAG_DRAWN;

        int oldWidth = mRight - mLeft;
        int oldHeight = mBottom - mTop;
        int newWidth = right - left;
        int newHeight = bottom - top;
        boolean sizeChanged = (newWidth!= oldWidth) || (newHeight != oldHeight);

        // Invalidate our old position
        invalidate(sizeChanged);

        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;

        mRenderNode.setLeftTopRightBottom(mLeft,mTop, mRight, mBottom);

        mPrivateFlags |= PFLAG_HAS_BOUNDS;


        if (sizeChanged) {
            sizeChange(newWidth, newHeight,oldWidth, oldHeight);
        }

        if ((mViewFlags & VISIBILITY_MASK)== VISIBLE || mGhostView != null) {
            // If we are visible, forcethe DRAWN bit to on so that
            // this invalidate will gothrough (at least to our parent).
            // This is because someonemay have invalidated this view
            // before this call tosetFrame came in, thereby clearing
            // the DRAWN bit.
            mPrivateFlags |= PFLAG_DRAWN;
            invalidate(sizeChanged);
            // parent display list mayneed to be recreated based on a change in the bounds
            // of any child
            invalidateParentCaches();
        }

        // Reset drawn bit to originalvalue (invalidate turns it off)
        mPrivateFlags |= drawn;

        mBackgroundSizeChanged = true;
        mDefaultFocusHighlightSizeChanged= true;
        if (mForegroundInfo != null) {
            mForegroundInfo.mBoundsChanged= true;
        }

       notifySubtreeAccessibilityStateChangedIfNeeded();
    }
    return changed;
}

setFrame方法设置mLeft,mTop,mRight,mBottom的值,即设置该view的位置

ViewGroup的方法:

1. @Override
public final void layout(int l, int t,int r, int b) {
    if (!mSuppressLayout && (mTransition== null || !mTransition.isChangingLayout())) {
        if (mTransition != null) {
            mTransition.layoutChange(this);
        }
        super.layout(l, t, r, b);
    } else {
        // record the fact that we noop'dit; request layout when transition finishes
        mLayoutCalledWhileSuppressed = true;
    }
}

还是调用了父类,即View的layout方法

2. @Override
protected abstract void onLayout(booleanchanged,
        int l, int t, int r, int b);

抽象方法,具体位置怎么确定交给ViewGroup的子类去实现

LinearLayout方法:

1. @Override
protected void onLayout(boolean changed,int l, int t, int r, int b) {
    if (mOrientation == VERTICAL) {
        layoutVertical(l, t, r, b);
    } else {
        layoutHorizontal(l, t, r, b);
    }
}

分为竖直和水平两种情况

2. void layoutVertical(int left, int top, int right, int bottom) {
    final int paddingLeft = mPaddingLeft;

    int childTop;
    int childLeft;

……

final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();

………

childTop += lp.topMargin;
        setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                childWidth, childHeight);
        childTop += childHeight + lp.bottomMargin+ getNextLocationOffset(child);

        i += getChildrenSkipCount(child, i);
    }
}

在竖直方向内setChildFrame中去调用子view的layout

3. private void setChildFrame(View child, int left, int top, int width, int height){
    child.layout(left, top, left + width, top + height);
}

setChildFrame参数宽和高是子View的测量宽高,调用子View.layout,里面setFrame将layout的四个顶点坐标赋值给:

mLeft = left;

mTop = top;

mRight = left + width;

mHeight = top + height;  // width height是该view的测量宽高

getWidth = mRight – mLeft = with;

getHeight = mHeight – mTop = height;

所以LinearLayout中子View最终宽和高等于测量宽高,除非特殊情况下,在setFrame之前改变layout的四个参数值,即重写layout方法

总结Layout过程:

父容器在layout方法中通过setFrame方法确定自己的位置,然后调用自己的onLayout,在onLayout中调用子View的layout方法,子View在layout中确定自己的位置,然后继续向下一层一层的传递,直到完成布局过程

Draw流程:

View的方法:

1. @CallSuper
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 severaldrawing steps which must be executed
     * in the appropriate order:
     *
     *     1. Draw the background
     *      2. If necessary, save the canvas' layersto prepare for fading
     *      3. Draw view's content
     *      4. Draw children
     *      5. If necessary, draw the fading edgesand restore layers
     *      6. Draw decorations (scrollbars forinstance)
     */

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

        drawAutofilledHighlight(canvas);

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

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

        // Step 7, draw the default focushighlight
        drawDefaultFocusHighlight(canvas);

        if (debugDraw()) {
            debugDrawFocus(canvas);
        }

        // we're done...
        return;
    }

    /*
     * Here we do the full fledgedroutine...
     * (this is an uncommon case wherespeed matters less,
     * this is why we repeat some of thetests 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 ScrollabilityCachescrollabilityCache = mScrollCache;
    final float fadeHeight =scrollabilityCache.fadingEdgeLength;
    int length = (int) fadeHeight;

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

    // also clip horizontal fades ifnecessary
    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);

    drawAutofilledHighlight(canvas);

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

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

    if (debugDraw()) {
        debugDrawFocus(canvas);
    }
}

绘制分为六个过程:绘制背景,如果必要,保存layer,绘制自己,绘制子元素,绘制淡出效果恢复layers,绘制装饰(如前景和scrollbar)

2. protected void onDraw(Canvas canvas) {
}

绘制自己的工作交给子类去实现

3. protected void dispatchDraw(Canvas canvas) {

}

如果是单纯的一个view,没有子类,不需要绘制子元素,但是如果是ViewGroup的话需要覆盖该方法去绘制子元素。

ViewGroup的方法:

1. @Override
protected void dispatchDraw(Canvascanvas) {
    boolean usingRenderNodeProperties =canvas.isRecordingFor(mRenderNode);
    final int childrenCount = mChildrenCount;
    final View[] children = mChildren;
    int flags = mGroupFlags;

………

if ((transientChild.mViewFlags &VISIBILITY_MASK) == VISIBLE ||
        transientChild.getAnimation() != null){
    more |= drawChild(canvas, transientChild, drawingTime);
}

……

}

因为是viewGroup所以要绘制子View,具体的内容绘制交给viewgroup的子类

2. protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
    return child.draw(canvas, this, drawingTime);
}

LinearLayout方法:

1. @Override
protected void onDraw(Canvas canvas){
    if (mDivider == null) {
        return;
    }

    if (mOrientation == VERTICAL) {
        drawDividersVertical(canvas);
    } else {
        drawDividersHorizontal(canvas);
    }
}

根据方向分为两种情况,竖直方向和水平方向。

2. void drawDividersVertical(Canvas canvas) {
    final int count =getVirtualChildCount();
    for (int i = 0; i < count; i++) {
        final View child =getVirtualChildAt(i);
        if (child != null &&child.getVisibility() != GONE) {
            if (hasDividerBeforeChildAt(i)){
                final LayoutParams lp =(LayoutParams) child.getLayoutParams();
                final int top =child.getTop() - lp.topMargin - mDividerHeight;
                drawHorizontalDivider(canvas,top);
            }
        }
    }

    if (hasDividerBeforeChildAt(count)) {
        final View child =getLastNonGoneChild();
        int bottom = 0;
        if (child == null) {
            bottom = getHeight() -getPaddingBottom() - mDividerHeight;
        } else {
            final LayoutParams lp =(LayoutParams) child.getLayoutParams();
            bottom = child.getBottom() +lp.bottomMargin;
        }
        drawHorizontalDivider(canvas, bottom);
    }
}

3. void drawHorizontalDivider(Canvas canvas, int top) {
    mDivider.setBounds(getPaddingLeft() +mDividerPadding, top,
            getWidth() -getPaddingRight() - mDividerPadding, top + mDividerHeight);
    mDivider.draw(canvas);
}

LinearLayout的onDraw主要是绘制分割线

总结draw过程:

绘制主要的过程是绘制背景,onDraw自己,dispatchDraw子元素,绘制装饰。

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页