1. 簡介
-
View
的繪制過程分為三部分:measure
、layout
、draw
饱狂。
measure
用來測量View的寬和高。
layout
用來計算View的位置宪彩。
draw
用來繪制View休讳。
經(jīng)過
measure
之后就進入了layout
過程,measure
過程可以查看這篇文章:自定義View原理篇(1)- measure過程尿孔。本章主要對
layout
過程進行詳細的分析俊柔。本文源碼基于android 27。
2. layout的始點
跟measure
一樣,layout
也是始于ViewRootImpl
的performTraversals()
:
2.1 ViewRootImpl的performTraversals
private void performTraversals() {
//...
//獲得view寬高的測量規(guī)格活合,mWidth和mHeight表示窗口的寬高雏婶,lp.widthhe和lp.height表示DecorView根布局寬和高
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);//執(zhí)行測量
//...
performLayout(lp, mWidth, mHeight);//執(zhí)行布局
//...
performDraw();//執(zhí)行繪制
//...
}
再來看看performLayout()
:
2.2 ViewRootImpl的performLayout
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
//...
//調用layout
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
//...
}
這里的host
就是DecorView
,如果不知道DecorView
,可以看看這篇文章:從setContentView揭開DecorView白指。
layout()
方法傳入的0,0,host.getMeasuredWidth,host.getMeasuredHeight
就是一個View
的上下左右四個位置留晚,可以看到,DecorView
都是從左上角位置(0,0)開始進行布局的告嘲,其寬高則為測量寬高错维。
下面重點來分析Layout
過程
3.layout過程分析
layout
用來計算View
的位置憨闰,即確定View
的Left
、Top
需五、Right
和 Bottom
這四個頂點的位置鹉动。如下圖所示:
同樣,layout
過程根據(jù)View
的類型也可以分為兩種情況:
- 計算單一
View
位置時宏邮,只需計算其自身即可泽示;- 計算
ViewGroup
位置時,需要計算ViewGroup
自身的位置以及其包含的子View
在ViewGroup
中的位置蜜氨。
我們對這兩種情況分別進行分析械筛。
3.1 單一View的layout過程
單一View
的layout
過程是從View
的layout()
方法開始:
3.1.1 View的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;
//isLayoutModeOptical(mParent);//判斷該view布局模式是否有一些特殊的邊界
//有特殊邊界則調用setOpticalFrame(l, t, r, b)
//無特殊邊界則調用setFrame(l, t, r, b)
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
// 若View的大小或位置有變化
// 會重新確定該View所有的子View在父容器的位置,通過調用onLayout()來實現(xiàn)飒炎。
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
// ...
}
我們接下來分別看看setOpticalFrame()
埋哟,setFrame()
,onLayout()
這三個方法郎汪。
3.1.2 View的setOpticalFrame
先來看看setOpticalFrame()
:
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
Insets parentInsets = mParent instanceof View ?
((View) mParent).getOpticalInsets() : Insets.NONE;
Insets childInsets = getOpticalInsets();
//調用setFrame()
return setFrame(
left + parentInsets.left - childInsets.left,
top + parentInsets.top - childInsets.top,
right + parentInsets.left + childInsets.right,
bottom + parentInsets.top + childInsets.bottom);
}
setOpticalFrame()
里面最終還是會調用到setFrame()
3.1.3 View的setFrame
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;
//...
//賦值赤赊,保存View的四個位置
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
//...
}
return changed;
}
可以看到,View
的四個位置就在這里給確定下來了煞赢。
3.1.4 View的onLayout
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
onLayout()
在View
中就是個空實現(xiàn)抛计,由于單一的View
沒有子View
,因此不需要確定子View
的布局照筑,所以onLayout()
也無需實現(xiàn)吹截。
3.1.5 單一View的layout過程流程圖
所以,單一View
的Layout
還是很簡單的凝危,來張流程圖簡單總結一下:
3.2 ViewGroup的layout過程
ViewGroup
的layout
過程除了需要計算ViewGroup
自身的位置外波俄,還需要計算其包含的子View
在ViewGroup
中的位置。
計算ViewGroup
自身的位置實際上跟單一View
的過程是一樣的蛾默,這里就不重述;唯一不同的就是單一View
的onLayout()
實現(xiàn)為空懦铺,ViewGroup
需要具體實現(xiàn)onLayout()
方法。
onLayout()
方法在ViewGroup
是一個抽象方法趴生,需要其子類去重寫阀趴,因為確定子View
的位置與具體的布局有關,所以ViewGroup
中沒有辦法統(tǒng)一實現(xiàn)苍匆。
我們在這里看看LinearLayout
的onLayout()
實現(xiàn):
3.2.1 LinearLayout的onLayout
@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);
}
}
LinearLayout
會區(qū)分方向來進行不同的layout
方法刘急,我們主要看下豎向的layoutVertical()
,橫向的原理差不多這里就不看了浸踩。
3.2.2 LinearLayout的layoutVertical
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;//記錄子View的Top位置
int childLeft;//記錄子View的Left位置
// ...
// 子View的數(shù)量
final int count = getVirtualChildCount();
// ...
for (int i = 0; i < count; i++) {//遍歷子View
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if (child.getVisibility() != GONE) {
//獲取子View的測量寬 / 高值
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
//...
//childTop加上子View的topMargin的值
childTop += lp.topMargin;
//調用setChildFrame()叔汁,這里確定子View的位置
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
//childTop加上子View的高度、bottomMargin等值
//因此后面的子View就順延往下放,這符合垂直方向的LinearLayout的特性
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
//...
}
}
}
layoutVertical()
通過遍歷子View
据块,并調用setChildFrame()
方法來確定子View
的位置码邻。
3.2.3 LinearLayout的setChildFrame
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
setChildFrame()
中就是調用子View
的layout()
方法來來確定子View
的位置。
3.2.4 ViewGroup的layout過程流程圖
4. 自定義View
4.1 自定義單一view
自定義單一view
一般無需重寫onLayout()
方法另假。
4.2 自定義ViewGroup
由于ViewGroup
沒實現(xiàn)onLayout()
像屋,所以自定義ViewGroup
需要重寫onLayout()
方法。這里給個簡單的模板:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//遍歷子View
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
//獲取當前子View寬/高值
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
//計算當前子View的四個位置值
int mLeft = l + 100 * i;//具體邏輯請自行計算
int mTop = t + 100 * i;//具體邏輯請自行計算
int mRight = mLeft + width;//具體邏輯請自行計算
int mBottom = mTop + height;//具體邏輯請自行計算
//根據(jù)上面的計算結果設置子View的4個頂點
child.layout(mLeft, mTop, mRight, mBottom);
}
}
5. 其他
5.1 getWidth()與getMeasuredWidth()區(qū)別边篮,getHeight()與getMeasuredHeight()同理
getWidth()
:獲得View
最終的寬;getMeasuredWidth()
:獲得View
測量的寬;
一般情況下己莺,這兩者獲得的值是一樣的,我們可以來看看他們的代碼實現(xiàn):
public final int getMeasuredWidth() {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
public final int getWidth() {
return mRight - mLeft;
}
結合源碼中的各種賦值過程戈轿,getWidth()
的值就是測量出的寬度凌受。
當然,我們可以通過重寫layout()
來修改最終的寬度思杯,但一般這沒有任何的實際意義胜蛉,如:
@Override
public void layout(int l, int t, int r, int b) {
// 修改傳入的位置參數(shù),這樣一來色乾,getWidth()獲得的寬度就比測量出來的寬度大上100了
super.layout(l, t, r + 100, b + 100);
}