View的工作原理————读书笔记

来源:互联网 发布:极客学院源码下载 编辑:程序博客网 时间:2024/05/22 02:03

View的工作原理

ViewRoot和DecorView

1.ViewRoot对应于ViewRootImpl类,它是连接WindowManager和DecorView的纽带,View的三大流程均通过ViewRoot来完成。
2.ActivityThread中,Activity创建完成后,会将DecorView添加到Window中,同时创建ViewRootImpl对象,并建立两者的关联。
3.View的绘制流程从ViewRoot的performTraversals方法开始,经过measure、layout和draw三大流程。
4.DecorView其实是一个FrameLayout,View层的事件都先经过DecorView,然后才传递给我们的View

理解MeasureSpec

MeasureSpec

MeasureSpec代表一个32位int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,而SpecSize是指在某种测量模式下的规格大小。
1.a) <<左移,>>右移,所以源码中EXACTLY = 1 << MODE_SHIFT就相当于010000……..0000(30个0),其他可类推; b) &运算 0011 & 1100 = 0000,按位与;|或运算 0011 | 1100 = 1111,按位或; c)~取反运算,~0000 = 1111,所以(size & ~MODE_MASK) | (mode & MODE_MASK) 就好理解了;
2.三类specMode:UNSPECIFIED,用于内部系统,无视;EXACTLY,精确大小或match_parent;AT_MOST,warp_content;

MeasureSpec和LayoutParams的对应关系

对于DecorView,其MeasureSpec由窗口大小和自身的LayoutParams共同决定;对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽/高。

子View的LayoutParams和父容器的MeasureSpec关系归纳:
a. 子View为精确宽高,无论父容器的MeasureSpec,子View的MeasureSpec都为精确值且遵循LayoutParams中的值。
b. 子View为match_parent时,如果父容器是精确模式,则子View也为精确模式且为父容器的剩余空间大小;如果父容器是wrap_content,则子View也是wrap_content且不会超过父容器的剩余空间。
c. 子View为wrap_content时,无论父View是精确还是wrap_content,子View的模式总是wrap_content,且不会超过父容器的剩余空间。

View的工作流程

View的工作流程:measure(确定View的测量宽/高)layout(确定View的最终宽/高和四个顶点的位置)draw(将View绘制到屏幕上)

measure过程

1.View的measure过程
View#onMeasure

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

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

从getDefaultSize方法实现来看,View的宽/高由specSize决定,所以可以得出以下结论:直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于使用match_parent。
2.ViewGroup的measure过程
ViewGroup#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);        }    }

ViewGroup#measureChild

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是一个抽象类,其测量过程的onMeasure方法需要各个子类去具体实现,比如LinearLoyout等具体实现自己查源码
注意:一个比较好的习惯是在onLayout方法中去获取View的测量宽高或最终宽高。
3.如何在Activity初始化时获取View的宽高(View的measure过程与Activity的生命周期不同步):
a. Activity或者View的onWindowFocusChanged方法(注意该方法会在Activity Pause和resume时被多次调用)。onWindowFocusChanged方法含义:View已经初始化完毕,宽/高已经准备好了,这个时候获取宽/高是OK的。
b. view.post(new Runnable( {@Overiddepublic void run(){})});在run方法中获取。通过post将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View已经初始化完成。
c. ViewTreeObserver中的onGlobalLayoutListener接口。当View树的状态发生改变或者View树内部的View的可见性发生改变时,onGlobalLayout方法会被回调,因此这是获取View的宽/高一个好时机。
d. view.measure手动获取: match_parent:无法测量; 精确值:int wMeasureSpec = MeasureSpec.makeMeasureSpec(exactlyValue, MeasureSpec.EXACTLY); wrap_content:int wMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30) - 1, MeasureSpec.AT_MOST); PS一下,还不懂(1<<30) - 1的,再多一句嘴,其实就是1111….11111(30个)

layout过程

Layout的作用是ViewGroup用来确定子元素的位置。
layout方法确定View本身的位置,onLayout方法则会确定所有子元素的位置。
View#layout

/** * Assign a size and position to a view and all of its * descendants */@SuppressWarnings({"unchecked"})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;}

