一、從setContentView(R.layout.activity_main);入手了解UI的繪制起始過程
1.Activity.java
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);//①
initWindowDecorActionBar();
}
PhoneWindow中的窗體類型,自定義窗體可以用到這些類型
WindowManager 中窗體的類型:
* @see #TYPE_BASE_APPLICATION
* @see #TYPE_APPLICATION
* @see #TYPE_APPLICATION_STARTING
* @see #TYPE_DRAWN_APPLICATION
* @see #TYPE_APPLICATION_PANEL
* @see #TYPE_APPLICATION_MEDIA
* @see #TYPE_APPLICATION_SUB_PANEL
* @see #TYPE_APPLICATION_ABOVE_SUB_PANEL
* @see #TYPE_APPLICATION_ATTACHED_DIALOG
* @see #TYPE_STATUS_BAR
* @see #TYPE_SEARCH_BAR
* @see #TYPE_PHONE
* @see #TYPE_SYSTEM_ALERT
* @see #TYPE_TOAST
* @see #TYPE_SYSTEM_OVERLAY
* @see #TYPE_PRIORITY_PHONE
* @see #TYPE_STATUS_BAR_PANEL
* @see #TYPE_SYSTEM_DIALOG
* @see #TYPE_KEYGUARD_DIALOG
* @see #TYPE_SYSTEM_ERROR
* @see #TYPE_INPUT_METHOD //輸入法
* @see #TYPE_INPUT_METHOD_DIALOG
*/
2.getWindow()拿到的是Window的實(shí)現(xiàn)類PhoneWindow
mWindow = new PhoneWindow(this, window);
PhoneWindow源碼:
在 com.android.internal.policy包下面
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();//②
}
……
mLayoutInflater.inflate(layoutResID,mContentParent//⑥最后
將布局渲染到幀布局當(dāng)中。
}
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();//③生成一個(gè)DecorView(繼承的FrameLayout)
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);//④
}
protected ViewGroup generateLayout(DecorView decor) {//⑤
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
}
.............
上面判斷加載哪一種布局,有的有actionBar 有的沒有,還有其他種類的布局,然后加載添加到decor中。
下圖中就是一種簡單的布局。不含有ActionBar,但是有一個(gè)ViewStub方便以后添加標(biāo)題欄
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
//然后將我們的布局填充到這個(gè)布局當(dāng)中
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
}
}

D:\Software\Android\SDK\platforms\android-23\data\res ,查看系統(tǒng)的資源文件
這里我們來看一下snackbar添加到布局中的原理
private static ViewGroup findSuitableParent(View view) {
ViewGroup fallback = null;
do {
if (view instanceof CoordinatorLayout) {
// We've found a CoordinatorLayout, use it
return (ViewGroup) view;
} else if (view instanceof FrameLayout) {
if (view.getId() == android.R.id.content) {
//見上圖中的id/content 將SnackBar添加到DecorView中FrameLayout
// If we've hit the decor content view, then we didn't find a CoL in the
// hierarchy, so use it.
return (ViewGroup) view;
} else {
// It's not the content view but we'll use it as our fallback
fallback = (ViewGroup) view;
}
}
if (view != null) {
// Else, we will loop and crawl up the view hierarchy and try to find a parent
final ViewParent parent = view.getParent();
view = parent instanceof View ? (View) parent : null;
}
} while (view != null);
// If we reach here then we didn't find a CoL or a suitable content view so we'll fallback
return fallback;
}
二、measure、layout、draw的三個(gè)執(zhí)行流程
View.java類
measure:測(cè)量,測(cè)量自己有多大,如果是ViewGroup的話會(huì)同時(shí)測(cè)量里面的子控件的大小
layout:擺放里面的子控件bounds(left,top,right,bottom)
draw:繪制 (直接繼承了view一般都會(huì)重寫onDraw)
ViewGroup.java
看View.java類的源碼:
1.view的requestLayout()方法開始,遞歸地不斷往上找父容器,最終找到DecorView
@CallSuper
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();//不斷的遞歸往上查找,一直到找到DecorView
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
2.執(zhí)行了DecorView的ViewRootImp類的performTranversal()方法 (ViewRootImp類:是PhoneWindow和DecorView的橋梁)
3.performTranversal里面會(huì)調(diào)用以下三個(gè)方法
performTranversal(){
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
performDraw();
}

4.performMeasure會(huì)執(zhí)行View的measure方法,從這里開始測(cè)量View
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
5.View的measure方法里面會(huì)調(diào)用onMeasure方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
............
if (forceLayout || needsLayout) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the 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 the high 32 bits, no mask needed
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
}
.........
}
onMeasure根據(jù)傳入父類的MeasureSpec,得到自身的大小。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
子類的MeasureSpec由父類和LayoutParams一起決定。

