上一篇咱們分析Android View的測量。咱們先回顧一下,View的測量,在ViewRootImpl#performTraverals方法下,先進行對DecorView根佈局測量獲取MeasureSpec,而後開始執行測量performMeasure(),經過View#measure找到對應View的核心onMeasure(),若是是ViewGroup,先遞歸子View,將父View的MeasureSpec和子View的LayoutParams做爲參數而進行測量,而後逐層返回,不斷保存ViewGroup的測量寬高。javascript
好了,咱們短短回顧後,回到ViewRootImpl#performTraverals方法:java
private void performTraversals() {
...
if (!mStopped) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
if (didLayout) {
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
...
}
if (!cancelDraw && !newSurface) {
if (!skipDraw || mReportNextDraw) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();
}
}
...
}複製代碼
源碼很是清晰,繼續咱們的分析performLayout()。Let's go!ide
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
Log.v(TAG, "Laying out " + host + " to (" +
host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}複製代碼
源碼挺清晰易懂,咱們着重看到host.layout(),host在上面mView賦值,那就是說host是指向DecorView對象的,方法所帶的參數分別是0,0,host.getMeasuredWidth(),host.getMeasuredHeight(),分別表明着View的左上右下四個位置。以前發分析所知DecorView是FrameLayout子類,FrameLayout是ViewGroup子類,而咱們在ViewGroup#layout方法中看到是用final修飾的,那就是說host.layout調用的就是ViewGroup#layout,咱們看一下該方法的源碼:oop
@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'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}複製代碼
咱們首先利用變量的命名推測,再結合源碼的註釋來分析,看一下mTransition對象,咱們看到是LayoutTransition類的對象,註釋寫着用於處理ViewGroup增長和刪除子視圖的動畫效果,那就是layout方法一開始多是判斷一些參數來處理動畫的過渡效果的,不影響總體的代碼邏輯,咱們能夠直接看super.layout(l, t, r, b);,那就是說調用的是View#layout方法,並將左上右下四個參數傳遞過去。佈局
public class View implements ···{
···
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);//設置相對於父佈局的位置
//判斷View的位置是否發生過變化,看有不必進行從新layout
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;
}
}複製代碼
一開始的判斷,咱們從他們全局變量的註釋來理解,說的大概是在測量方法被跳過期,須要在layout()前再次調用measure()測量方法。接着是isLayoutModeOptical(),這裏面的註釋是這個ViewGroup的佈局是否在視角範圍裏,setOpticalFrame()裏面的實現方法通過一些判斷計算,一樣調用回setFrame(l, t, r, b)方法。動畫
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
···
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);
···
return changed;
}複製代碼
這方法開始咱們能夠跳過,主要是對mLeft 、mTop、mRight、mBottom賦值,咱們稍微看一下方法註釋中對left,top,right,bottom解析是各位置的點,且是相對於父佈局的,那就是說如今賦值後能夠肯定了View本身在父佈局的位置了。另外咱們在類方法中查詢getLeft()等其餘三個點,看看他們返回值對用mLeft等對應值的,這個點咱們後面再說,咱們繼續往下分析。this
在setFrame()以後咱們終於能夠看到onLayout(),點進去查看View#onLayout方法:spa
public class View implements···{
···
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
···
}
public abstract class ViewGroup extends View implements·· ··· @Override protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
···複製代碼
從上面源碼咱們看到View#onLayout與ViewGroup#onLayout都是實現了一個空方法。可是ViewGroup是一個抽象方法,那就是說繼承ViewGroup的子類必須重寫onLayout()方法。由於上篇咱們分析View的測量一樣是不一樣的ViewGroup都有不一樣的onMeasure(),既然測量都不一樣了,onLayout()佈局方法就確定不一樣了,咱們按照上篇的邏輯,依舊對FrameLayout(DecorView)的onLayout來分析:code
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom,
boolean forceLeftGravity) {
final int count = getChildCount();
// parentLeft由父容器的padding和Foreground決定
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
// 不爲GONE
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//獲取子View的測量寬高
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
// 當子View設置水平方向layout_gravity屬性
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
// 居中的計算方式
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
// 右側的計算方式
case Gravity.RIGHT:
if (!forceLeftGravity) {
childLeft = parentRight - width - lp.rightMargin;
break;
}
// 左側的計算方式
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
// 當子View設置豎直方向layout_gravity屬性
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
//對子元素進行佈局
child.layout(childLeft, childTop, childLeft + width, childTop + height);
}
}
}複製代碼
FrameLayout#onLayout方法直接調用layoutChildren方法,裏面的實現方法雖然有點長,可是比較好理解,無非加點空間想象力上去就無壓力了。orm
咱們梳理一下:首先是獲得父佈局的左上右下的Padding值,而後遍歷子佈局,經過子View的layout_gravity屬性、子View的LayoutParams屬性、父佈局的Padding值來肯定子View的左上右下參數,而後調用child.layout方法,把佈局流程從父容器傳遞到子元素。
上面咱們已經分析過View#layout方法,是一個空方法,主要做用是咱們使用的子View重寫該方法,例如TextView、CustomView自定義View等等。不一樣的View不一樣的佈局方式。你們有興趣能夠看看他們的實現過程。
View#getWidth()、View#getMeasureWidth()
咱們在分析View#setFrame()分析到這個問題,咱們在今篇View的佈局可知,在View#setFrame()執行裏對
mLeft、mRight、mTop、mBottom,從命名方式帶m,咱們能夠知道這是一個全局變量,在View的佈局時賦值的。
而View#getMeasureWidth()就要回到咱們上一篇View的測量,在View#onMeasure方法中會調用View#setMeasuredDimension方法,在這方式的實現子View設置自身寬高的,這方法裏有View#setMeasuredDimensionRaw方法,咱們看一下它的源碼:
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}複製代碼
簡單來講就是對mMeasuredWidth與mMeasuredHeight賦值,因此在View#getMeasureWidth方法裏返回的值,,是咱們進行測量後的值mMeasuredWidth。
他們的值基本狀況下是一致的,那麼不一致時何時呢?看回咱們本篇中的FrameLayout#onLayout,最後是否是調用了childView#layout方法,FrameLayout咱們不可修改,可是在咱們CustomView自定義View,重寫onLayout的時候是能夠按照咱們的特殊要求修改的,例如修改成:childView.layout(0,0,100,100);那麼View#getWidth()、View#getMeasureWidth()返回的值就會不一致,有興趣的同窗能夠本身去驗證一下。
因此他們的值在不特殊修改的狀況下返回時同樣的,可是他們的意義是徹底不一樣的,一個在測量過程、一個在佈局過程。你們要稍微留意。
View的佈局流程就已經所有分析完了。咱們總結一下:佈局流程相對簡單一些,上一篇View的測量,咱們能夠獲得View的寬和高,ViewGroup的layout佈局,調用layout方法,肯定在父佈局的位置,在onLayout()方法中遍歷其子View,調用子View的layout方法並根據子View大小、View的LayoutParams值、父View對子 View位置的限制做爲參數傳入,完成佈局;而View的測量利用測量出來的寬和高來計算出子View相對於父View的位置參數,完成佈局。在下篇,咱們將會講述最後一步,View的繪製。