一、簡介
為啥會取這個標題,絕不是為了噱頭,源於最近看了一部國產漫畫一武庚紀2,劇情和畫質都非常棒的良心之作,且看武庚的崛起 。。。
回憶當初稍微復雜的界面,布局的層級嵌套多層,布局最終會解析成 View 的樹形結構,這對渲染性能產生了一定的影響,並且也增大了代碼的維護難度。Google 工程師正是考慮到這一因素,推出了 ConstraintLayout
二、ConstraintLayout
ConstraintLayout 翻譯為 約束布局,也有人把它稱作 增強型的相對布局,由 2016 年 Google I/O 推出。扁平式的布局方式,無任何嵌套,減少布局的層級,優化渲染性能。從支持力度而言,將成為主流布局樣式,完全代替其他布局。有個成語用的非常好,集萬千寵愛於一身,用到這里非常合適,約束集 LinearLayout(線性布局),RelativeLayout(相對布局),百分比布局等的功能於一身,功能強大,使用靈活。紙上得來終覺淺,絕知此事要躬行。讓我們在實際開發的場景中去檢驗約束布局,在實戰中積累經驗。
接下來我會以實際開發中遇到的幾個場景來講解。
題外話,本文需要您對 ConstraintLayout 有一定的熟悉了解度,若您對 ConstraintLayout 不熟悉請鏈接一下地址:
郭霖大神的Android新特性介紹,ConstraintLayout完全解析
1、Circular positioning(圓形定位)
標題后面的中文是自己翻譯的,可能不是很准確。
官方文檔是這么介紹的:
You can constrain a widget center relative to another widget center, at an angle and a distance. This allows you to position a widget on a circle
我是這么理解的,您可以將一個控件的中心以一定的角度和距離約束到另一個控件的中心,相當於在一個圓上放置一個控件。
示例代碼如下:
<Button android:id="@+id/buttonA" ... /> <Button android:id="@+id/buttonB" ... //引用的控件ID app:layout_constraintCircle="@+id/buttonA" //圓半徑 app:layout_constraintCircleRadius="100dp" //偏移圓角度 水平右方向為0逆時針方向旋轉 app:layout_constraintCircleAngle="45" />
效果圖:
圖文並茂,理解起來較容易些。圓形定位使用其他布局是很難實現的(除自定義外),該功能在實際的開發中用的並不多,可以用來實現類似鍾表的效果。該功能只不過是約束布局的冰山一角,且往下看。
2、WRAP_CONTENT : enforcing constraints(強制約束)
官方文檔是這么介紹的:
If a dimension is set to WRAP_CONTENT, in versions before 1.1 they will be treated as a literal dimension -- meaning, constraints will not limit the resulting dimension. While in general this is enough (and faster), in some situations, you might want to use WRAP_CONTENT, yet keep enforcing constraints to limit the resulting dimension. In that case, you can add one of the corresponding attribute
英文一直是我的弱項,我是這么理解的,1.1.0 版本之前是沒有這個功能的,說的是控件的寬設置為 WRAP_CONTENT (包裹內容)時,如果實際寬度超過了約束的最大寬度,那么約束會失效(高同理),為了防止約束失效,增加了以下屬性:
- app:layout_constrainedWidth=”true|false” //默認false
- app:layout_constrainedHeight=”true|false” //默認false
官網並沒有過多說明,那么怎么去理解呢,接下來以app:layout_constrainedWidth屬性來看兩個例子。
a、例子
B 控件位於 A 控件右側與屏幕右側的中間。代碼如下:
<Button android:id="@+id/bt_1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="16dp" android:text="A" app:layout_constraintLeft_toLeftOf="parent"/> <Button android:id="@+id/bt_2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="B" app:layout_constraintLeft_toRightOf="@+id/bt_1" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/bt_1"/>
那么我改變 B 控件的內容,使寬度增大:
通過效果圖可以得出,給 B 控件添加的左右約束失效。為了防止約束失效,在 1.1.0 版本中新增了app:layout_constrainedWidth="true"屬性。注意控件的左右都應該有約束條件, 如下:
app:layout_constraintLeft_toRightOf="@+id/bt_1" //控件的左邊位於xx控件的右邊 app:layout_constraintRight_toRightOf="parent" //控件的右邊位於xx控件的右邊
效果圖如下:
app:layout_constrainedWidth="true" 會導致渲染變慢,變慢時長可忽略不計。
b、例子
產品部的美女提出了這樣的一個需求,看圖:
A,B 兩控件,B 在 A 的右側,隨着 A,B 寬度的增加,B 始終在 A 的右側,當 A ,B 控件的寬度之和大於父控件的寬度時,B 要求被完全顯示,同時 A 被擠壓。我相信大家肯定也遇到過類似的需求,並且相當不好處理,只通過布局文件,不論是使用線性布局,還是相對布局都沒法實現。當初我是通過計算文本的寬度來控制父控件的左右對齊方式來實現的,並且有誤差。那么ConstraintLayout又是怎么只通過布局文件去實現的呢?
代碼如下:
<Button android:id="@+id/bt_1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" app:layout_constrainedWidth="true" // 設置為true app:layout_constraintHorizontal_bias="0" // 設置水平偏好為0 app:layout_constraintHorizontal_chainStyle="packed" //設置鏈樣式 app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@+id/bt_2" app:layout_constraintTop_toTopOf="parent"/> <Button android:id="@+id/bt_2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="BBBBBBBBBBB" app:layout_constrainedWidth="true" app:layout_constraintLeft_toRightOf="@+id/bt_1" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"/>
結合了以下兩個屬性來達到了需求的效果:
- app:layout_constraintHorizontal_chainStyle="packed" //設置鏈樣式
- app:layout_constraintHorizontal_bias="0" // 設置水平偏好為0
接下來簡單介紹下 chain , bias 在后續的百分比布局中會講到。
Chains(鏈)
Chains provide group-like behavior in a single axis (horizontally or vertically). The other axis can be constrained independently.
鏈使我們能夠對一組在水平或豎直方向互相關聯的控件的屬性進行統一管理。成為鏈的條件:一組控件它們通過一個雙向的約束關系鏈接起來。 並且鏈的屬性是由一條鏈的頭結點控制的,如下:
代碼如下:
<Button android:id="@+id/bt_1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="A" //默認樣式 app:layout_constraintHorizontal_chainStyle="spread" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@+id/bt_2" /> <Button android:id="@+id/bt_2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="B" app:layout_constraintLeft_toRightOf="@+id/bt_1" app:layout_constraintRight_toRightOf="parent" />
那么鏈有哪些樣式,以下圖來詮釋:
Weighted 樣式下,寬或高的維度應設置為match_parent(0dp)
3、MATCH_CONSTRAINT dimensions(填充父窗體約束)
官方文檔是這么介紹的:
When a dimension is set to MATCH_CONSTRAINT, the default behavior is to have the resulting size take all the available space. Several additional modifiers are available
在約束布局中寬高的維度 match_parent 被 0dp 代替,默認生成的大小占所有的可用空間。那么有以下幾個屬性可以使用:
-
layout_constraintWidth_min and layout_constraintHeight_min //設置最小尺寸
-
layout_constraintWidth_max and layout_constraintHeight_max //設置最大尺寸
-
layout_constraintWidth_percent and layout_constraintHeight_percent //設置相對於父類的百分比
開發中有這樣一個需求,位於父控件的中間且寬度為父控件的一半,那么我們可以這么去實現:
4、goneMargin(隱藏邊距)
當約束目標的可見性為View.GONE時,還可以通過以下屬性設置不同的邊距值:
- layout_goneMarginStart
- layout_goneMarginEnd
- layout_goneMarginLeft
- layout_goneMarginTop
- layout_goneMarginRight
- layout_goneMarginBottom
如以下例子:
Margins and chains (in 1.1), Optimizer (in 1.1) 略。
5、約束之百分比布局
百分比布局大家肯定不會陌生,由於Android的碎片化非常嚴重,那么屏幕適配將是一件非常令人頭疼的事情,百分比適配也就應運而生,約束布局同樣也可以實現百分比的功能,並且更加強大,靈活。
經常我們會遇到這樣的需求,個人主頁要求頂部的背景圖寬高 16:9 來適配,如下圖:
約束布局的實現方式如下:
<!-- "W,9:16" 同樣的效果 --> <ImageView android:layout_width="0dp" android:layout_height="0dp" android:scaleType="centerCrop" android:src="@mipmap/icon" app:layout_constraintDimensionRatio="H,16:9" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"/>
新增了如下屬性:
app:layout_constraintDimensionRatio="H,16:9"
官網的介紹是這樣的:
You can also define one dimension of a widget as a ratio of the other one. In order to do that, you need to have at least one constrained dimension be set to 0dp (i.e., MATCH_CONSTRAINT), and set the attribute layout_constraintDimensionRatio to a given ratio
意思是說約束布局支持子控件設置寬高比,前提條件是至少需要將寬高中的一個設置為0dp。為了約束一個特定的邊,基於另一個邊的尺寸,可以預先附加W,或H以逗號隔開。
然后需求變動,需要將寬度調整為屏幕的一半:
只需要新增 app:layout_constraintWidth_percent="0.5" 屬性。
接着需要控件左對齊:
同時新增了app:layout_constraintHorizontal_bias="0"屬性。
官網的介紹如下:
The default when encountering such opposite constraints is to center the widget; but you can tweak the positioning to favor one side over another using the bias attributes:
具有相反方向約束的控件,我們可以改變偏好值,來調整位置偏向某一邊。有點類似LinearLayout的weight屬性。
最后需要調整控件距離頂部的高度為父控件高度的20%:
這里用到了虛擬輔助類 Guideline ,同時1.1.0版本還添加了兩個虛擬類Barrier,Group。它們是虛擬對象,並不會占用實際的空間,但可以幫助我們更好更精細地控制布局。綜上的需求變化我們可以相對於父控件任意改變控件大小,控件的位置,從而能夠更好的適配各大屏幕。
5、Guideline
Guideline 與 LinearLayout 類似可以設置水平或垂直方向,android:orientation="horizontal",android:orientation="vertical",水平方向高度為0,垂直方向寬度為0。Guideline 具有以下的三種定位方式:
- layout_constraintGuide_begin 距離父容器起始位置的距離(左側或頂部)
- layout_constraintGuide_end 距離父容器結束位置的距離(右側或底部)
- layout_constraintGuide_percent 距離父容器寬度或高度的百分比
例如,設置一條垂直方向距離父控件左側為100dp的Guideline:
<android.support.constraint.Guideline android:layout_width="wrap_content" android:orientation="vertical" app:layout_constraintGuide_begin="100dp" android:layout_height="wrap_content"/>
效果圖如下:
6、Barrier
Barrier,直譯為障礙、屏障。在約束布局中,可以使用屬性constraint_referenced_ids屬性來引用多個帶約束的組件,從而將它們看作一個整體,Barrier 的介入可以完成很多其他布局不能完成的功能,如下:
開發中有這樣的一個需求,看下圖:
姓名,聯系方式位於 A 區域(隨着文本的寬度變化 A 區域的寬度也隨之變化),B 區域在 A 區域的右側。使用傳統的布局方式實現嵌套過多,布局不夠優雅。那么我們一起來看看約束布局是怎么去實現的:
<TextView android:id="@+id/tv_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="姓名:" app:layout_constraintBottom_toBottomOf="@+id/et_name" app:layout_constraintTop_toTopOf="@+id/et_name"/> <TextView android:id="@+id/tv_contract" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="聯系方式:" app:layout_constraintBottom_toBottomOf="@+id/et_contract" app:layout_constraintTop_toTopOf="@+id/et_contract"/> <EditText android:id="@+id/et_name" android:layout_width="0dp" android:layout_height="wrap_content" android:hint="請輸入姓名" app:layout_constraintLeft_toLeftOf="@+id/barrier" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"/> <EditText android:id="@+id/et_contract" android:layout_width="0dp" android:layout_height="wrap_content" android:hint="請輸入聯系方式" app:layout_constraintLeft_toLeftOf="@+id/barrier" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/et_name"/> <android.support.constraint.Barrier android:id="@+id/barrier" android:layout_width="wrap_content" android:layout_height="wrap_content" app:barrierDirection="right" app:constraint_referenced_ids="tv_name,tv_contract"/>
barrierDirection 指定方向,constraint_referenced_ids引用的控件 id(多個id以逗號隔開)。
7、Group
Group用於控制多個控件的可見性。
e.g:
若 android:visibility="gone" 那么 A,B 控件都會隱藏。
三、總結
實戰中的情形千變萬化,需要大家掌握ConstraintLayout的基本屬性,靈活應用。
相信我約束布局會讓你愛不釋手。崛起拭目以待。
