序言:為什么會(huì)分析這個(gè)問(wèn)題呢,因?yàn)樯洗吾斸旊娫捗嬖囍斜幻嬖嚬賳?wèn)到了,很尷尬的沒(méi)回答出來(lái),View的繪制流程看過(guò)一點(diǎn)源碼,但是感覺(jué)還不夠,像這種View的問(wèn)題能夠延伸出很多問(wèn)題,下面正文開(kāi)始:
Q1:在一個(gè)RelativeLayout中有一個(gè)TextView和一個(gè)Button,當(dāng)點(diǎn)擊Button的時(shí)候給TextView設(shè)置文本,這時(shí)RelativeLayout會(huì)重新測(cè)量嗎?如果會(huì),為什么?
首先我們先大致的想一下這個(gè)問(wèn)題問(wèn)的是關(guān)于哪一塊的知識(shí),如果毫不猶豫上去就是一通回答,這樣顯得太不明智了,我也知道會(huì)重新測(cè)量,為什么?下面我們從源碼的角度去看。既然是設(shè)置文本,那么我們就從TextView的setText中去看看吧:
TextView:
private void setText(CharSequence text, BufferType type, boolean notifyBefore, int oldlen) {
...
if (mLayout != null) {
checkForRelayout();
}
...
}
在setText中,我找到了這個(gè)checkForRelayout方法,由于我們初始化過(guò)了,所以setText肯定會(huì)執(zhí)行該方法:
TextView:
/**
* Check whether entirely new text requires a new view layout
* or merely a new text layout.
* 檢查新文本是否需要一個(gè)新的視圖布局
*/
private void checkForRelayout() {
// If we have a fixed width, we can just swap in a new text layout
// if the text height stays the same or if the view height is fixed.
//如果textview的寬度和高度固定不變的話
if ((mLayoutParams.width != LayoutParams.WRAP_CONTENT
|| (mMaxWidthMode == mMinWidthMode && mMaxWidth == mMinWidth))
&& (mHint == null || mHintLayout != null)
&& (mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight() > 0)) {
// Static width, so try making a new text layout.
int oldht = mLayout.getHeight();
int want = mLayout.getWidth();
int hintWant = mHintLayout == null ? 0 : mHintLayout.getWidth();
/*
* No need to bring the text into view, since the size is not
* changing (unless we do the requestLayout(), in which case it
* will happen at measure).
* 因?yàn)榇笮〔粫?huì)變,所以不需要將文本放入視圖中
*/
makeNewLayout(want, hintWant, UNKNOWN_BORING, UNKNOWN_BORING,
mRight - mLeft - getCompoundPaddingLeft() - getCompoundPaddingRight(),
false);
if (mEllipsize != TextUtils.TruncateAt.MARQUEE) {
// In a fixed-height view, so use our new text layout.
if (mLayoutParams.height != LayoutParams.WRAP_CONTENT
&& mLayoutParams.height != LayoutParams.MATCH_PARENT) {
autoSizeText();
invalidate();
return;
}
// Dynamic height, but height has stayed the same,
// so use our new text layout.
//動(dòng)態(tài)高度,但是高度不變
if (mLayout.getHeight() == oldht
&& (mHintLayout == null || mHintLayout.getHeight() == oldht)) {
autoSizeText();
invalidate();
return;
}
}
// We lose: the height has changed and we have a dynamic height.
// Request a new view layout using our new text layout.
requestLayout();
invalidate();
} else {
// Dynamic width, so we have no choice but to request a new
// view layout with a new text layout.
//動(dòng)態(tài)寬度,我們只能請(qǐng)求一個(gè)新的布局
nullLayouts();
requestLayout();
invalidate();
}
}
說(shuō)實(shí)話,看源碼真的是一件很累的過(guò)程,我們可能很難找到下手的點(diǎn),在這里給大家分享一個(gè)方法,找你覺(jué)得是重點(diǎn)的代碼或者方法去看(和你本次看源碼想要研究的方向相同),一旦你看著看著發(fā)現(xiàn)看不太懂了,你就倒回來(lái)再看其他地方。從上面這段代碼我們不難看出,在這里調(diào)用了requestLayout()和invalidate()這兩個(gè)方法,看到這里相信大家應(yīng)該就明白了吧,是他是他就是他,requestLayout(),好,我們也順便來(lái)看一下這個(gè)方法:
TextView:
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()) {
//向父容器請(qǐng)求布局
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
在requestLayout()方法中調(diào)用了mParent.requestLayout(),也就是說(shuō)調(diào)用了父View的requestLayout()方法,然后一級(jí)一級(jí)往上傳,最終會(huì)調(diào)用ViewRootImpl中的requestLayout()方法:
RootViewImpl:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
首先會(huì)先去判斷一下是否是在當(dāng)前線程,然后會(huì)調(diào)用scheduleTraversals()方法:
RootViewImpl:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
看到這里估計(jì)有很多人會(huì)懵逼了,這里好像也沒(méi)有什么嘛,別急老鐵,這里有一個(gè)名叫mTraversalRunnable的參數(shù),那我們就點(diǎn)進(jìn)去看看他的實(shí)現(xiàn):
RootViewImpl:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
接下來(lái)會(huì)調(diào)用doTraversal()方法:
RootViewImpl:
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
然后終于到了我們所期待的地方了,這里又調(diào)用了performTraversals()方法,相信看過(guò)View的繪制流程源碼的童鞋應(yīng)該就知道了,這里才真正開(kāi)始View的測(cè)量,擺放,繪制等操作。在performTraversals()這個(gè)方法中分別調(diào)用了onMeasure,onLayout,onDraw等方法,有興趣的童鞋可以自行去看。至此我們應(yīng)該就能知道上述問(wèn)題該如何回答了。
Q2:為什么TextView的寬高設(shè)置成wrap_content,在Activity中獲取的時(shí)候?qū)挾葹?,高度不為0?

