由于performLayout之前是performMeasure()操作晶疼,所以不熟悉測量的小伙伴看我上一篇博客Android View 測量原理
我想了想,如果直接從ViewGroup里面的方法談起猾普,可能和網(wǎng)上很多博客一樣了,但是如果只是向framework開發(fā)者分析哪些缔御,又分析不到應(yīng)用層抬闷,所以我覺得應(yīng)該從performLayout()這個方法開始分析測量,因為如果在向framework層深入,那就會接觸到WindowManagerService笤成,這個過程需要掌握Binder知識评架,但是Binder知識很多人一時半會掌握不了,尤其是對于application開發(fā)者炕泳,不關(guān)注這些纵诞,所以從performLayout()說起。
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
...
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
...
}finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
這里的host是decorView培遵,decorView對應(yīng)的布局是一個FrameLayout浙芙,所以我們進入FrameLayout的layout方法
//傳遞進來的是左上右下的值
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;
//判斷是不是左上右下這些值有所改變,如果改變的話為true,并且在setFrame中給mLeft...mRight賦值
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//true進入調(diào)用到onLayout方法
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
......
}
.....
}
繼續(xù)進入onLyout方法中,我們會發(fā)現(xiàn)是空方法籽腕,所以我們此時想到了ViewGroup
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
繼續(xù)看ViewGroup的onLayout方法,可想而知每個子類都有自己的實現(xiàn)嗡呼,我們用LinearLayout舉例
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
LinearLayout實現(xiàn)方法是:
@Override
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;
// 水平可用寬度
int childSpace = width - paddingLeft - mPaddingRight;
final int count = getVirtualChildCount();//調(diào)用getChildCount()
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
//gravity屬性配置的值
switch (majorGravity) {
//當(dāng)配置bottom時候
case Gravity.BOTTOM:
// 看出來是已父容器總內(nèi)容寬度為基準的最下面
childTop = mPaddingTop + bottom - top - mTotalLength;
break;
// 同理配置的center
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;
// 同理配置的top
case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}
for (int i = 0; i < count; i++) {
//得到每一個孩子View
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
final int childWidth = child.getMeasuredWidth();//view的寬
final int childHeight = child.getMeasuredHeight();//view的高
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:
//水平方向的話view左側(cè)的距離
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;
//設(shè)置子view的坐標(biāo)
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
//加上子view的坐標(biāo)繼續(xù)向下排列
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
//下一個view
i += getChildrenSkipCount(child, i);
}
}
}
通過上面標(biāo)記的注釋我們知道了對每一個子view進行排列。
同時注意幾個方法
setFrame
當(dāng)size和position變化時皇耗,返回true南窗。如果發(fā)生了變化,會在setFrame方法內(nèi)部調(diào)用invalidate郎楼。onLayout
View中onLayout什么都沒有做万伤,在ViewGroup中,根據(jù)各自實際規(guī)則(Linear呜袁、Relative 等)對內(nèi)部Views進行布局安排敌买。getMeasuredWidth與getWidth
可以調(diào)用的時機不同:getMeasuredWidth在measure后即可調(diào)用,getWidth要在layout后才可以調(diào)用阶界。(在發(fā)生時機之前調(diào)用的話均返回0)
含義不同:getMeasuredWidth是View計算出自己的實際大小虹钮,getWidth是在布局后的大小。最簡單的荐操,在ScrollLayout中芜抒,getHeight返回屏幕內(nèi)的高度珍策,getMeasuredHeight返回屏幕內(nèi)+屏幕外的總高度托启。