大致流程:首先会通过setFrame方法来设定View的四个顶点的位置,View的四个顶点一旦确定,View在父容器中的位置也就确定了;接着会调用onLayout方法,这个方法的用途是父容器确定子元素的位置,和onMeasure方法类似,onLayout的具体实现同样和具体的布局有关,所以View和ViewGroup均没有真正实现onLayout方法。具体实现可参看LinearLayout的onLayout源码。

draw过程

绘制背景(background.draw(canvas)),绘制自己(onDraw()),绘制chidren(dispatchDraw),绘制装饰(onDrawScrollBars)
注:
1.View绘制过程的传递是通过dispatchDraw来实现的,dispatchDraw会遍历调用所有的子元素的draw方法,如此draw事件就一层层地传递下去。
2.setWillNotDraw方法用于在一个View不需要绘制时的优化(设置为true时)。当然明确要onDraw来绘制内容时,设置为false。

自定义View

自定义View的分类

1.继承View重写onDraw方法
2.继承ViewGroup派生特殊的Layout
3.继承特定的View
4.继承特定的ViewGroup

自定义View须知

1.直接继承View或ViewGroup的需要自己处理wrap_content。
2.View要在onDraw方法中要处理padding,而ViewGroup要在onMeasure和onLayout中处理padding和margin。
3.View中的post方法可以取代handler。
4.在View的onDetachedFromWindow中停止动画,线程或回收其他资源。(防止内存泄漏)
5.View带有滑动嵌套情形时,做好滑动冲突处理

事例代码请见书中。我在下面贴一下LinearLayout的相关源码。可以参考学习。
LinearLayout#onMeasure

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {    if (mOrientation == VERTICAL) {        measureVertical(widthMeasureSpec, heightMeasureSpec);    } else {        measureHorizontal(widthMeasureSpec, heightMeasureSpec);    }}

LinearLayout#measureVertical

