學(xué)習(xí)筆記| (四)View的工作原理

知識體系:

  • ViewRoot和DecorView
  • MeasureSpec
  • View的工作流程
    • measure過程
    • layout過程
    • draw過程
  • 自定義view
    • 分類
    • 須知
    • 事例
    • 思想

一、ViewRoot和DecorView

1.ViewRoot對應(yīng)ViewRootImpl纬黎,它是WindowManage和DocorView直接的紐帶唱较。Activity創(chuàng)建完了之后惧磺,通過setContentView添加布局迷扇,鍵DecorView添加到Window中夏醉,同時創(chuàng)建ViewRootImpl,將DecorView和ViewRootImpl關(guān)聯(lián)起來。

理解:為什么要將DecorView和ViewRootImpl關(guān)聯(lián)欠母?
可以將DecorView理解為View的一個容器锚贱,這時候還沒有展示出來仔戈,要通過ViewRoot才能將它展示出來;

2.View創(chuàng)建有三個流程:measure惋鸥、layout杂穷、draw,這三個流程都需要ViewRoot來完成


performTraversals工作流程.png

以performxx開頭的方法都是在RootViewImpl中被調(diào)用的,
①performMeasure:
在RootViewImpl中調(diào)用performMeasure卦绣,在performMeasure中會調(diào)用View的measure方法耐量,在View的measure中又會調(diào)用View的onMeasure
②performLayout:
③performDraw:
在RootViewImpl中調(diào)用performDraw滤港,在performDraw中會調(diào)用RootViewImpl的draw方法廊蜒,在View的measure中又會調(diào)用View的onMeasure

從上面這個圖中可以看出溅漾,View的繪制流程是從ViewGroup的performTraversals開始的山叮,依次會調(diào)用performMeasure、performLayout添履、performDraw屁倔,這三個方法完成了頂級View的三個繪制流程。在performMeasure中又會調(diào)用measure,在measure中又會調(diào)用onMeasure暮胧,在onMeasure中則會對所有的子View進行measure過程锐借,這樣就完成了依次measure過程问麸,將繪制過程從父容器傳遞到了子View中,接著在子view會依次執(zhí)行父容器的measure過程钞翔;performLayout過程和上面是一樣的严卖;performDraw有一點點不一樣,只是在draw中通過dispatchDraw傳到子view的布轿,不過并沒有本質(zhì)的區(qū)別

measure之后可以得到View測量后的寬和高哮笆,幾乎所有情況下,這個就是最終的寬和高了(有特殊情況)
通過getMeasuredWidth()/getMeasuredHeight()獲得汰扭;

layout可以確定四個頂點的位置稠肘,通過getTop/getRight/getBottom/getLeft可以獲取四個頂點的坐標(biāo);通過getWidth/getHeight可以獲得最終的寬和高萝毛;

draw是將View顯示在屏幕上的

為什么叫setContentView启具,而不叫setView;

因為DecorView作為頂級View珊泳,他會包含一個LinearLayout,一般由兩部分組成:頂部標(biāo)題欄和底部內(nèi)容欄(id為content)拷沸,而通過setContentView添加布局是添加到內(nèi)容欄中的色查,所以叫setContentView,要獲取內(nèi)容了的view: content = findViewById(R.android.id.content)撞芍,要獲取它的子元素:content.getChilAt(0);

二秧了、MeasureSpec

  1. MeasureSpec可以理解為一種測量規(guī)范,它會影響View的測量序无,而它的創(chuàng)建又會受父容器的影響验毡,獲得View的layoutparams之后,根據(jù)父容器的一些規(guī)則帝嗡,轉(zhuǎn)換為MeasureSpec晶通。

2.MeasureSpec是int型,32位哟玷,高2位代表SpecMode狮辽,低30位表示SpecSize;這里所說的MeasureSpec都是指它的值;

3.SpecMode有三類:

  • UNSPECIFIED:表示父容器不限制子View的大小巢寡,通常用在系統(tǒng)中喉脖,表示一種測量狀態(tài)抑月;
  • EXACTLY:表示父容器知道子View的具體大小,對應(yīng)于LayoutParams的match_parent或者是一個準(zhǔn)確的數(shù)值题诵;
  • AT_MOST:表示父容器會指定一個大小洁仗,子View的大小有它自己決定,只要不超過指定的大小就行了京痢,對應(yīng)wrap_content;

4.MeasureSpec和LayoutParams的關(guān)系:

  • 對于頂級View(DecorView)來說:
    MeasureSpec = 窗口尺寸 + View的LayoutParams

  • 對于普通View來說:
    MeasureSpec = 父容器的規(guī)則 + View的LayoutParams

5.對4中的源碼分析:

  • 頂級View創(chuàng)建MeasureSpec:
在ViewRootImpl中篷店,先看看 performTraversals():

private void performTraversals(){
    ......

    // Ask host how big it wants to be
    windowSizeMayChange |= measureHierarchy(host, lp, res,
          desiredWindowWidth, desiredWindowHeight);
    ......

}

private boolean measureHierarchy(){
    ....
    if (!goodMeasure) {
        //desiredWindowWidth是屏幕的尺寸
        childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
        childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);

        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
            windowSizeMayChange = true;
        }
    }
    ....

}

/**創(chuàng)建MeasureSpec
 * @param  int windowSize    屏幕尺寸
 * @param  int rootDimension LayoutParams中的寬和高
 */
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {

    case ViewGroup.LayoutParams.MATCH_PARENT:
        // 精確模式祭椰,大小是窗口的大小
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        // 最大模式,大小不能超過specsize
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        // layoutparams給的固定值
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}
  • 普通View創(chuàng)建MeasureSpec:
對于普通View方淤,View的measure過程是從ViewGroup中傳遞的蹄殃,先看看**ViewGroup**的measureChildWithMargins:
protected void measureChildWithMargins(){
    
    //獲取子View的LayoutParams
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

    //創(chuàng)建子View的MeasureSpec
    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的MeasureSpec
 * @param spec : 父容器的MeasureSpec
 * @param padding : 間距
 * @param childDimension :子View的LayoutParams
 * @return
 */
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);

    //大小要減去間距
    int size = Math.max(0, specSize - padding);

    int resultSize = 0;
    int resultMode = 0;

    switch (specMode) {
    // Parent has imposed an exact size on us
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size. So be it.
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size. It can't be
            // bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    // Parent has imposed a maximum size on us
    case MeasureSpec.AT_MOST:
        if (childDimension >= 0) {
            // Child wants a specific size... so be it
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size, but our size is not fixed.
            // Constrain child to not be bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size. It can't be
            // bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;

    // Parent asked to see how big we want to be
    case MeasureSpec.UNSPECIFIED:
        if (childDimension >= 0) {
            // Child wants a specific size... let him have it
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size... find out how big it should
            // be
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size.... find out how
            // big it should be
            resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
            resultMode = MeasureSpec.UNSPECIFIED;
        }
        break;
    }
    //noinspection ResourceType
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

可以將上面的規(guī)則用表格分析:


普通view measurespec創(chuàng)建規(guī)則.png

從這個表格可以很清晰的知道讳苦,普通View的MeasureSpec是由父容器的MeasureSpec和view的LayoutParams共同決定的吩谦;
①當(dāng)子View的大小確定的時候式廷,無論父容器是什么SpecMode,子View都顯示的是精確模式滑废,大小為設(shè)置的大腥涑谩;
②當(dāng)子View設(shè)置的是matchparent時逛绵,子view以父容器的mode為準(zhǔn)倔韭,大小為父容器的剩余空間大小/不超過父容器的剩余空間;
③當(dāng)子View設(shè)置為wrap_content時,mode以子view為準(zhǔn)寿酌,前面分析過胰苏,wrap_content對應(yīng)的是AT_MOST,所以子View的mode為AT_MOST,大小不超過父容器的剩余空間醇疼;
④后面兩種情況都不考慮UNSPECIFIED硕并,因為這種模式一般用于系統(tǒng)內(nèi)部多次調(diào)用measure的情況倔毙,不用考慮。

三卵蛉、View的工作流程

  • measure過程
    1.view的measure過程:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
    ....
    onMeasure(widthMeasureSpec, heightMeasureSpec);
    ....

 }

view的measure方法是final類型么库,子類不能重寫诉儒,所以在他的內(nèi)部調(diào)用了onMeasure方法;

/**
 * 設(shè)置測量的數(shù)值
 * @param widthMeasureSpec  
 * @param heightMeasureSpec 
 */
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

/**
 * 獲取默認(rèn)的大小
 * @param size : 系統(tǒng)建議的大小
 * @param measureSpec : 獲取的MeasureSpec
 * @return
 */
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;
}

/**
 * 獲取建議的最小寬度
 * @return
 */
