本文是從網(wǎng)絡(luò)上復(fù)制整理,方便個(gè)人閱讀。正確性不置可否。
為進(jìn)賢等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計(jì)制作服務(wù),及進(jìn)賢網(wǎng)站建設(shè)行業(yè)解決方案。主營(yíng)業(yè)務(wù)為成都網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站、進(jìn)賢網(wǎng)站設(shè)計(jì),以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠(chéng)的服務(wù)。我們深信只要達(dá)到每一位用戶的要求,就會(huì)得到認(rèn)可,從而選擇與我們長(zhǎng)期合作。這樣,我們也可以走得更遠(yuǎn)!
Android布局原則:
盡量多使用LinearLayout和RelativeLayout;FrameLayout使用在布局疊加的時(shí);AbsoluteLayout已經(jīng)廢棄,不要使用;TableLayout已經(jīng)被GridView替代,不建議使用。
在布局層次一樣的情況下,建議使用LinearLayout代替RelativeLayout,因?yàn)長(zhǎng)inearLayout性能要稍高一點(diǎn)。
將可復(fù)用的標(biāo)簽抽取出來并且通過include標(biāo)簽使用。
使用merge標(biāo)簽減少布局的嵌套層次。
使用ViewStub標(biāo)簽加載一些不常用的布局。
一、RelativeLayout和LinearLayout是Android中常用的布局,兩者的使用會(huì)極大的影響程序生成每一幀的性能,因此,正確的使用它們是提升程序性能的重要工作。下面將通過分析它們的源碼來探討其View繪制性能,并得出其正確的使用方法。
通過官方文檔我們知道View的繪制進(jìn)行measure, layout, draw,分別對(duì)應(yīng)onMeasure(), onLayout, onDraw(),而他們的性能差異主要在onMeasure()上。
首先是RelativeLayout:
1 @Override
2 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
3 ......
4 View[] views = mSortedHorizontalChildren;
5 int count = views.length;
6
7 for (int i = 0; i < count; i++) {
8 View child = views[i];
9 if (child.getVisibility() != GONE) {
10 LayoutParams params = (LayoutParams) child.getLayoutParams();
11 int[] rules = params.getRules(layoutDirection);
12
13 applyHorizontalSizeRules(params, myWidth, rules);
14 measureChildHorizontal(child, params, myWidth, myHeight);
15
16 if (positionChildHorizontal(child, params, myWidth, isWrapContentWidth)) {
17 offsetHorizontalAxis = true;
18 }
19 }
20 }
21
22 views = mSortedVerticalChildren;
23 count = views.length;
24 final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
25
26 for (int i = 0; i < count; i++) {
27 View child = views[i];
28 if (child.getVisibility() != GONE) {
29 LayoutParams params = (LayoutParams) child.getLayoutParams();
30
31 applyVerticalSizeRules(params, myHeight);
32 measureChild(child, params, myWidth, myHeight);
33 if (positionChildVertical(child, params, myHeight, isWrapContentHeight)) {
34 offsetVerticalAxis = true;
35 }
36
37 if (isWrapContentWidth) {
38 if (isLayoutRtl()) {
39 if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
40 width = Math.max(width, myWidth - params.mLeft);
41 } else {
42 width = Math.max(width, myWidth - params.mLeft - params.leftMargin);
43 }
44 } else {
45 if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
46 width = Math.max(width, params.mRight);
47 } else {
48 width = Math.max(width, params.mRight + params.rightMargin);
49 }
50 }
51 }
52
53 if (isWrapContentHeight) {
54 if (targetSdkVersion < Build.VERSION_CODES.KITKAT) {
55 height = Math.max(height, params.mBottom);
56 } else {
57 height = Math.max(height, params.mBottom + params.bottomMargin);
58 }
59 }
60
61 if (child != ignore || verticalGravity) {
62 left = Math.min(left, params.mLeft - params.leftMargin);
63 top = Math.min(top, params.mTop - params.topMargin);
64 }
65
66 if (child != ignore || horizontalGravity) {
67 right = Math.max(right, params.mRight + params.rightMargin);
68 bottom = Math.max(bottom, params.mBottom + params.bottomMargin);
69 }
70 }
71 }
72 ......
73 }
根據(jù)上述關(guān)鍵代碼,RelativeLayout分別對(duì)所有子View進(jìn)行兩次measure,橫向縱向分別進(jìn)行一次。
LinearLayout:
1 @Override
2 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
3 if (mOrientation == VERTICAL) {
4 measureVertical(widthMeasureSpec, heightMeasureSpec);
5 } else {
6 measureHorizontal(widthMeasureSpec, heightMeasureSpec);
7 }
8 }
根據(jù)線性布局方向,執(zhí)行不同的方法,這里分析measureVertical方法。
1 void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
2 ......
3 for (int i = 0; i < count; ++i) {
4 ......
5
6 LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
7
8 totalWeight += lp.weight;
9
10 if (heightMode == MeasureSpec.EXACTLY && lp.height == 0 && lp.weight > 0) {
11 // Optimization: don't bother measuring children who are going to use
12 // leftover space. These views will get measured again down below if
13 // there is any leftover space.
14 final int totalLength = mTotalLength;
15 mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
16 skippedMeasure = true;
17 } else {
18 int oldHeight = Integer.MIN_VALUE;
19
20 if (lp.height == 0 && lp.weight > 0) {
21 // heightMode is either UNSPECIFIED or AT_MOST, and this
22 // child wanted to stretch to fill available space.
23 // Translate that to WRAP_CONTENT so that it does not end up
24 // with a height of 0
25 oldHeight = 0;
26 lp.height = LayoutParams.WRAP_CONTENT;
27 }
28
29 // Determine how big this child would like to be. If this or
30 // previous children have given a weight, then we allow it to
31 // use all available space (and we will shrink things later
32 // if needed).
33 measureChildBeforeLayout(
34 child, i, widthMeasureSpec, 0, heightMeasureSpec,
35 totalWeight == 0 ? mTotalLength : 0);
36
37 if (oldHeight != Integer.MIN_VALUE) {
38 lp.height = oldHeight;
39 }
40
41 final int childHeight = child.getMeasuredHeight();
42 final int totalLength = mTotalLength;
43 mTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +
44 lp.bottomMargin + getNextLocationOffset(child));
45
46 if (useLargestChild) {
47 largestChildHeight = Math.max(childHeight, largestChildHeight);
48 }
49 }
50 ......
LinearLayout首先會(huì)對(duì)所有的子View進(jìn)行measure,并計(jì)算totalWeight(所有子View的weight屬性之和),然后判斷子View的weight屬性是否為最大,如為最大則將剩余的空間分配給它。如果不使用weight屬性進(jìn)行布局,則不進(jìn)行第二次measure。
1 // Either expand children with weight to take up available space or
2 // shrink them if they extend beyond our current bounds. If we skipped
3 // measurement on any children, we need to measure them now.
4 int delta = heightSize - mTotalLength;
5 if (skippedMeasure || delta != 0 && totalWeight > 0.0f) {
6 float weightSum = mWeightSum > 0.0f ? mWeightSum : totalWeight;
7
8 mTotalLength = 0;
9
10 for (int i = 0; i < count; ++i) {
11 final View child = getVirtualChildAt(i);
12
13 if (child.getVisibility() == View.GONE) {
14 continue;
15 }
16
17 LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams();
18
19 float childExtra = lp.weight;
20 if (childExtra > 0) {
21 // Child said it could absorb extra space -- give him his share
22 int share = (int) (childExtra * delta / weightSum);
23 weightSum -= childExtra;
24 delta -= share;
25
26 final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
27 mPaddingLeft + mPaddingRight +
28 lp.leftMargin + lp.rightMargin, lp.width);
29
30 // TODO: Use a field like lp.isMeasured to figure out if this
31 // child has been previously measured
32 if ((lp.height != 0) || (heightMode != MeasureSpec.EXACTLY)) {
33 // child was measured once already above...
34 // base new measurement on stored values
35 int childHeight = child.getMeasuredHeight() + share;
36 if (childHeight < 0) {
37 childHeight = 0;
38 }
39
40 child.measure(childWidthMeasureSpec,
41 MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));
42 } else {
43 // child was skipped in the loop above.
44 // Measure for this first time here
45 child.measure(childWidthMeasureSpec,
46 MeasureSpec.makeMeasureSpec(share > 0 ? share : 0,
47 MeasureSpec.EXACTLY));
48 }
49
50 // Child may now not fit in vertical dimension.
51 childState = combineMeasuredStates(childState, child.getMeasuredState()
52 & (MEASURED_STATE_MASK>>MEASURED_HEIGHT_STATE_SHIFT));
53 }
54
55 ......
56 }
57 ......
58 } else {
59 alternativeMaxWidth = Math.max(alternativeMaxWidth,
60 weightedMaxWidth);
61
62
63 // We have no limit, so make all weighted views as tall as the largest child.
64 // Children will have already been measured once.
65 if (useLargestChild && heightMode != MeasureSpec.EXACTLY) {
66 for (int i = 0; i < count; i++) {
67 final View child = getVirtualChildAt(i);
68
69 if (child == null || child.getVisibility() == View.GONE) {
70 continue;
71 }
72
73 final LinearLayout.LayoutParams lp =
74 (LinearLayout.LayoutParams) child.getLayoutParams();
75
76 float childExtra = lp.weight;
77 if (childExtra > 0) {
78 child.measure(
79 MeasureSpec.makeMeasureSpec(child.getMeasuredWidth(),
80 MeasureSpec.EXACTLY),
81 MeasureSpec.makeMeasureSpec(largestChildHeight,
82 MeasureSpec.EXACTLY));
83 }
84 }
85 }
86 }
87 ......
88 }
根據(jù)上面源碼的分析,RelativeLayout將對(duì)所有的子View進(jìn)行兩次measure,而LinearLayout在使用 weight屬性進(jìn)行布局時(shí)也會(huì)對(duì)子View進(jìn)行兩次measure,如果他們位于整個(gè)View樹的頂端時(shí)并可能進(jìn)行多層的嵌套時(shí),位于底層的View將 會(huì)進(jìn)行大量的measure操作,大大降低程序性能。因此,應(yīng)盡量將RelativeLayout和LinearLayout置于View樹的底層,并減 少嵌套。
二、Measure 和 Layout
從整體上來看 Measure 和 Layout 兩個(gè)步驟的執(zhí)行:
樹的遍歷是有序的,由父視圖到子視圖,每一個(gè) ViewGroup 負(fù)責(zé)測(cè)繪它所有的子視圖,而最底層的 View 會(huì)負(fù)責(zé)測(cè)繪自身。
具體分析
measure 過程由measure(int, int)
方法發(fā)起,從上到下有序的測(cè)量 View ,在 measure 過程的最后,每個(gè)視圖存儲(chǔ)了自己的尺寸大小和測(cè)量規(guī)格。 layout 過程由layout(int, int, int, int)
方法發(fā)起,也是自上而下進(jìn)行遍歷。在該過程中,每個(gè)父視圖會(huì)根據(jù) measure 過程得到的尺寸來擺放自己的子視圖。
measure 過程會(huì)為一個(gè)View及所有子節(jié)點(diǎn)的 mMeasuredWidth 和 mMeasuredHeight 變量賦值,該值可以通過 getMeasuredWidth()
和getMeasuredHeight()
方
法獲得。而且這兩個(gè)值必須在父視圖約束范圍之內(nèi),這樣才可以保證所有的父視圖都接收所有子視圖的測(cè)量。如果子視圖對(duì)于 Measure
得到的大小不滿意的時(shí)候,父視圖會(huì)介入并設(shè)置測(cè)量規(guī)則進(jìn)行第二次 measure。比如,父視圖可以先根據(jù)未給定的 dimension
去測(cè)量每一個(gè)子視圖,如果最終子視圖的未約束尺寸太大或者太小的時(shí)候,父視圖就會(huì)使用一個(gè)確切的大小再次對(duì)子視圖進(jìn)行 measure 。
measure 過程傳遞尺寸的兩個(gè)類
ViewGroup.LayoutParams (View 自身的布局參數(shù))
MeasureSpecs 類(父視圖對(duì)子視圖的測(cè)量要求)
ViewGroup.LayoutParams
這個(gè)類我們很常見,就是用來指定視圖的高度和寬度等參數(shù)。對(duì)于每個(gè)視圖的 height 和 width,你有以下選擇:
MATCH_PARENT 表示子視圖希望和父視圖一樣大(不包含padding值)
WRAP_CONTENT 表示視圖為正好能包裹其內(nèi)容大小(包含padding值)
ViewGroup 的子類有其對(duì)應(yīng)的 ViewGroup.LayoutParams 的子類。比如 RelativeLayout 擁有的 ViewGroup.LayoutParams 的子類 RelativeLayoutParams。
有時(shí)我們需要使用 view.getLayoutParams() 方法獲取一個(gè)視圖 LayoutParams
,然后進(jìn)行強(qiáng)轉(zhuǎn),但由于不知道其具體類型,可能會(huì)導(dǎo)致強(qiáng)轉(zhuǎn)錯(cuò)誤。其實(shí)該方法得到的就是其所在父視圖類型的 LayoutParams,比如 View
的父控件為 RelativeLayout,那么得到的 LayoutParams 類型就為 RelativeLayoutParams。
MeasureSpecs
測(cè)量規(guī)格,包含測(cè)量要求和尺寸的信息,有三種模式:
UNSPECIFIED
父視圖不對(duì)子視圖有任何約束,它可以達(dá)到所期望的任意尺寸。比如ListView、ScrollView,一般自定義View中用不到,
EXACTLY
父視圖為子視圖指定一個(gè)確切的尺寸,而且無論子視圖期望多大,它都必須在該指定大小的邊界內(nèi),對(duì)應(yīng)的屬性為 match_parent 或具體指,比如 100dp,父控件可以通過MeasureSpec.getSize(measureSpec)
直接得到子控件的尺寸。
AT_MOST
父視圖為子視圖指定一個(gè)最大尺寸。子視圖必須確保它自己所有子視圖可以適應(yīng)在該尺寸范圍內(nèi),對(duì)應(yīng)的屬性為
wrap_content,這種模式下,父控件無法確定子 View
的尺寸,只能由子控件自己根據(jù)需求去計(jì)算自己的尺寸,這種模式就是我們自定義視圖需要實(shí)現(xiàn)測(cè)量邏輯的情況。
三、include
在實(shí)際開發(fā)中,我們經(jīng)常會(huì)遇到一些共用的UI組件,比如帶返回按鈕的導(dǎo)航欄,如果為每一個(gè)xml文件都設(shè)置這部分布局,一是重復(fù)的工作量大,二是如果有變更,那么每一個(gè)xml文件都得修改。不過,我們可以將這些共用的組件抽取出來單獨(dú)放到一個(gè)xml文件中,然后使用< include />標(biāo)簽導(dǎo)入到相應(yīng)布局
四、merge
< merge />標(biāo)簽的作用是合并UI布局,使用該標(biāo)簽?zāi)芙档蚒I布局的嵌套層次。該標(biāo)簽的主要使用場(chǎng)景主要包括兩個(gè),第一種情況是當(dāng)xml文件的根布局是FrameLayout時(shí),可以用merge作為根節(jié)點(diǎn)。理由是因?yàn)锳ctivity的內(nèi)容布局中,默認(rèn)就用了一個(gè)FrameLayout作為xml布局根節(jié)點(diǎn)的父節(jié)點(diǎn);第二種情況是當(dāng)用include標(biāo)簽導(dǎo)入一個(gè)共用布局時(shí),如果父布局和子布局根節(jié)點(diǎn)為同一類型,可以使用merge將子節(jié)點(diǎn)布局的內(nèi)容合并包含到父布局中,這樣就可以減少一級(jí)嵌套層次。這樣就降低了布局嵌套層次。
五、ViewStub
ViewStub是Android布局優(yōu)化中一個(gè)很不錯(cuò)的標(biāo)簽/控件,直接繼承自View。但是真正用的可能不多。當(dāng)對(duì)一個(gè)ViewStub調(diào)用inflate()方法或設(shè)置它可見時(shí),系統(tǒng)會(huì)加載在ViewStub標(biāo)簽中引入的我們自己定義的View,然后填充在父布 局當(dāng)中。也就是說,在對(duì)ViewStub調(diào)用inflate()方法或設(shè)置visible之前,它是不占用布局空間和系統(tǒng)資源的。它的使用場(chǎng)景可以是在我 們需要加載并顯示一些不常用的View時(shí),例如一些網(wǎng)絡(luò)異常的提示信息等。
本文名稱:Android布局
分享地址:http://aaarwkj.com/article38/jeggsp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站導(dǎo)航、App開發(fā)、建站公司、微信公眾號(hào)、軟件開發(fā)、企業(yè)建站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)