下面我們?cè)賮砜纯磄etDefaultSize這個(gè)方法:
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;
}
結(jié)合上圖可以知道如何測(cè)量出View的大小。
我們?cè)賮砜纯矗篻etSuggestedMinimumWidth()方法。
protected int getSuggestedMinimumWidth() {
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
public int getMinimumWidth() {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
public int getIntrinsicWidth() {
return -1;
}
這種情況是確保View設(shè)置了背景也能準(zhǔn)確的測(cè)量出View的大小。
三 ViewGroup的測(cè)量流程

在View的測(cè)量流程中,通過makeMeasureSpec來保存寬高信息,在其他流程通過getMode或getSize得到模式和寬高。那么問題來了,上面提到MeasureSpec是LayoutParams和父容器的模式所共同影響的,那么,對(duì)于DecorView來說,它已經(jīng)是頂層view了,沒有父容器,那么它的MeasureSpec怎么來的呢?
閱讀源碼的時(shí)候一定要帶著疑問,這時(shí)候我們提出了問題,那么接下來就要在源碼中找出答案。
MeasureSpec肯定是在測(cè)量之前就已經(jīng)準(zhǔn)備好,這時(shí)候我們就想什么時(shí)候開始測(cè)量的呢?上面已經(jīng)提到過ViewRootImpl#PerformTraveals這個(gè)方法是UI繪制的起點(diǎn),那我們就去那里看一下。果不其然,看到了我們想看的東西。
private void performTraversals() {
...
if (!mStopped) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); // 1
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
·······
}
/**
* @param windowSize
* The available width or height of the window
*
* @param rootDimension
* The layout params for one dimension (width or height) of the
* window.
*
* @return The measure spec to use to measure the root view.
*/
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
//省略...
}
return measureSpec;
}
getRootMeasureSpec(desiredWindowWidth,lp.width)方法,其中desiredWindowWidth就是屏幕的尺寸,并把返回結(jié)果賦值給childWidthMeasureSpec成員變量(childHeightMeasureSpec同理),因此childWidthMeasureSpec(childHeightMeasureSpec)應(yīng)該保存了DecorView的MeasureSpec。
下面我們來看一下ViewGroup的測(cè)量過程,ViewGroup是繼承了View,調(diào)用的是View的measure方法,我們主要看一下ViewGroup的onMeasure方法,就以LinearLayout為例:
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;
int consumedExcessSpace = 0;
// 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;
}
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);
}
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 remainingExcess = heightSize - mTotalLength
+ (mAllowInconsistentMeasurement ? 0 : consumedExcessSpace);
if (skippedMeasure || remainingExcess != 0 && totalWeight > 0.0f) {
float remainingWeightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
mTotalLength = 0;
for (int i = 0; i < count; ++i) {
final View child = getVirtualChildAt(i);
if (child == null || child.getVisibility() == View.GONE) {
continue;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final float childWeight = lp.weight;
if (childWeight > 0) {
final int share = (int) (childWeight * remainingExcess / remainingWeightSum);
remainingExcess -= share;
remainingWeightSum -= childWeight;
final int childHeight;
if (mUseLargestChild && heightMode != MeasureSpec.EXACTLY) {
childHeight = largestChildHeight;
} else if (lp.height == 0 && (!mAllowInconsistentMeasurement
|| heightMode == MeasureSpec.EXACTLY)) {
// This child needs to be laid out from scratch using
// only its share of excess space.
childHeight = share;
} else {
// This child had some intrinsic height to which we
// need to add its share of excess space.
childHeight = child.getMeasuredHeight() + share;
}
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
Math.max(0, childHeight), MeasureSpec.EXACTLY);
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin,
lp.width);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
// 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);
}
}
這個(gè)源碼是比較復(fù)雜的,但是我們可以看出的是,如果自身大小是確定的,那么高度就是自身的大小,否則的話需要測(cè)量所有的子View,同時(shí)還有分割線之類的,將他們的高度疊加。設(shè)置自身的大小。
總結(jié)
一、measure的過程
如何去合理的測(cè)量一顆View樹?
如果ViewGroup和View都是直接指定的寬高,我還要測(cè)量嗎?
正是因?yàn)楣雀柙O(shè)計(jì)的自適應(yīng)尺寸機(jī)制(比如Match_parent,wrap_content),造成了寬高不確定,所以就需要進(jìn)程測(cè)量measure過程。
measure過程會(huì)遍歷整顆View樹,然后依次測(cè)量每一個(gè)View的真實(shí)的尺寸。(樹的遍歷--先序遍歷)
MeasureSpec:測(cè)量規(guī)格
int 32位:010111100011100
拿前面兩位當(dāng)做mode,后面30位當(dāng)做值。
1.mode:
1) EXACTLY: 精確的。比如給了一個(gè)確定的值 100dp
AT_MOST: 根據(jù)父容器當(dāng)前的大小,結(jié)合你指定的尺寸參考值來考慮你應(yīng)該是多大尺寸,需要計(jì)算(Match_parent,wrap_content就是屬于這種)
UNSPECIFIED: 最多的意思。根據(jù)當(dāng)前的情況,結(jié)合你制定的尺寸參考值來考慮,在不超過父容器給你限定的只存的前提下,來測(cè)量你的一個(gè)恰好的內(nèi)容尺寸。
用的比較少,一般見于ScrollView,ListView(大小不確定,同時(shí)大小還是變的。會(huì)通過多次測(cè)量才能真正決定好寬高。)
2.value:寬高的值。
經(jīng)過大量測(cè)量以后,最終確定了自己的寬高,需要調(diào)用:setMeasuredDimension(w,h)
寫自定義控件的時(shí)候,我們要去獲得自己的寬高來進(jìn)行一些計(jì)算,必須先經(jīng)過measure,才能獲得到寬高---不是getWidth(),而是getMeasuredWidth()
也就是當(dāng)我們重寫onMeasure的時(shí)候,我們需要在里面調(diào)用child.measure()才能獲取child的寬高。
從規(guī)格當(dāng)中獲取mode和value:
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
反過來將mode和value合成一個(gè)規(guī)格呢:
MeasureSpec.makeMeasureSpec(resultSize, resultMode);
ViewGroup:
設(shè)計(jì)它的目的是什么?
1)作為容器處理焦點(diǎn)問題。
2)作為容器處理事件分發(fā)問題;
3)控制容器添加View的流程:addView(),removeView()
4)抽出了一些容器的公共的工具方法:measureChildren,measureChild,measureChildWidthMargins方法。
-------------------重點(diǎn):-----------------------
玩自定義控件的時(shí)候,需要進(jìn)行測(cè)量measure,如何做好這件事?
兩種情況:
1.繼承自View的子類
只需要重寫onMeasure測(cè)量好自己的寬高就可以了。
最終調(diào)用setMeasuredDimension()保存好自己的測(cè)量寬高。
套路:
int mode = MeasureSpec.getMode(widthMeasureSpec);
int Size = MeasureSpec.getSize(widthMeasureSpec);
int viewSize = 0;
switch(mode){
case MeasureSpec.EXACTLY:
viewSize = size;//當(dāng)前view的尺寸就為父容器的尺寸
break;
case MeasureSpec.AT_MOST:
viewSize = Math.min(size, getContentSize());//當(dāng)前view的尺寸就為內(nèi)容尺寸和費(fèi)容器尺寸當(dāng)中的最小值。
break;
case MeasureSpec.UNSPECIFIED:
viewSize = getContentSize();//內(nèi)容有多大,久設(shè)置多大尺寸。
break;
default:
break;
}
//setMeasuredDimension(width, height);
setMeasuredDimension(size);
2.繼承自ViewGroup的子類:
不但需要重寫onMeasure測(cè)量自己,還要測(cè)量子控件的規(guī)格大小。
可以直接使用ViewGroup的工具方法來測(cè)量里面的子控件,也可以自己來實(shí)現(xiàn)這一套子控件的測(cè)量(比如:RelativeLayout)
套路:
//1.測(cè)量自己的尺寸
ViewGroup.onMeasure();
//1.1 為每一個(gè)child計(jì)算測(cè)量規(guī)格信息(MeasureSpec)
ViewGroup.getChildMeasureSpec();
//1.2 將上面測(cè)量后的結(jié)果,傳給每一個(gè)子View,子view測(cè)量自己的尺寸
child.measure();
//1.3 子View測(cè)量完,ViewGroup就可以拿到這個(gè)子View的測(cè)量后的尺寸了
child.getChildMeasuredSize();//child.getMeasuredWidth()和child.getMeasuredHeight()
//1.4ViewGroup自己就可以根據(jù)自身的情況(Padding等等),來計(jì)算自己的尺寸
ViewGroup.calculateSelfSize();
//2.保存自己的尺寸
ViewGroup.setMeasuredDimension(size);
二、layout的過程
ViewGroup才有擺放的過程,有興趣的同學(xué)可以看一下,繼承自ViewGroup控件的源碼。
三、draw的過程
ViewGroup的onDraw方法默認(rèn)是不會(huì)調(diào)用的,因?yàn)樵赩iewGroup構(gòu)造方法里面就默認(rèn)設(shè)置了
setFlags(WILL_NOT_DRAW, DRAW_MASK);//原因是因?yàn)閂iewGroup本來就沒東西顯示,除了設(shè)置了背景,這樣就是為了效率。
如果需要它執(zhí)行onDraw可以,設(shè)置背景或者如下:
setWillNotDraw(false);
2.自繪控件View/ViewGroup
主要重寫onDraw方法繪制,還會(huì)要處理onMeasure,onLayout
3.組合控件。
比如封裝通用的標(biāo)題欄控件。
并不需要自己去繪制視圖上面顯示的內(nèi)容,而只是用系統(tǒng)原生的控件就可以了。
但是我們可以將幾個(gè)原生控件組合到一起,可以創(chuàng)建出的控件就是組合控件。
比如:在構(gòu)造方法里面LayoutInflater.from(context).inflate(R.layout.title,this);然后再加上業(yè)務(wù)邏輯。
最后的話:有些地方還有點(diǎn)粗糙,后面會(huì)繼續(xù)補(bǔ)充。