/** * Measures the children when the orientation of this LinearLayout is set */void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {    mTotalLength = 0;    int maxWidth = 0;    int childState = 0;    int alternativeMaxWidth = 0;    int weightedMaxWidth = 0;    boolean allFillParent = true;    float totalWeight = 0;    final int count = getVirtualChildCount();    final int widthMode = MeasureSpec.getMode(widthMeasureSpec);    final int heightMode = MeasureSpec.getMode(heightMeasureSpec);    boolean matchWidth = false;    boolean skippedMeasure = false;    final int baselineChildIndex = mBaselineAlignedChildIndex;            final boolean useLargestChild = mUseLargestChild;    int largestChildHeight = Integer.MIN_VALUE;    // See how tall everyone is. Also remember max width.    for (int i = 0; i < count; ++i) {        final View child = getVirtualChildAt(i);        if (child == null) {            mTotalLength += measureNullChild(i);            continue;        }        if (child.getVisibility() == View.GONE) {           i += getChildrenSkipCount(child, i);           continue;        }        if (hasDividerBeforeChildAt(i)) {            mTotalLength += mDividerHeight;        }        LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();        totalWeight += lp.weight;        if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {            // Optimization: don't bother measuring children who are going to use            // leftover space. These views will get measured again down below if            // there is any leftover space.            final int totalLength = mTotalLength;            mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);            skippedMeasure = true;        } else {            int oldHeight = Integer.MIN_VALUE;            if (lp.height == 0 && lp.weight > 0) {                // heightMode is either UNSPECIFIED or AT_MOST, and this                // child wanted to stretch to fill available space.                // Translate that to WRAP_CONTENT so that it does not end up                // with a height of 0                oldHeight = 0;                lp.height = LayoutParams.WRAP_CONTENT;            }            // Determine how big this child would like to be. If this or            // previous children have given a weight, then we allow it to            // use all available space (and we will shrink things later            // if needed).            measureChildBeforeLayout(                   child, i, widthMeasureSpec, 0, heightMeasureSpec,                   totalWeight == 0 ? mTotalLength : 0);            if (oldHeight != Integer.MIN_VALUE) {               lp.height = oldHeight;            }            final int childHeight = child.getMeasuredHeight();            final int totalLength = mTotalLength;            mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +                   lp.bottomMargin + getNextLocationOffset(child));            if (useLargestChild) {                largestChildHeight = Math.max(childHeight, largestChildHeight);            }        }        /**         * If applicable, compute the additional offset to the child's baseline         * we'll need later when asked {@link #getBaseline}.         */        if ((baselineChildIndex >= 0) && (baselineChildIndex == i + 1)) {           mBaselineChildTop = mTotalLength;        }        // if we are trying to use a child index for our baseline, the above        // book keeping only works if there are no children above it with        // weight.  fail fast to aid the developer.        if (i < baselineChildIndex && lp.weight > 0) {            throw new RuntimeException("A child of LinearLayout with index "                    + "less than mBaselineAlignedChildIndex has weight > 0, which "                    + "won't work.  Either remove the weight, or don't set "                    + "mBaselineAlignedChildIndex.");        }        boolean matchWidthLocally = false;        if (widthMode != MeasureSpec.EXACTLY && lp.width == LayoutParams.MATCH_PARENT) {            // The width of the linear layout will scale, and at least one            // child said it wanted to match our width. Set a flag            // indicating that we need to remeasure at least that view when            // we know our width.            matchWidth = true;            matchWidthLocally = true;        }        final int margin = lp.leftMargin + lp.rightMargin;        final int measuredWidth = child.getMeasuredWidth() + margin;        maxWidth = Math.max(maxWidth, measuredWidth);        childState = combineMeasuredStates(childState, child.getMeasuredState());        allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;        if (lp.weight > 0) {            /*             * Widths of weighted Views are bogus if we end up             * remeasuring, so keep them separate.             */            weightedMaxWidth = Math.max(weightedMaxWidth,                    matchWidthLocally ? margin : measuredWidth);        } else {            alternativeMaxWidth = Math.max(alternativeMaxWidth,                    matchWidthLocally ? margin : measuredWidth);        }        i += getChildrenSkipCount(child, i);    }    if (mTotalLength > 0 && hasDividerBeforeChildAt(count)) {        mTotalLength += mDividerHeight;    }    if (useLargestChild &&            (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED)) {        mTotalLength = 0;        for (int i = 0; i < count; ++i) {            final View child = getVirtualChildAt(i);            if (child == null) {                mTotalLength += measureNullChild(i);                continue;            }            if (child.getVisibility() == GONE) {                i += getChildrenSkipCount(child, i);                continue;            }            final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)                    child.getLayoutParams();            // Account for negative margins            final int totalLength = mTotalLength;            mTotalLength = Math.max(totalLength, totalLength + largestChildHeight +                    lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));        }    }    // Add in our padding    mTotalLength += mPaddingTop + mPaddingBottom;    int heightSize = mTotalLength;    // Check against our minimum height    heightSize = Math.max(heightSize, getSuggestedMinimumHeight());    // Reconcile our calculated size with the heightMeasureSpec    int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);    heightSize = heightSizeAndState & MEASURED_SIZE_MASK;    // Either expand children with weight to take up available space or    // shrink them if they extend beyond our current bounds. If we skipped    // measurement on any children, we need to measure them now.    int delta = heightSize - mTotalLength;    if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {        float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;        mTotalLength = 0;        for (int i = 0; i < count; ++i) {            final View child = getVirtualChildAt(i);            if (child.getVisibility() == View.GONE) {                continue;            }            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();            float childExtra = lp.weight;            if (childExtra > 0) {                // Child said it could absorb extra space -- give him his share                int share = (int) (childExtra * delta / weightSum);                weightSum -= childExtra;                delta -= share;                final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,                        mPaddingLeft + mPaddingRight +                                lp.leftMargin + lp.rightMargin, lp.width);                // TODO: Use a field like lp.isMeasured to figure out if this                // child has been previously measured                if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {                    // child was measured once already above...                    // base new measurement on stored values                    int childHeight = child.getMeasuredHeight() + share;                    if (childHeight < 0) {                        childHeight = 0;                    }                    child.measure(childWidthMeasureSpec,                            MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));                } else {                    // child was skipped in the loop above.                    // Measure for this first time here                          child.measure(childWidthMeasureSpec,                            MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,                                    MeasureSpec.EXACTLY));                }                // Child may now not fit in vertical dimension.                childState = combineMeasuredStates(childState, child.getMeasuredState()                        & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));            }            final int margin =  lp.leftMargin + lp.rightMargin;            final int measuredWidth = child.getMeasuredWidth() + margin;            maxWidth = Math.max(maxWidth, measuredWidth);            boolean matchWidthLocally = widthMode != MeasureSpec.EXACTLY &&                    lp.width == LayoutParams.MATCH_PARENT;            alternativeMaxWidth = Math.max(alternativeMaxWidth,                    matchWidthLocally ? margin : measuredWidth);            allFillParent = allFillParent && lp.width == LayoutParams.MATCH_PARENT;            final int totalLength = mTotalLength;            mTotalLength = Math.max(totalLength, totalLength + child.getMeasuredHeight() +                    lp.topMargin + lp.bottomMargin + getNextLocationOffset(child));        }        // Add in our padding        mTotalLength += mPaddingTop + mPaddingBottom;        // TODO: Should we recompute the heightSpec based on the new total length?    } else {        alternativeMaxWidth = Math.max(alternativeMaxWidth,                                       weightedMaxWidth);        // We have no limit, so make all weighted views as tall as the largest child.        // Children will have already been measured once.        if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {            for (int i = 0; i < count; i++) {                final View child = getVirtualChildAt(i);                if (child == null || child.getVisibility() == View.GONE) {                    continue;                }                final LinearLayout.LayoutParams lp =                        (LinearLayout.LayoutParams) child.getLayoutParams();                float childExtra = lp.weight;                if (childExtra > 0) {                    child.measure(                            MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),                                    MeasureSpec.EXACTLY),                            MeasureSpec.makeMeasureSpec(largestChildHeight,                                    MeasureSpec.EXACTLY));                }            }        }    }    if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {        maxWidth = alternativeMaxWidth;    }    maxWidth += mPaddingLeft + mPaddingRight;    // Check against our minimum width    maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),            heightSizeAndState);    if (matchWidth) {        forceUniformWidth(count, heightMeasureSpec);    }}

LinearLayout#onLayout

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

LinearLayout#layoutVertical

/** * Position the children during a layout pass if the orientation of this * LinearLayout is set to {@link #VERTICAL}.  */void layoutVertical(int left, int top, int right, int bottom) {    final int paddingLeft = mPaddingLeft;    int childTop;    int childLeft;    // Where right end of child should go    final int width = right - left;    int childRight = width - mPaddingRight;    // Space available for child    int childSpace = width - paddingLeft - mPaddingRight;    final int count = getVirtualChildCount();    final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;    final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;    switch (majorGravity) {       case Gravity.BOTTOM:           // mTotalLength contains the padding already           childTop = mPaddingTop + bottom - top - mTotalLength;           break;           // mTotalLength contains the padding already       case Gravity.CENTER_VERTICAL:           childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;           break;       case Gravity.TOP:       default:           childTop = mPaddingTop;           break;    }    for (int i = 0; i < count; i++) {        final View child = getVirtualChildAt(i);        if (child == null) {            childTop += measureNullChild(i);        } else if (child.getVisibility() != GONE) {            final int childWidth = child.getMeasuredWidth();            final int childHeight = child.getMeasuredHeight();            final LinearLayout.LayoutParams lp =                    (LinearLayout.LayoutParams) child.getLayoutParams();            int gravity = lp.gravity;            if (gravity < 0) {                gravity = minorGravity;            }            final int layoutDirection = getLayoutDirection();            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {                case Gravity.CENTER_HORIZONTAL:                    childLeft = paddingLeft + ((childSpace - childWidth) / 2)                            + lp.leftMargin - lp.rightMargin;                    break;                case Gravity.RIGHT:                    childLeft = childRight - childWidth - lp.rightMargin;                    break;                case Gravity.LEFT:                default:                    childLeft = paddingLeft + lp.leftMargin;                    break;            }            if (hasDividerBeforeChildAt(i)) {                childTop += mDividerHeight;            }            childTop += lp.topMargin;            setChildFrame(child, childLeft, childTop + getLocationOffset(child),                    childWidth, childHeight);            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);            i += getChildrenSkipCount(child, i);        }    }}

LinearLayout#onDraw

@Overrideprotected void onDraw(Canvas canvas) {    if (mDivider == null) {        return;    }    if (mOrientation == VERTICAL) {        drawDividersVertical(canvas);    } else {        drawDividersHorizontal(canvas);    }}

LinearLayout#drawDividersVertical

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);    }}
0 0
原创粉丝点击