轉(zhuǎn)自:Constraint Layout Practice and Summary-1
Constraint Layout 2.0 several new features-2
從約束布局發(fā)布后,一直沒有將其應(yīng)用到工作中,只是停留在Demo階段(使用的越多越感覺這種布局更加靈活、高效)。去年下半年到今年的工作中,將一部分原有的相對布局、線性布局和幀布局等切換到使用約束布局。剛開始有一些生疏不適應(yīng),元素間的關(guān)系相互制約的比較緊密。剛開始用完的感覺就是維護(hù)起來比較難,因?yàn)殛P(guān)心的要素比以往的布局要多。
本文比較簡單,因?yàn)橛昧艘欢螘r間了,便將之前用到的部分進(jìn)行了簡單的總結(jié),主要是1.0的基本運(yùn)用,還包含了2.0的一些新特性。對于運(yùn)用的知識加以理解和深入,并進(jìn)行內(nèi)化,最好的方式就是實(shí)踐加總結(jié)。再者是通過實(shí)踐后用文章檢驗(yàn)糾錯,不斷調(diào)整,不斷學(xué)習(xí)。最好的輸入就是輸出,文章是眾多輸出方式中的一種。
1. What is?
ConstraintLayout作為最受歡迎的Jetpack庫之一,TA是一個ViewGroup。TA的出現(xiàn)主要是為了解決布局嵌套過多的問題,以靈活的方式定位和調(diào)整部件。幾個月前2.0也發(fā)布了,最新穩(wěn)定版為2.0.4(December 17, 2020;Alpha Release:2.1.0-alpha2),從studio 2.3以后布局中默認(rèn)開始使用Constraint Layout。
2. Why use?
ConstraintLayout可以使用扁平視圖層次結(jié)構(gòu)創(chuàng)建復(fù)雜的布局,降低頁面布局層級,提升頁面渲染性能。與RelativeLayout相似,卻有更高的靈活性,并且更易于與Android Studio的布局編輯器配合使用。
先來感受下約束的效果~~
比如一張圖片在布局中水平對齊,如下代碼
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
在studio中Design視圖下可以直接進(jìn)行操作。
視圖中添加了正反約束,約束線像是彈簧一樣彎彎曲曲的拉著imageview,在兩個約束條件下顯示水平居中。

還可以添加約束偏差,上邊視圖在兩個約束條件之間居中且默認(rèn)偏差為50%,下圖可以在屬性窗口中進(jìn)行自由調(diào)整。

視圖繪制大致分為測量、布局和繪制三個階段,在繪制過程的每個階段都需要對視圖樹進(jìn)行自頂向下的遍歷操作,因此視圖層次結(jié)構(gòu)嵌套越多,繪制所需時間和計算功耗就會越多,通過扁平化的層次是解決該問題的方式之一。
官方用LinearLayout和RelativeLayout進(jìn)行布局,然后使用Constraintlayout做同樣界面的布局結(jié)構(gòu),用OnFrameMetricsAvailableListener分析了兩種布局所執(zhí)行的每次測量和布局操作所花費(fèi)時間,以收集有關(guān)應(yīng)用界面渲染的逐幀時間信息。結(jié)果為ConstraintLayout在測量和布局階段的性能比傳統(tǒng)布局大約高40%(藍(lán)色為Traditional,紅色為Constraintlayout)。

