1. 介紹
-
View
的繪制過程分為三部分:測量
念恍、位置
、繪制
。測量View的寬和高:
measure
計算View的位置:layout
繪制View:draw
經(jīng)過
measure
之后就進(jìn)入了layout
過程悬秉,measure
過程可以查看這篇文章:自定義View原理篇-measure過程营密。本篇文章主要對
layout
過程進(jìn)行分析械媒。
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);
//進(jìn)行測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//...
//進(jìn)行布局
performLayout(lp, mWidth, mHeight);
//...
//進(jìn)行繪制
performDraw();
//...
}
進(jìn)一步看看performLayout()
:
2.2 ViewRootImpl的performLayout
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
//...
//調(diào)用layout
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
//...
}
這里的host
就是DecorView
,如果不知道DecorView
纷捞,可以自己私下找文章理解理解。
layout()
方法傳入的0,0,host.getMeasuredWidth,host.getMeasuredHeight
就是一個View
的上下左右四個位置被去,可以看到主儡,DecorView
都是從左上角位置(0,0)開始進(jìn)行布局的,其寬高則為測量寬高惨缆。
下面重點來分析Layout
過程
3.layout過程分析
layout
用來計算View
的位置糜值,即確定View
的Left
丰捷、Top
、Right
和 Bottom
這四個頂點的位置臀玄。如下圖所示:
同樣瓢阴,layout
過程根據(jù)View
的類型也可以分為兩種情況:
- 計算單一
View
位置時,只需計算其自身即可健无;- 計算
ViewGroup
位置時荣恐,需要計算ViewGroup
自身的位置以及其包含的子View
在ViewGroup
中的位置。
我們對這兩種情況分別進(jìn)行分析累贤。
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布局模式是否有一些特殊的邊界
//有特殊邊界則調(diào)用setOpticalFrame(l, t, r, b)
//無特殊邊界則調(diào)用setFrame(l, t, r, b)
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
// 若View的大小或位置有變化
// 會重新確定該View所有的子View在父容器的位置叠穆,通過調(diào)用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();
//調(diào)用setFrame()
return setFrame(
left + parentInsets.left - childInsets.left,
top + parentInsets.top - childInsets.top,
right + parentInsets.left + childInsets.right,
bottom + parentInsets.top + childInsets.bottom);
}
setOpticalFrame()
里面最終還是會調(diào)用到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
還是很簡單的,來張流程圖簡單總結(jié)一下:
3.2 ViewGroup的layout過程
ViewGroup
的layout
過程除了需要計算ViewGroup
自身的位置外会烙,還需要計算其包含的子View
在ViewGroup
中的位置负懦。
計算ViewGroup
自身的位置實際上跟單一View
的過程是一樣的,這里就不重述;唯一不同的就是單一View
的onLayout()
實現(xiàn)為空柏腻,ViewGroup
需要具體實現(xiàn)onLayout()
方法纸厉。
onLayout()
方法在ViewGroup
是一個抽象方法,需要其子類去重寫五嫂,因為確定子View
的位置與具體的布局有關(guān)残腌,所以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ū)分方向來進(jìn)行不同的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;
//調(diào)用setChildFrame()孩灯,這里確定子View的位置
setChildFrame(child, childLeft, childTop + getLocationOffset(child),
childWidth, childHeight);
//childTop加上子View的高度、bottomMargin等值
//因此后面的子View就順延往下放逾滥,這符合垂直方向的LinearLayout的特性
childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
//...
}
}
}
layoutVertical()
通過遍歷子View
峰档,并調(diào)用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()
中就是調(diào)用子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);
//獲取當(dāng)前子View寬/高值
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
//計算當(dāng)前子View的四個位置值
int mLeft = l + 100 * i;//具體邏輯請自行計算
int mTop = t + 100 * i;//具體邏輯請自行計算
int mRight = mLeft + width;//具體邏輯請自行計算
int mBottom = mTop + height;//具體邏輯請自行計算
//根據(jù)上面的計算結(jié)果設(shè)置子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;
}
結(jié)合源碼中的各種賦值過程抬驴,getWidth()
的值就是測量出的寬度炼七。
當(dāng)然,我們可以通過重寫layout()
來修改最終的寬度布持,但一般這沒有任何的實際意義豌拙,如:
@Override
public void layout(int l, int t, int r, int b) {
// 修改傳入的位置參數(shù),這樣一來题暖,getWidth()獲得的寬度就比測量出來的寬度大上100了
super.layout(l, t, r + 100, b + 100);
}
5.2
本篇文章只是大概的講解一下位置的流程按傅,還需要大家自己下去好好的理解理解