轉自:Constraint Layout Practice and Summary-1
Constraint Layout 2.0 several new features-2
從約束布局發布后,一直沒有將其應用到工作中,只是停留在Demo階段(使用的越多越感覺這種布局更加靈活、高效)。去年下半年到今年的工作中,將一部分原有的相對布局、線性布局和幀布局等切換到使用約束布局。剛開始有一些生疏不適應,元素間的關系相互制約的比較緊密。剛開始用完的感覺就是維護起來比較難,因為關心的要素比以往的布局要多。
本文比較簡單,因為用了一段時間了,便將之前用到的部分進行了簡單的總結,主要是1.0的基本運用,還包含了2.0的一些新特性。對于運用的知識加以理解和深入,并進行內化,最好的方式就是實踐加總結。再者是通過實踐后用文章檢驗糾錯,不斷調整,不斷學習。最好的輸入就是輸出,文章是眾多輸出方式中的一種。
1. What is?
ConstraintLayout
作為最受歡迎的Jetpack
庫之一,TA是一個ViewGroup
。TA的出現主要是為了解決布局嵌套過多的問題,以靈活的方式定位和調整部件。幾個月前2.0也發布了,最新穩定版為2.0.4
(December 17, 2020;Alpha Release:2.1.0-alpha2),從studio 2.3以后布局中默認開始使用Constraint Layout
。
2. Why use?
ConstraintLayout
可以使用扁平視圖層次結構創建復雜的布局,降低頁面布局層級,提升頁面渲染性能。與RelativeLayout
相似,卻有更高的靈活性,并且更易于與Android Studio
的布局編輯器配合使用。
先來感受下約束的效果~~
比如一張圖片在布局中水平對齊,如下代碼
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
在studio中Design
視圖下可以直接進行操作。
視圖中添加了正反約束,約束線像是彈簧一樣彎彎曲曲的拉著imageview
,在兩個約束條件下顯示水平居中。
還可以添加約束偏差,上邊視圖在兩個約束條件之間居中且默認偏差為50%,下圖可以在屬性窗口中進行自由調整。
視圖繪制大致分為測量、布局和繪制三個階段,在繪制過程的每個階段都需要對視圖樹進行自頂向下的遍歷操作,因此視圖層次結構嵌套越多,繪制所需時間和計算功耗就會越多,通過扁平化的層次是解決該問題的方式之一。
官方用LinearLayout
和RelativeLayout
進行布局,然后使用Constraintlayout
做同樣界面的布局結構,用OnFrameMetricsAvailableListener
分析了兩種布局所執行的每次測量和布局操作所花費時間,以收集有關應用界面渲染的逐幀時間信息。結果為ConstraintLayout
在測量和布局階段的性能比傳統布局大約高40%
(藍色為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
)不再進行講解,可以參考官方文檔。
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,當目標控件設置為隱藏(GONE)的時候,gonemargin*邊距值仍然生效。其他對應的屬性查看官方文檔即可,這里不再敘述。
舉個栗子:
假設text2
的左邊約束在text1
的右邊,并給TextView2設置app:layout_goneMarginLeft="16dp"
,效果如下:
當將text1
的可見性設為android:visibility=gone
時,左側的16dp生效了,效果如下:
說明: 如Text1顯示時距左側是6dp,當不顯示時Text2也應該是距離左側6dp(但當Text1顯示時,Text2距離左側是0dp),那么使用goneMarginLeft
則可以滿足該場景。
3.3 Ratio
寬高比,使用app:layout_constraintDimensionRatio
來設置寬高比,該比率有兩種設置方式,如下:
- 浮點值:表示寬度和高度之間的比率(比如:1.0F);
- 比率:'寬度:高度'形式的比率(比如:1:1)。
// formula -> height:width=ratio
h:w=ratio
在使用過程中,剛開始理解有誤,所以主要針對比率進行說明。
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已經設定固值,無法通過先獲取h再通過ratio進行計算。w=100dp(方便計算),h=0dp,當w,2:1時,有w:h=1:2,然后將值代入,100:h=1:2,換算為1h=200,則h=200。
② w=100dp,h=0p,當h,2:1時,有h已經設置為固定值,不能通過ratio計算h值,有w:h=2:1,為2h=100dp,則h=50dp。
3.4 Chain
鏈,在橫軸或或者豎軸上的控件相互約束時,可以組成一個線性的鏈式約束,在一個維度(水平or垂直)上,管理一組控件的方式。
多個view相互引用即創建了一個鏈,第一個為鏈頭,鏈的屬性也由鏈頭的屬性進行控制。水平方向左邊第一個控件為鏈頭,垂直方向上最上邊控件為鏈頭。
總共分為4中樣式,可以通過 app:layout_constraintHorizontal_chainStyle
或 app:layout_constraintVertical_chainStyle
設置鏈式控件的樣式,包含spread
、spread inside
和 packed
。
1) Spread(default)
元素均勻的分散進行分布。
// 省略了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" />
執行以上代碼,效果如下:
2) Spread inside
類似spread模式,但鏈的端點不會分散。即第一個和最后一個視圖固定在鏈兩端的約束邊界上,其余視圖均勻分布。
// 其余代碼省略
...
app:layout_constraintHorizontal_chainStyle="spread_inside"
執行以上代碼,效果如下:
當設置為spread
或spread inside
模式時,可以通過將一個或多個視圖設置為“match constraints”(0dp)
來填充剩余空間。默認情況下,設置為“match constraints”
的每個視圖之間的空間均勻分布。也就引出下一模式:權重模式。
android:layout_width="0dp"
效果如下:
3) Weighted
權重模式,類似于LinearLayout
中的layout_weight
,原理是相同的。權重值最高的視圖獲得的空間最大;相同權重的視圖獲得同樣大小的空間。
// 省略部分代碼
// 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" />
執行代碼,效果如下:
4) Packed
視圖將被包裝在一起(在考慮外邊距之后)。也可以通過更改鏈的頭視圖偏差(Bias,下邊有介紹)調整整條鏈的偏差(左/右或上/下)。
執行代碼,效果如下:
舉栗子:
在開發中有顯示用戶頭像、姓名和編號的區域,有個需求是當有編號時三項全部顯示,沒有編號則只顯示頭像和姓名,那么采用這種方式能容易的實現。
// 省略layout_width、layout_margin*等基礎屬性代碼
<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可以改變兩邊的權重,類似于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
像輔助線,引導線一樣,可以添加垂直或水平的引導線來約束視圖,并且應用用戶看不到該引導線(不顯示在設備上,因為默認設置為View.GONE)。可以根據相對于布局邊緣的dp
單位或百分比在布局中定位引導線。
android_orientation
控制是橫向or縱向。
三種定位方式:
-
layout_constraintGuide_begin
:距離父容器起始位置的距離(對于垂直和水平來講為左側或頂部)。 -
layout_constraintGuide_end
:距離父容器結束位置的距離(右側或底部)。 -
layout_constraintGuide_percent
:距離父容器寬度或高度的百分比,取值范圍0.0-1.0,優先級高于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" />
運行效果如下:
4.2 Barrier
直譯為屏障、分界線,可以用它來約束視圖,不會定義自己的位置。相反,屏障的位置會隨著其中所含視圖的位置而移動。
比如填寫收貨地址的表單中,左側為標簽內容提示文案,右側為輸入文本區域,中間一條垂直分界屏障線,左右兩側據此則可以向中間靠攏。當然實現這個效果也可以使用Guideline
,需要根據具體需求進行選擇。
屬性說明
-
barrierDirection
: 屏障所在的位置,可設置的值有:start、end、left、right、top、bottom
。 -
constraint_referenced_ids
: 屏障引用的控件,可設置多個(用逗號“,”隔開)。
// 省略部分代碼
<!-- 圖中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。
使用時注意兩點:一是組內某個控件設置View.setVisibility(int)
來控制控件顯示/隱藏時是無效的。二是控件存在于多個group中,只有最后一個生效。
屬性說明
-
constraint_referenced_ids
: 以逗號分隔的ID列表來引用控件。
// 控件代碼省略,通過group設置一組控件(id1、id2......)的顯示狀態
<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
: 引用的另一個控件(目標控件)ID值。 -
layout_constraintCircleRadius
: 源控件的中心到其他控件(目標控件)中心的距離。 -
layout_constraintCircleAngle
: 源控件應該處于哪個角度(以度為單位,從0到360)。
// 省略寬高邊距等設置代碼
<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
- 嵌套層級少。當一個復雜布局中,有id強關聯的,要復用比較難。所以需要在開發前進行拆分設計,復雜布局要分拆到不同的文件中。
- 可視化編輯:
ConstraintLayout
還有一個獨立的編輯器,文章開始頁展示了操作效果,只需要托拽就可以完成整個布局。 - 布局高效,輕松應對復雜布局,適配性好,有百分比布局、設置自身寬高比例,各種輔助組件。
- 減少測繪/布局時間,提升效率。性能檢測方式如下:
// 1、記錄每個幀的界面操作 window.addOnFrameMetricsAvailableListener( frameMetricsAvailableListener, frameMetricsHandler); // 2、觸發OnFrameMetricsAvailableListener回調 Window.OnFrameMetricsAvailableListener { _, frameMetrics, _ -> val frameMetricsCopy = FrameMetrics(frameMetrics); // 布局測量采用納秒級 val layoutMeasureDurationNs = frameMetricsCopy.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION);
<font size=3 color=#999999>輸出是最好的輸入方式!</font>