不知道從什么時候開始,Android 死丟丟已經默認使用約束布局 ConstraintLayout 作為默認布局了,但是懶癌發作一直不想學習,每次都換成 LinearLayout,這次也忘記了為啥開始學習這個東西,學完發現還挺爽……寫個筆記記錄一下,哈哈
正文開始~
相對布局
屬性集合
類似 RelativeLayout ,使用相對位置的屬性來互相約束位置。具體的屬性以及使用方式也類似 RelativeLayout,默認像 FrameLayout 一樣堆疊在一起,使用屬性講層級關系區分開:
layout_constraintLeft_toLeftOf 當前控件的左側與某個控件的左側對齊
layout_constraintLeft_toRightOf 當前控件的左側與某個控件的右側對齊
layout_constraintRight_toLeftOf 當前控件的右側與某個控件的左側對齊
layout_constraintRight_toRightOf 當前控件的右側與某個控件的右側對齊
layout_constraintStart_toEndOf 同上
layout_constraintStart_toStartOf
layout_constraintEnd_toStartOf
layout_constraintEnd_toEndOf
layout_constraintTop_toTopOf 當前控件與某個控件頂端對齊
layout_constraintTop_toBottomOf 即當前控件某個控件的下面
layout_constraintBottom_toTopOf 即當前控件在某個控件的上面
layout_constraintBottom_toBottomOf 當前控件與某個控件底部對齊
layout_constraintBaseline_toBaselineOf 文本基線對齊
具體示例
<!-- 居中對齊實現方式 -->
<!-- 上下左右全部受 parent 約束,最后的效果就是「居中對齊」 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="居中對齊"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
<!-- 同理,左右受 parent 約束,效果就是「水平居中對齊」-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="水平居中對齊"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
<!-- 同理,上下受 parent 約束,效果就是「垂直居中對齊」-->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="垂直居中對齊"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
居中對齊很好理解,下邊我們來寫一個正常的 item 布局看看:
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp">
<ImageView
android:id="@+id/iv_logo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@mipmap/ic_launcher" />
<!-- 設置標題名稱 View 的左側邊緣位于 logo 的右側 -->
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="蝦吃蝦涮(華貿店)"
app:layout_constraintLeft_toRightOf="@+id/iv_logo"
app:layout_constraintTop_toTopOf="parent" />
<!-- 設置價格 View 的底部靠近父布局,且頂部參考 titleView,同時左側與 titleView 對齊 -->
<TextView
android:id="@+id/tv_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="¥64/人"
android:textSize="13sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="@id/tv_title"
app:layout_constraintTop_toBottomOf="@id/tv_title" />
<!-- 設置 distanceView 緊貼屏幕右側,且頂部與 priceView 對齊-->
<TextView
android:id="@+id/tv_distance"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1.1km"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_price" />
<!-- 設置 areaView 頂部與左側都參考 priceView,底部位置參考 ivLogo-->
<TextView
android:id="@+id/tv_area"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="朝陽區 大望路"
android:textSize="13sp"
app:layout_constraintBottom_toBottomOf="@id/iv_logo"
app:layout_constraintLeft_toLeftOf="@id/tv_price"
app:layout_constraintTop_toBottomOf="@id/tv_price" />
<!-- 設置 hotView 緊貼屏幕右側,且頂部與 areaView 頂部對齊-->
<TextView
android:id="@+id/tv_curr_hot"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="當前人氣89"
android:textSize="13sp"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/tv_area" />
<!-- dividerView 位于整個布局的最底部,且始終位于 ivLogo 底部,并保持一定距離 -->
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:layout_marginTop="7dp"
android:background="@android:color/darker_gray"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_logo" />
</android.support.constraint.ConstraintLayout>
可以發現使用約束布局實現比普通的 RL 實現還要簡單,界面完全實現扁平化,沒有任何嵌套。如果使用 LL 或者 RL 來實現同樣的效果,代碼要復雜多少想必不用我多說。
Bias 偏向
以上的內容就是基本使用了,把上下左右各種參考、依賴關系搞明白,本身沒有多么復雜,使用起來也和 RL 差不多,下面來介紹一些新花樣。
bias
很好理解,正如其英文本意一樣,它表達的是偏移。當某一布局同時受兩個相反方向的約束力時,該布局就會處于約束它的那兩個力量的正中央。而 layout_constraintHorizontal_bias
與 layout_constraintVertical_bias
就是用在這種時候,用來將某一方向的約束力減弱。來自兩側的約束力可以為 parent,也可以是普通 View。
文字描述可能有點抽象,具體布局文件還是更好理解一些:
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="this is a text"
app:layout_constraintLeft_toLeftOf="parent" />
<TextView
android:id="@+id/tv2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="this is a text"
app:layout_constraintHorizontal_bias="0.3"
app:layout_constraintLeft_toRightOf="@id/tv1"
app:layout_constraintRight_toLeftOf="@id/tv3" />
<TextView
android:id="@+id/tv3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="this is a text"
app:layout_constraintRight_toRightOf="parent" />
</android.support.constraint.ConstraintLayout>
布局很簡單,三個 TextView 并排顯示,左右兩個分別緊貼父布局,中間一個受左右兩側布局約束,本來應該是位于兩個 TextView 正中央,但是由于設置了 layout_constraintHorizontal_bias
小于 0.5,所以最后效果中間的 TextView 整體偏向左側,展示如下圖:
Circle 布局
這個看上去很厲害的!可以令 B 布局以 A 布局為圓心,然后用角度和半徑距離來約束兩個布局的位置。廢話不多說,上圖:
這個也很好理解,主要有三個屬性:
layout_constraintCircle : 當前布局以哪個布局為圓心
layout_constraintCircleRadius :半徑
layout_constraintCircleAngle : 擺放角度
我反正是給谷歌跪了……
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="@+id/tv_center"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:text="Circle Center"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<!-- 「測試布局」以 tv_center 為圓心,位于其 135° 方向的 100dp 處 -->
<TextView
android:text="測試布局"
app:layout_constraintCircle="@id/tv_center"
app:layout_constraintCircleAngle="135"
app:layout_constraintCircleRadius="100dp"
android:textColor="@android:color/black"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</android.support.constraint.ConstraintLayout>
替代 MATCH_PARENT 的 MATCH_CONSTRAINT
在約束布局中,由于布局受各方約束控制,也就沒有所謂的「match_parent」了。隨之而來的需求則是,左邊有個布局約束我,右邊還有個布局約束我,然后我就想充滿剩余的全部位置,「match_constraint」也就應運而生了。
說起來復雜,其實只需要把對應的 View 寬高設置為 0dp 即可,該 View 就會占據上剩余的所有可用空間。在這種情況下,谷歌給我們提供了幾個額外的屬性:
layout_constraintWidth_min 寬度最小值
layout_constraintHeight_min 高度最小值
layout_constraintWidth_max 寬度最大值
layout_constraintHeight_max 高度最大值
layout_constraintWidth_percent 寬度占剩余位置的百分比
layout_constraintHeight_percent 高度占剩余位置的百分比
具體示例如下:
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test Button"
app:layout_constraintLeft_toLeftOf="parent" />
<Button
android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintRight_toRightOf="parent" />
<Button
android:id="@+id/btn3"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constrainedWidth="true"
android:text="Button"
app:layout_constraintWidth_min="wrap"
app:layout_constraintWidth_max="wrap"
app:layout_constraintWidth_percent="0.3"
app:layout_constraintLeft_toRightOf="@id/btn1"
app:layout_constraintRight_toLeftOf="@id/btn2" />
</android.support.constraint.ConstraintLayout>
Chains 鏈
如果幾個不同的 View 兩兩發生關聯,如下圖,則這幾個 View 構成了一個 Chains(鏈)。
具體布局代碼如下:
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/btn2" />
<Button
android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintLeft_toRightOf="@id/btn1"
app:layout_constraintRight_toLeftOf="@id/btn3" />
<Button
android:id="@+id/btn3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintLeft_toRightOf="@id/btn2"
app:layout_constraintRight_toRightOf="parent" />
</android.support.constraint.ConstraintLayout>
這樣這三個 Button 就形成了一個橫向的 Chain,在這個鏈的最左側的元素成為鏈頭,我們可以在其身上設置一些屬性,來決定這個鏈的展示效果:
該屬性為:
layout_constraintHorizontal_chainStyle
layout_constraintVertical_chainStyle
其取值可以為:spread、spread_inside、packed。
具體樣式展示如下:
- spread,基本上就是按照權重等分
-
spread_inside,也是等分展示,但是兩側吸附
image -
packed,整條鏈擠在一起,居中展示
image
官網有一個圖來展示不同樣式的 Chains,可以參考一下,也很形象:
虛擬輔助視圖
與以往的 ViewGroup 不同,ConstraintLayout 還提供了幾種輔助頁面繪制的布局,這種布局一般表現為引導線之類,不會在頁面上繪制,但是可以通過占位的方式,成為不同布局的約束條件。
GuideLine
顧名思義,GuideLine 可以創建基于父布局 ConstraintLayout 的水平或者垂直準線,從而幫助開發者進行布局定位。
這個布局有四個基本屬性,依次為:
orientation 如上所述,用來表示是垂直方向還是豎直方向
layout_constraintGuide_begin 距離父親的起始位置
layout_constraintGuide_end 距離父親的結束位置
layout_constraintGuide_percent 距離父親的位置,用百分比表示
經過試驗,percent 優先級最高,其次是 begin,最后是 end,一般來講使用 percent 就足夠了
xml 以及對應的頁面效果如下:
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- 假設說現在需要將 ImageView 擺放到右下角的位置,就可以使用 GL 輔助實現-->
<android.support.constraint.Guideline
android:id="@+id/gl_vertical"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.8"/>
<android.support.constraint.Guideline
android:id="@+id/gl_horizontal"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.8" />
<ImageView
android:src="@mipmap/ic_launcher"
app:layout_constraintLeft_toRightOf="@id/gl_vertical"
app:layout_constraintTop_toBottomOf="@id/gl_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</android.support.constraint.ConstraintLayout>
Barrier
與 GuideLine 差不多,但是比它更靈活,可以用來約束多個布局,且自動匹配最大最小值進行約束。
Barrier 有兩個基本屬性:
-
barrierDirection
取值可為 top, bottom, left, right, start, end
用于約定柵欄攔截的 View 方向,假設說要攔截的 View 在右側,這個屬性就應該為 right 或者 end
-
constraint_referenced_ids
被柵欄保護,屏蔽起來的 View 集合,直接輸入 viewId,用逗號分隔即可;barrier 會根據寬度或者高度最大的那個 View 來設置柵欄的邊界
可能會有些抽象,我們在開發時可能會遇到一種比較蛋疼的需求:
姓名、性別、出生日期、手機號等字段從上到下一字排開,但是每個字段對應的值要保證彼此左側對齊
講道理以前這種布局我一直不知道怎么畫,但是現在有了 barrier
以后這問題就迎刃而解了。我們可以用 barrier 將左側的那些字段與右側的值攔截開,barrier 會自動識別最寬的那個字段,并將之作為 barrier 的寬度,之后每個值都用 barrier 來制造約束就可以了。
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="姓名:"/>
<TextView
android:id="@+id/tv_gender"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/tv_name"
android:text="性別:"/>
<TextView
android:id="@+id/tv_phone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/tv_gender"
android:text="手機號:"/>
<TextView
android:id="@+id/tv_birthday"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/tv_phone"
android:text="出生日期:"/>
<android.support.constraint.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="end"
app:constraint_referenced_ids="tv_name,tv_phone,tv_gender,tv_birthday"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@id/barrier"
android:text="易烊千璽"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/tv_name"
app:layout_constraintLeft_toRightOf="@id/barrier"
android:text="男"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@id/barrier"
app:layout_constraintTop_toBottomOf="@id/tv_gender"
android:text="13800138000"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="@id/barrier"
app:layout_constraintTop_toBottomOf="@id/tv_phone"
android:text="2000年1月1日" />
</android.support.constraint.ConstraintLayout>
顯示效果如圖,很完美有沒有?
Group
Group 是一個組,用來批量控制 View 的顯示與隱藏;但是注意這不是個 ViewGroup,它只是一個不執行繪制的 View,和 barrier 一樣,它有一個 constraint_referenced_ids 的屬性,可以將需要隱藏的 ViewId 丟進去,在需要的時候將其批量隱藏即可。
還通過上面的例子,假設現在要把性別一欄隱藏掉:
<android.support.constraint.Group
app:constraint_referenced_ids="tv_gender,tv_sex_value"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
通過將性別的 key 和 value 的 id 都放進去,將其設置為 gone,則可以將該組實現隱藏:
但是,使用 Group 控制可見性是有坑的:
和以前用的ViewGroup有一點不同,以前用ViewGroup約束View的時候,外層ViewGroup設置成可見,里層View設置成不可見是可以生效的,但是用Group就不能。Group約束的元素的可見性始終一致。
調用Group的setVisibility方法不會立即對它約束對子View生效,而是要等到Group所在的ConstrainLayout調用preLayout方法時才會生效。preLayout只有在第一次layout和布局發生變化時才會調用。
Optimizer優化
可以通過將標簽app:layout_optimizationLevel元素添加到 ConstraintLayout 來決定應用哪些優化
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_optimizationLevel="standard|dimensions|chains"/>
- none: 不執行優化
- standard: 默認,僅優化直接和障礙約束
- direct: 優化直接約束
- barrier: 優化障礙約束
- chain:優化鏈條約束
- dimensions:優化維度測量,減少匹配約束元素的度量數量