protected int getSuggestedMinimumWidth() {
    //如果沒有背景胁澳,則為mMinWidth米者,值為android:minWidth設(shè)置的值蔓搞,沒有設(shè)置則為0随橘;
    //如果設(shè)置了背景,則為android:minWidth和背景最小寬度的最大值
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

在上面代碼中調(diào)用了mBackground.getMinimumWidth():
//返回的是drawable的原始寬度蒲祈,前提是它有原始寬度才行梆掸,比如ShapeDrawable無原始寬度牙言,BitmapDrawable有原始寬度咱枉;
public int getMinimumWidth() {
    final int intrinsicWidth = getIntrinsicWidth();
    return intrinsicWidth > 0 ? intrinsicWidth : 0;
}

總結(jié):在測量view大小的時候徒恋,exactly和at_most這兩種情況下返回的都是測量的大小欢伏,這里說的是測量的大小而不是最終大小硝拧,是因為最終大小是在layout中確定的;其他情況下返回的是系統(tǒng)建議的最小大小匠璧,如果沒有設(shè)置背景夷恍,則返回的是android:minwidth的值媳维,沒有設(shè)置的話就是0,;如果設(shè)置了背景指黎,返回的是android:minwidth和backgroud最小寬度的最大值醋安,而當(dāng)drawable為背景墓毒,并且有原始大小的時候才能得到background的最小值;

另外在getDefaultSize()中柠辞,我們發(fā)現(xiàn)叭首,當(dāng)為exactly和atmost的時候踪栋,返回的大小都是specsize夷都,當(dāng)設(shè)置layout為wrap_content的時候,根據(jù)表4.1中我們可以看到厢破,它的大小為parentSize,也就是說為parent的剩余寬度笆焰,這時候和設(shè)置為match_parent并沒有區(qū)別嚷掠。所以在自定義View直接繼承View的時候荞驴,要重寫onMeasure,并且規(guī)定當(dāng)specmode為at_most的時候,給它一個固定寬度值/高度值霹娄;

2.ViewGtoup的measure過程:
因為ViewGroup是一個抽象類犬耻,它的具體實現(xiàn)了LinearLayout等的布局都不一樣执泰,所以不用在ViewGroup中實現(xiàn)onMeasure术吝。

//測量子View的大小

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) {
    //先獲取子View的LayoutParams
    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);
}

getChildMeasureSpec在上面的代碼中已經(jīng)具體分析過了排苍。

下面來看一下它的一個具體實現(xiàn)類LinearLayout:

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

先判斷是水平排列還是垂直排列

void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
    ....

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

            nonSkippedChildCount++;
            if (hasDividerBeforeChildAt(i)) {
                mTotalLength += mDividerHeight;
            }

            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            totalWeight += lp.weight;

            final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
            if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
                // Optimization: don't bother measuring children who are only
                // laid out using excess space. These views will get measured
                // later if we have space to distribute.
                final int totalLength = mTotalLength;
                mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
                skippedMeasure = true;
            } else {
                if (useExcessSpace) {
                    // The heightMode is either UNSPECIFIED or AT_MOST, and
                    // this child is only laid out using excess space. Measure
                    // using WRAP_CONTENT so that we can find out the view's
                    // optimal height. We'll restore the original height of 0
                    // after measurement.
                    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).
                final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
                measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
                        heightMeasureSpec, usedHeight);

                final int childHeight = child.getMeasuredHeight();
                if (useExcessSpace) {
                    // Restore the original height and record how much space
                    // we've allocated to excess-only children so that we can
                    // match the behavior of EXACTLY measurement.
                    lp.height = 0;
                    consumedExcessSpace += childHeight;
                }

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

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

        ....

}

在測量子view高度的時候则果,用mTotalLength記錄每一個view的高度和寬度還有間距后西壮,通過setMeasuredDimension測量LinearLayout的大小,如果它的布局采用的是match_parent做修,則大小為測量的大小,如果為wrap_content蔗坯,則大小為子view的高度和燎含,但是仍然不能超過parent的剩余高度屏箍。

3.在Activity中獲取view的寬和高:
①通過onWindowFocusChanged:(會調(diào)用多次)

public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);

    if (hasFocus){
       int width =  mTitleText.getMeasuredWidth();
       int height =  mTitleText.getMeasuredHeight();
       Log.e(TAG,"onWindowFocusChanged:"+width+","+height);
    }
}

②通過view.post:

mTitleText.post(new Runnable() {
    @Override
    public void run() {
        int width =  mTitleText.getMeasuredWidth();
        int height =  mTitleText.getMeasuredHeight();
        Log.e(TAG,"onStart-->post:"+width+","+height);
    }
});

③通過ViewTreeObserver(會調(diào)用多次):

ViewTreeObserver tree = mTitleText.getViewTreeObserver();
tree.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        int width =  mTitleText.getMeasuredWidth();
        int height =  mTitleText.getMeasuredHeight();
        Log.e(TAG,"onStart-->ViewTreeObserver:"+width+","+height);
    }
});

④通過measure:
這種方式比較復(fù)雜赴魁,而且要區(qū)分不同的情況:

當(dāng)為match_parent的時候,沒法獲取榄棵,因為這時候要知道父容器的剩余大小郎嫁,無法獲取
//具體數(shù)值
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.EXACTLY);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
mTitleText.measure(widthMeasureSpec,heightMeasureSpec);
//wrap_content
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
mTitleText.measure(widthMeasureSpec,heightMeasureSpec);
  • layout過程:

