前言
在Android View的測(cè)量,布局,繪制(一)中,描述了View測(cè)量,這篇文章,主要針對(duì)View的布局進(jìn)行講解。
View的布局?jǐn)[放,主要是在performLayout方法中進(jìn)行。
##ViewRootImpl
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
if (host == null) {
return;
}
...
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); //1
...
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
注釋1host是DectorView,這里調(diào)用了host.layout方法,把起始點(diǎn)x=0,y=0傳入,然后將測(cè)量好的寬高傳入。
##View
public void layout(int l, int t, int r, int b) {
//如果不是第一次,跳過(guò)否則會(huì)在此進(jìn)行測(cè)量,意思是第一次進(jìn)來(lái)會(huì)進(jìn)行一次測(cè)量用于保存寬高,意義在于優(yōu)化,接著往下看
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
//初次進(jìn)行上下左右點(diǎn)的初始化
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
//這里調(diào)用了setFrame進(jìn)行初始化mLeft,mRight,mTop,mBottom這四個(gè)值
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); //1
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b); //2
...
}
...
}
注釋1 調(diào)用了setFrame方法進(jìn)行初始化mLeft,mRight,mTop,mBottom這四個(gè)值。
##View
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
...
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) { //1
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使舊信息無(wú)效
invalidate(sizeChanged);
//重新初始化定位
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
...
}
return changed;
}
setFrame方法在進(jìn)行初始化的時(shí)候會(huì)對(duì)比上一次是否一致,若一致則不會(huì)進(jìn)入注釋1的if判斷, 若是一致,則會(huì)使舊的信息直接失效invalidate(sizeChanged)。接著在對(duì)View的上下左右?guī)讉€(gè)點(diǎn)賦值。
注釋2
##View
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
onLayout方法,在View類中,只是個(gè)空的方法,具體的業(yè)務(wù)都交給子類是重寫(xiě)。從上面我們知道當(dāng)前View的子類是DecorView。
##DecorView
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
...
}
接著他調(diào)用了父類的onLayout而它的父類是FreamLayout所以,找到最終目標(biāo)。
##FrameLayout
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
這里請(qǐng)注意,有個(gè)核心問(wèn)題要注意的是同之前所講的測(cè)量流程, 布局也是同樣, 每一個(gè)不同布局組件她們的實(shí)現(xiàn)是不一樣的,而在這里我們以FreamLayout舉例,在這里他開(kāi)始調(diào)用了一個(gè)
layoutChildren方法。
##FrameLayout
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
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);
if (child.getVisibility() != GONE) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
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;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL: //水平居中
/**
* parentLeft + (parentRight - parentLeft - width) / 2 中心點(diǎn)
* 加上左邊偏移量,減去右邊偏移量得到起點(diǎn)Left坐標(biāo)
*/
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;
}
//childTop同理
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);
}
}
}
這個(gè)時(shí)候會(huì)發(fā)現(xiàn),當(dāng)前組件在不斷的迭代當(dāng)前的子view,然后開(kāi)始調(diào)用自己layout方法進(jìn)行定位,所以直接從此處可以看出來(lái),當(dāng)前布局?jǐn)[放流程實(shí)際上是,先得到頂層, 頂層自己先開(kāi)始layout進(jìn)行布局定位,然后調(diào)用onLayout調(diào)用子view讓子view調(diào)用自己的layout對(duì)自己進(jìn)行定位以達(dá)到定位的所有目的,
總結(jié):
那么其實(shí)要清楚了當(dāng)前的繪制流程和布局流程,需要開(kāi)發(fā)自己自定義的布局其實(shí)實(shí)際上就只需要添加我門(mén)自己的業(yè)務(wù)代碼,不管是FreamLayout,還是LinearLayout等官方提供出來(lái)的布局組件, 都是依照這套機(jī)制來(lái)玩的, 只不過(guò)是添加了不同的業(yè)務(wù),實(shí)現(xiàn)了相對(duì)應(yīng)的效果。
備注:文中Android源碼版本9.0
作者:Alan
原創(chuàng)博客,請(qǐng)注明轉(zhuǎn)載處....