Constraint Layout Practice and Summary

轉自: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,在兩個約束條件下顯示水平居中。

1.gif

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

2.gif

視圖繪制大致分為測量、布局和繪制三個階段,在繪制過程的每個階段都需要對視圖樹進行自頂向下的遍歷操作,因此視圖層次結構嵌套越多,繪制所需時間和計算功耗就會越多,通過扁平化的層次是解決該問題的方式之一。

官方用LinearLayoutRelativeLayout進行布局,然后使用Constraintlayout做同樣界面的布局結構,用OnFrameMetricsAvailableListener分析了兩種布局所執行的每次測量和布局操作所花費時間,以收集有關應用界面渲染的逐幀時間信息。結果為ConstraintLayout在測量和布局階段的性能比傳統布局大約高40%(藍色為Traditional,紅色為Constraintlayout)。

3.png

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"
}
4.png

3.2 Margin

邊距,這里主要介紹個新特性,就是layout_goneMarginStart、layout_goneMarginLeft、layout_goneMarginTop,當目標控件設置為隱藏(GONE)的時候,gonemargin*邊距值仍然生效。其他對應的屬性查看官方文檔即可,這里不再敘述。

舉個栗子:
假設text2的左邊約束在text1的右邊,并給TextView2設置app:layout_goneMarginLeft="16dp",效果如下:

5.png

當將text1的可見性設為android:visibility=gone時,左側的16dp生效了,效果如下:

6.png

說明: 如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_chainStyleapp:layout_constraintVertical_chainStyle設置鏈式控件的樣式,包含spreadspread insidepacked

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" />

執行以上代碼,效果如下:


7.png
2) Spread inside

類似spread模式,但鏈的端點不會分散。即第一個和最后一個視圖固定在鏈兩端的約束邊界上,其余視圖均勻分布。

// 其余代碼省略
...
app:layout_constraintHorizontal_chainStyle="spread_inside"
9.png

執行以上代碼,效果如下:


8.png

當設置為spreadspread 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" />

執行代碼,效果如下:


10.png
4) Packed

視圖將被包裝在一起(在考慮外邊距之后)。也可以通過更改鏈的頭視圖偏差(Bias,下邊有介紹)調整整條鏈的偏差(左/右或上/下)。

執行代碼,效果如下:


11.png

舉栗子:
在開發中有顯示用戶頭像、姓名和編號的區域,有個需求是當有編號時三項全部顯示,沒有編號則只顯示頭像和姓名,那么采用這種方式能容易的實現。

//  省略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"/>
12.png

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可以改變兩邊的權重,類似于LinearLayoutweight屬性。

屬性說明

  • 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" />

運行效果如下:


13.png

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" />
14.gif

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的位置上,效果如下:

15.png

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>

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容