layout過程是ViewGroup用來確定子View的位置泽铛,當(dāng)ViewGroup的位置確定了之后,通過for循環(huán)遍歷子元素杠茬,調(diào)用子元素的layout()弛随,在layout()中又會調(diào)用onLayout()確定子元素的位置

在View中

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;

    //通過setFrame設(shè)定四個頂點的位置-->這樣view的位置就確定了
    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);
    }
}

因為onLayout的實現(xiàn)和具體的布局有關(guān)栓票,所以在它的子類中實現(xiàn)就行了:

//它的實現(xiàn)和具體的布局有關(guān)
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}

在ViewGroup中:

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

            //確定子view的位置
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

            i += getChildrenSkipCount(child, i);
        }
    }
}
private void setChildFrame(View child, int left, int top, int width, int height) {
        //調(diào)用子view的layout
        child.layout(left, top, left + width, top + height);
    }

測量寬高和最終寬高有什么區(qū)別:
實質(zhì)上一般情況下,他們的值是相等的坠狡,只是在measure過程中獲得是測量寬高遂跟,在layout過程中獲得是最終的寬和高,時間上會先獲得測量的寬高凯亮,而在layout過程中触幼,可能會出現(xiàn)這樣的情況,導(dǎo)致他們的值不相等:

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right+100, bottom+100);
}

這時候堂鲤,最終寬高會始終比測量寬高大100

  • draw過程

是將view繪制在屏幕上媒峡,步驟:
①畫背景:mBackGround.draw()-->canvas
②畫自己:onDraw
③畫子元素:dispatchDraw()
④畫裝飾:onDrawScrollBar()

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

        drawAutofilledHighlight(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);

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

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

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

view還有一個特殊的方法:

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

它的意思是半哟,當(dāng)不需要繪制任何內(nèi)容签餐,設(shè)置為true之后系統(tǒng)會進行相應(yīng)的優(yōu)化,這個方法在view中是默認(rèn)關(guān)閉的戒良,在viewgroup中是默認(rèn)開啟的冠摄;它在實際開發(fā)中的意義是:當(dāng)自定義view繼承自ViewGroup并且本身不具備繪制功能是,開啟它河泳,系統(tǒng)將會進行相應(yīng)的優(yōu)化拆挥;當(dāng)ViewGroup調(diào)用onDraw繪制內(nèi)容時,要**顯示的關(guān)閉WILL_NOT_DRAW **

四惰瓜、自定義view

1.分類:

  • 繼承View食拜,重新onDraw:
    可以實現(xiàn)不規(guī)則的效果负甸,但是得重寫OnDraw,需要自己支持padding和wrap_content呻待,有必要時要自定義屬性

  • 繼承ViewGroup蚕捉,重寫onLayout

  • 繼承特定的View(如TextView)

  • 繼承特定的ViewGroup(如LinearLayout)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市秘通,隨后出現(xiàn)的幾起案子敛熬,更是在濱河造成了極大的恐慌应民,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件繁仁,死亡現(xiàn)場離奇詭異归园,居然都是意外死亡蔓倍,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門默勾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來母剥,“玉大人形导,你說我怎么就攤上這事§帕ィ” “怎么了阎曹?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長斟湃。 經(jīng)常有香客問我檐薯,道長,這世上最難降的妖魔是什么墓猎? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任陶衅,我火速辦了婚禮直晨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘罩句。我一直安慰自己敛摘,他們只是感情好兄淫,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著慨丐,像睡著了一般泄私。 火紅的嫁衣襯著肌膚如雪晌端。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天蓬痒,我揣著相機與錄音漆羔,去河邊找鬼。 笑死粹断,一個胖子當(dāng)著我的面吹牛瓶埋,可吹牛的內(nèi)容都是我干的诊沪。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼晕粪,長吁一口氣:“原來是場噩夢啊……” “哼巫湘!你這毒婦竟也來了昏鹃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤阅嘶,失蹤者是張志新(化名)和其女友劉穎讯柔,沒想到半個月后护昧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡极祸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年遥金,在試婚紗的時候發(fā)現(xiàn)自己被綠了稿械。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冲粤。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖厢呵,靈堂內(nèi)的尸體忽然破棺而出襟铭,到底是詐尸還是另有隱情,我是刑警寧澤赐劣,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布哩都,位于F島的核電站漠嵌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏碉考。R本人自食惡果不足惜挺身,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一章钾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧惨撇,春花似錦府寒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至碰煌,卻和暖如春绅作,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背堕扶。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留典尾,地道東北人钾埂。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓褥紫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親髓考。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容