3. Usage
本文使用Android Studio 4.0(Build #AI-193.6911.18.40.6514223)和Constraintlayout:2.0.4
對于相對位置(layout_constraintLeft_toLeftOf、layout_constraintLeft_toRightOf等),邊距(android:layout_marginStart、android:layout_marginEnd、android:layout_marginRight、layout_goneMarginLeft等),水平垂直居中,水平垂直偏移(layout_constraintHorizontal_bias、layout_constraintVertical_bias)不再進(jìn)行講解,可以參考官方文檔。
3.1 Add ConstraintLayout to your project
// Add the dependencies for the artifacts you need in the build.gradle file for your app or module:
// Need Sync Project with Gradle Files
dependencies {
// latest version 2.1.0-alpha2
// December 17, 2020
implementation "androidx.constraintlayout:constraintlayout:2.0.4"
}

3.2 Margin
邊距,這里主要介紹個新特性,就是layout_goneMarginStart、layout_goneMarginLeft、layout_goneMarginTop,當(dāng)目標(biāo)控件設(shè)置為隱藏(GONE)的時候,gonemargin*邊距值仍然生效。其他對應(yīng)的屬性查看官方文檔即可,這里不再敘述。
舉個栗子:
假設(shè)text2的左邊約束在text1的右邊,并給TextView2設(shè)置app:layout_goneMarginLeft="16dp",效果如下:

當(dāng)將text1的可見性設(shè)為android:visibility=gone時,左側(cè)的16dp生效了,效果如下:

說明: 如Text1顯示時距左側(cè)是6dp,當(dāng)不顯示時Text2也應(yīng)該是距離左側(cè)6dp(但當(dāng)Text1顯示時,Text2距離左側(cè)是0dp),那么使用goneMarginLeft則可以滿足該場景。
3.3 Ratio
寬高比,使用app:layout_constraintDimensionRatio來設(shè)置寬高比,該比率有兩種設(shè)置方式,如下:
- 浮點(diǎn)值:表示寬度和高度之間的比率(比如:1.0F);
- 比率:'寬度:高度'形式的比率(比如:1:1)。
// formula -> height:width=ratio
h:w=ratio
在使用過程中,剛開始理解有誤,所以主要針對比率進(jìn)行說明。
1、app:layout_constraintDimensionRatio="16:9"表示寬:高=16:9
2、app:layout_constraintDimensionRatio="w,3:4"表示寬:高=4:3,w表示分母是寬。
3、app:layout_constraintDimensionRatio="h,16:9"表示寬:高=16:9,h表示分母是高。
4、指定值情況下:
舉栗:
① w已經(jīng)設(shè)定固值,無法通過先獲取h再通過ratio進(jìn)行計算。w=100dp(方便計算),h=0dp,當(dāng)w,2:1時,有w:h=1:2,然后將值代入,100:h=1:2,換算為1h=200,則h=200。
② w=100dp,h=0p,當(dāng)h,2:1時,有h已經(jīng)設(shè)置為固定值,不能通過ratio計算h值,有w:h=2:1,為2h=100dp,則h=50dp。
3.4 Chain
鏈,在橫軸或或者豎軸上的控件相互約束時,可以組成一個線性的鏈?zhǔn)郊s束,在一個維度(水平or垂直)上,管理一組控件的方式。
多個view相互引用即創(chuàng)建了一個鏈,第一個為鏈頭,鏈的屬性也由鏈頭的屬性進(jìn)行控制。水平方向左邊第一個控件為鏈頭,垂直方向上最上邊控件為鏈頭。
總共分為4中樣式,可以通過 app:layout_constraintHorizontal_chainStyle或 app:layout_constraintVertical_chainStyle設(shè)置鏈?zhǔn)娇丶臉邮?,包?code>spread、spread inside 和 packed 。
1) Spread(default)
元素均勻的分散進(jìn)行分布。
// 省略了layout_width、layout_height等屬性
// textX為head,鏈的屬性由鏈頭控制
<TextView
android:id="@+id/textX"
android:text="A"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/textD"
/>
<TextView
android:id="@+id/textD"
android:text="Better"
app:layout_constraintLeft_toRightOf="@id/textX"
app:layout_constraintRight_toLeftOf="@id/textF" />
<TextView
android:id="@+id/textF"
android:text="You"
app:layout_constraintLeft_toRightOf="@id/textD"
app:layout_constraintRight_toRightOf="parent" />
執(zhí)行以上代碼,效果如下:

2) Spread inside
類似spread模式,但鏈的端點(diǎn)不會分散。即第一個和最后一個視圖固定在鏈兩端的約束邊界上,其余視圖均勻分布。
// 其余代碼省略
...
app:layout_constraintHorizontal_chainStyle="spread_inside"

執(zhí)行以上代碼,效果如下:

當(dāng)設(shè)置為spread或spread inside模式時,可以通過將一個或多個視圖設(shè)置為“match constraints”(0dp)來填充剩余空間。默認(rèn)情況下,設(shè)置為“match constraints”的每個視圖之間的空間均勻分布。也就引出下一模式:權(quán)重模式。
android:layout_width="0dp"
效果如下:
3) Weighted
權(quán)重模式,類似于LinearLayout中的layout_weight,原理是相同的。權(quán)重值最高的視圖獲得的空間最大;相同權(quán)重的視圖獲得同樣大小的空間。
// 省略部分代碼
// chain style is spread or spread_inside
<TextView
android:id="@+id/textX"
android:layout_width="wrap_content"
app:layout_constraintHorizontal_chainStyle="spread" />
// 文本D占用2份可用空白空間
<TextView
android:id="@+id/textD"
android:layout_width="0dp"
app:layout_constraintHorizontal_weight="2" />
// 文本F占用2份可用空白空間
<TextView
android:id="@+id/textF"
android:layout_width="0dp"
app:layout_constraintHorizontal_weight="2" />
執(zhí)行代碼,效果如下:

4) Packed
視圖將被包裝在一起(在考慮外邊距之后)。也可以通過更改鏈的頭視圖偏差(Bias,下邊有介紹)調(diào)整整條鏈的偏差(左/右或上/下)。
執(zhí)行代碼,效果如下:

舉栗子:
在開發(fā)中有顯示用戶頭像、姓名和編號的區(qū)域,有個需求是當(dāng)有編號時三項(xiàng)全部顯示,沒有編號則只顯示頭像和姓名,那么采用這種方式能容易的實(shí)現(xiàn)。
// 省略layout_width、layout_margin*等基礎(chǔ)屬性代碼
<ImageView
android:id="@+id/iv_avatar" />
<TextView
android:id="@+id/tv_name"
app:layout_constraintLeft_toRightOf="@id/iv_avatar"
app:layout_constraintTop_toTopOf="@id/iv_avatar"
app:layout_constraintBottom_toBottomOf="@id/iv_avatar"
app:layout_constraintBottom_toTopOf="@id/tv_code"
app:layout_constraintVertical_chainStyle="packed"
tools:text="姓名" />
<ImageView
android:id="@+id/iv_switch_identity"
app:layout_constraintBottom_toBottomOf="@id/tv_name"
app:layout_constraintLeft_toRightOf="@id/tv_name"
app:layout_constraintTop_toTopOf="@id/tv_name" />
<TextView
android:id="@+id/tv_code"
app:layout_constraintLeft_toRightOf="@id/iv_avatar"
app:layout_constraintTop_toBottomOf="@id/tv_name"
app:layout_constraintBottom_toBottomOf="parent"/>

3.5 Center
居中,這個比較有特色的是彼此互為沖突即可居中。
// 水平居中
<TextView
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent/>
// 垂直居中
<TextView
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent/>
3.6 Bias
定義控件的方向偏差,使用Bias可以改變兩邊的權(quán)重,類似于LinearLayout的weight屬性。
屬性說明
-
layout_constraintHorizontal_bias: 水平偏差,取值范圍:0.0~1.0。 -
layout_constraintVertical_bias: 垂直偏差,取值范圍:0.0~1.0。
<TextView
// 水平占比30%
app:layout_constraintHorizontal_bias="0.3"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent/>
4. 輔助工具
4.1 Guideline
像輔助線,引導(dǎo)線一樣,可以添加垂直或水平的引導(dǎo)線來約束視圖,并且應(yīng)用用戶看不到該引導(dǎo)線(不顯示在設(shè)備上,因?yàn)槟J(rèn)設(shè)置為View.GONE)。可以根據(jù)相對于布局邊緣的dp單位或百分比在布局中定位引導(dǎo)線。
android_orientation控制是橫向or縱向。
三種定位方式:
-
layout_constraintGuide_begin:距離父容器起始位置的距離(對于垂直和水平來講為左側(cè)或頂部)。 -
layout_constraintGuide_end:距離父容器結(jié)束位置的距離(右側(cè)或底部)。 -
layout_constraintGuide_percent:距離父容器寬度或高度的百分比,取值范圍0.0-1.0,優(yōu)先級高于begin和end。
<TextView
... // 省略代碼
app:layout_constraintRight_toLeftOf="@id/guideline"
app:layout_constraintTop_toTopOf="parent" />
<TextView
... // 省略代碼
app:layout_constraintLeft_toRightOf="@id/guideline"
app:layout_constraintTop_toTopOf="parent" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
運(yùn)行效果如下:

4.2 Barrier
直譯為屏障、分界線,可以用它來約束視圖,不會定義自己的位置。相反,屏障的位置會隨著其中所含視圖的位置而移動。
比如填寫收貨地址的表單中,左側(cè)為標(biāo)簽內(nèi)容提示文案,右側(cè)為輸入文本區(qū)域,中間一條垂直分界屏障線,左右兩側(cè)據(jù)此則可以向中間靠攏。當(dāng)然實(shí)現(xiàn)這個效果也可以使用Guideline,需要根據(jù)具體需求進(jìn)行選擇。
屬性說明
-
barrierDirection: 屏障所在的位置,可設(shè)置的值有:start、end、left、right、top、bottom。 -
constraint_referenced_ids: 屏障引用的控件,可設(shè)置多個(用逗號“,”隔開)。
// 省略部分代碼
<!-- 圖中width變化為50和150切換 -->
<TextView
android:id="@+id/tvName"
android:layout_width="150dp"
android:text="name"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tvSchoolAddress"
android:text="SchoolAddress"
app:layout_constraintTop_toBottomOf="@id/tvName" />
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="right"
app:constraint_referenced_ids="tvName,tvSchoolAddress" />
<TextView
android:id="@+id/tvClassTime"
android:text="ClassTime"
app:layout_constraintLeft_toRightOf="@id/barrier" />

4.3 Group
這個很容易理解, 用于控制一組控件的是否顯示,繼承于ConstraintHelper。
使用時注意兩點(diǎn):一是組內(nèi)某個控件設(shè)置View.setVisibility(int)來控制控件顯示/隱藏時是無效的。二是控件存在于多個group中,只有最后一個生效。
屬性說明
-
constraint_referenced_ids: 以逗號分隔的ID列表來引用控件。
// 控件代碼省略,通過group設(shè)置一組控件(id1、id2......)的顯示狀態(tài)
<androidx.constraintlayout.widget.Group
android:id="@+id/group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="id1,id2"
android:visibility="invisible"
/>
4.4 Circular positioning
圓形定位,以角度和半徑距離約束控件中心相對于另一個控件中心。
屬性說明
-
layout_constraintCircle: 引用的另一個控件(目標(biāo)控件)ID值。 -
layout_constraintCircleRadius: 源控件的中心到其他控件(目標(biāo)控件)中心的距離。 -
layout_constraintCircleAngle: 源控件應(yīng)該處于哪個角度(以度為單位,從0到360)。
// 省略寬高邊距等設(shè)置代碼
<ImageView
android:id="@+id/iv_header"
android:layout_marginLeft="19dp"
android:layout_marginTop="19dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_gender"
app:layout_constraintCircle="@id/iv_header"
app:layout_constraintCircleRadius="35dp"
app:layout_constraintCircleAngle="140"/>
圖片iv_gender在圖片iv_header的140度角半徑為35dp的位置上,效果如下:

5. Summary
- 嵌套層級少。當(dāng)一個復(fù)雜布局中,有id強(qiáng)關(guān)聯(lián)的,要復(fù)用比較難。所以需要在開發(fā)前進(jìn)行拆分設(shè)計,復(fù)雜布局要分拆到不同的文件中。
- 可視化編輯:
ConstraintLayout還有一個獨(dú)立的編輯器,文章開始頁展示了操作效果,只需要托拽就可以完成整個布局。 - 布局高效,輕松應(yīng)對復(fù)雜布局,適配性好,有百分比布局、設(shè)置自身寬高比例,各種輔助組件。
- 減少測繪/布局時間,提升效率。性能檢測方式如下:
// 1、記錄每個幀的界面操作 window.addOnFrameMetricsAvailableListener( frameMetricsAvailableListener, frameMetricsHandler); // 2、觸發(fā)OnFrameMetricsAvailableListener回調(diào) Window.OnFrameMetricsAvailableListener { _, frameMetrics, _ -> val frameMetricsCopy = FrameMetrics(frameMetrics); // 布局測量采用納秒級 val layoutMeasureDurationNs = frameMetricsCopy.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION);
<font size=3 color=#999999>輸出是最好的輸入方式!</font>