這個(gè)問(wèn)題呢,是我在找上面那個(gè)問(wèn)題的答案的過(guò)程中發(fā)現(xiàn)的,既然是寬高的問(wèn)題,那么我們當(dāng)然得要去看onMeasure方法了:
if (widthMode == MeasureSpec.EXACTLY) {
// Parent has told us how big to be. So be it.
width = widthSize;
} else {
//寬度設(shè)置成wrap_content會(huì)走這里
if (mLayout != null && mEllipsize == null) {
des = desired(mLayout);
}
if (des < 0) {
boring = BoringLayout.isBoring(mTransformed, mTextPaint, mTextDir, mBoring);
if (boring != null) {
mBoring = boring;
}
} else {
fromexisting = true;
}
if (boring == null || boring == UNKNOWN_BORING) {
if (des < 0) {
des = (int) Math.ceil(Layout.getDesiredWidth(mTransformed, 0, mTransformed.length(), mTextPaint, mTextDir));
}
} else {
width = boring.width;
}
...
這下面是計(jì)算設(shè)置drawable和hint的寬度,所以我們可以忽略
}
關(guān)于寬度的我們只需要看這一段就好了,TextView設(shè)置wrap_content,會(huì)走下面的else,然后第一次進(jìn)來(lái),這個(gè)onMeasue里面的mLayout還沒(méi)有初始化,所以mLayout = null,然后由于des = -1,所以boring會(huì)被初始化,boring != null,所以width = boring.width,而boring.width這個(gè)東西初始值為0,所以width = 0;同樣的高度也是這樣分析就可以了,要注意的是,高度和textSize和行數(shù)有關(guān),所以設(shè)置不同的行數(shù)和textSize(默認(rèn)是有TextSize的)得到的hight都不一樣的。
總結(jié):其實(shí)大家可以這樣理解,寬高都設(shè)置成wrap_content,沒(méi)設(shè)置文本的情況下,寬度肯定為0,但是單行的高度是固定的(和TextSize也有關(guān),一旦設(shè)置也是固定的了)。