日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 人文社科 > 生活经验 >内容正文

生活经验

android的布局流程,Android View 布局流程(Layout)全面解析

發(fā)布時(shí)間:2023/11/27 生活经验 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android的布局流程,Android View 布局流程(Layout)全面解析 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

上一篇文章,筆者詳細(xì)講述了View三大工作流程的第一個(gè),Measure流程,如果對(duì)測(cè)量流程還不熟悉的讀者可以參考一下上一篇文章。測(cè)量流程主要是對(duì)View樹進(jìn)行測(cè)量,獲取每一個(gè)View的測(cè)量寬高,那么有了測(cè)量寬高,就是要進(jìn)行布局流程了,布局流程相對(duì)測(cè)量流程來說簡(jiǎn)單許多。那么我們開始對(duì)layout流程進(jìn)行詳細(xì)的解析。

ViewGroup的布局流程

上一篇文章提到,三大流程始于ViewRootImpl#performTraversals方法,在該方法內(nèi)通過調(diào)用performMeasure、performLayout、performDraw這三個(gè)方法來進(jìn)行measure、layout、draw流程,那么我們就從performLayout方法開始說,我們先看它的源碼:

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()); // 1

//省略...

} finally {

Trace.traceEnd(Trace.TRACE_TAG_VIEW);

}

mInLayout = false;

}

由上面的代碼可以看出,直接調(diào)用了①號(hào)的host.layout方法,host也就是DecorView,那么對(duì)于DecorView來說,調(diào)用layout方法,就是對(duì)它自身進(jìn)行布局,注意到傳遞的參數(shù)分別是0,0,host.getMeasuredWidth,host.getMeasuredHeight,它們分別代表了一個(gè)View的上下左右四個(gè)位置,顯然,DecorView的左上位置為0,然后寬高為它的測(cè)量寬高。由于View的layout方法是final類型,子類不能重寫,因此我們直接看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;

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

mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

ListenerInfo li = mListenerInfo;

if (li != null && li.mOnLayoutChangeListeners != null) {

ArrayList listenersCopy =

(ArrayList)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;

}

首先看①號(hào)代碼,調(diào)用了setFrame方法,并把四個(gè)位置信息傳遞進(jìn)去,這個(gè)方法用于確定View的四個(gè)頂點(diǎn)的位置,即初始化mLeft,mRight,mTop,mBottom這四個(gè)值,當(dāng)初始化完畢后,ViewGroup的布局流程也就完成了

那么,我們先看View#setFrame方法:

protected boolean setFrame(int left, int top, int right, int bottom) {

//省略...

mLeft = left;

mTop = top;

mRight = right;

mBottom = bottom;

mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

//省略...

return changed;

}

可以看出,它對(duì)mLeft、mTop、mRight、mBottom這四個(gè)值進(jìn)行了初始化,對(duì)于每一個(gè)View,包括ViewGroup來說,以上四個(gè)值保存了Viwe的位置信息,所以這四個(gè)值是最終寬高,也即是說,如果要得到View的位置信息,那么就應(yīng)該在layout方法完成后調(diào)用getLeft()、getTop()等方法來取得最終寬高,如果是在此之前調(diào)用相應(yīng)的方法,只能得到0的結(jié)果,所以一般我們是在onLayout方法中獲取View的寬高信息。

在設(shè)置ViewGroup自身的位置完成后,我們看到會(huì)接著調(diào)用②號(hào)方法,即onLayout()方法,該方法在ViewGroup中調(diào)用,用于確定子View的位置,即在該方法內(nèi)部,子View會(huì)調(diào)用自身的layout方法來進(jìn)一步完成自身的布局流程。由于不同的布局容器的onMeasure方法均有不同的實(shí)現(xiàn),因此不可能對(duì)所有布局方式都說一次,另外上一篇文章是用FrameLayout#onMeasure進(jìn)行講解的,那么現(xiàn)在也對(duì)FrameLayout#onLayout方法進(jìn)行講解:

@Override

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

//把父容器的位置參數(shù)傳遞進(jìn)去

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();

//以下四個(gè)值會(huì)影響到子View的布局參數(shù)

//parentLeft由父容器的padding和Foreground決定

final int parentLeft = getPaddingLeftWithForeground();

//parentRight由父容器的width和padding和Foreground決定

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();

//獲取子View的測(cè)量寬高

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;

//當(dāng)子View設(shè)置了水平方向的layout_gravity屬性時(shí),根據(jù)不同的屬性設(shè)置不同的childLeft

//childLeft表示子View的 左上角坐標(biāo)X值

switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {

/* 水平居中,由于子View要在水平中間的位置顯示,因此,要先計(jì)算出以下:

* (parentRight - parentLeft -width)/2 此時(shí)得出的是父容器減去子View寬度后的

* 剩余空間的一半,那么再加上parentLeft后,就是子View初始左上角橫坐標(biāo)(此時(shí)正好位于中間位置),

* 假如子View還受到margin約束,由于leftMargin使子View右偏而rightMargin使子View左偏,所以最后

* 是 +leftMargin -rightMargin .

*/

case Gravity.CENTER_HORIZONTAL:

childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +

lp.leftMargin - lp.rightMargin;

break;

//水平居右,子View左上角橫坐標(biāo)等于 parentRight 減去子View的測(cè)量寬度 減去 margin

case Gravity.RIGHT:

if (!forceLeftGravity) {

childLeft = parentRight - width - lp.rightMargin;

break;

}

//如果沒設(shè)置水平方向的layout_gravity,那么它默認(rèn)是水平居左

//水平居左,子View的左上角橫坐標(biāo)等于 parentLeft 加上子View的magin值

case Gravity.LEFT:

default:

childLeft = parentLeft + lp.leftMargin;

}

//當(dāng)子View設(shè)置了豎直方向的layout_gravity時(shí),根據(jù)不同的屬性設(shè)置同的childTop

//childTop表示子View的 左上角坐標(biāo)的Y值

//分析方法同上

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;

}

//對(duì)子元素進(jìn)行布局,左上角坐標(biāo)為(childLeft,childTop),右下角坐標(biāo)為(childLeft+width,childTop+height)

child.layout(childLeft, childTop, childLeft + width, childTop + height);

}

}

}

由源碼看出,onLayout方法內(nèi)部直接調(diào)用了layoutChildren方法,而layoutChildren則是具體的實(shí)現(xiàn)。

先梳理一下以上邏輯:首先先獲取父容器的padding值,然后遍歷其每一個(gè)子View,根據(jù)子View的layout_gravity屬性、子View的測(cè)量寬高、父容器的padding值、來確定子View的布局參數(shù),然后調(diào)用child.layout方法,把布局流程從父容器傳遞到子元素。

那么,現(xiàn)在就分析完了ViewGroup的布局流程,那么我們接著分析子元素的布局流程。

子View的布局流程

子View的布局流程也很簡(jiǎn)單,如果子View是一個(gè)ViewGroup,那么就會(huì)重復(fù)以上步驟,如果是一個(gè)View,那么會(huì)直接調(diào)用View#layout方法,根據(jù)以上分析,在該方法內(nèi)部會(huì)設(shè)置view的四個(gè)布局參數(shù),接著調(diào)用onLayout方法,我們看看View#onLayout方法:

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {

}

這是一個(gè)空實(shí)現(xiàn),主要作用是在我們的自定義View中重寫該方法,實(shí)現(xiàn)自定義的布局邏輯。

那么到目前為止,View的布局流程就已經(jīng)全部分析完了。可以看出,布局流程的邏輯相比測(cè)量流程來說,簡(jiǎn)單許多,獲取一個(gè)View的測(cè)量寬高是比較復(fù)雜的,而布局流程則是根據(jù)已經(jīng)獲得的測(cè)量寬高進(jìn)而確定一個(gè)View的四個(gè)位置參數(shù)。在下一篇文章,將會(huì)講述最后一個(gè)流程:繪制流程。希望這篇文章給大家對(duì)View的工作流程的理解帶來幫助,謝謝閱讀。

更多閱讀

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

總結(jié)

以上是生活随笔為你收集整理的android的布局流程,Android View 布局流程(Layout)全面解析的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。