原文:ConstraintLayout layouts 作者:Mark Allison 、 Sebastiano Poggi
本文將列舉講述如何使用 ConstraintLayout
來代替常見的三種布局 LinearLayout 、 RelatvieLayout 、 PercentLayout
的用法,本文使用的 Android Studio 都是 2.4 alpha 7
版本的,而 ConstraintLayout 庫是使用的 1.0.2
。
LinearLayout
的基本用法就是將子組件 View 在水平或者垂直方向浮動(dòng)對齊,基于屬性 orientation
來設(shè)置。在視圖編輯器中使用 ConstraintLayout 要實(shí)現(xiàn)這個(gè)特性非常簡單,假如要實(shí)現(xiàn)相同的垂直方向浮動(dòng)對齊,步驟很簡單,就是添加 View 然后將每一個(gè) View 的上邊緣添加約束向到它位置上的另一個(gè) View 即可,如下圖:
在 XML 中實(shí)現(xiàn)該特性也僅僅是為每一個(gè) View 實(shí)現(xiàn)一個(gè)約束屬性 app:layout_constraintTop_toBottomOf
到整個(gè)浮動(dòng)布局中在它之前的 View。
<?xml version="1.0" encoding="utf-8"?>
<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="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
tools:text="TextView"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
tools:text="TextView"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView1" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
tools:text="TextView"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />
<TextView
android:id="@+id/textView4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
tools:text="TextView"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView3" />
</android.support.constraint.ConstraintLayout>
要想創(chuàng)建跟 LinearLayout 類似的 weight 權(quán)重特性的話,我們需要?jiǎng)?chuàng)建約束 Chain 鏈,詳細(xì)可以看看我的另一篇文章,表現(xiàn)如下圖:
Chain 鏈創(chuàng)建后,我們只需要在屬性視圖中為每個(gè)需要設(shè)置 weight 權(quán)重的鏈組件修改 layout_width
為 match_constraint
或者 0dp
(兩者是一樣的),然后再設(shè)置對應(yīng)的權(quán)重值到 weight
的配置屬性,因?yàn)檫@個(gè)例子中我們使用的是水平的 Chain 鏈,所以設(shè)置權(quán)重的時(shí)候設(shè)置的屬性是 horizontal_weight
,如下圖。
最后,我們就可以再 blueprint 藍(lán)圖視圖下看到如下的展現(xiàn):
首先要如之前的教程一樣,在 XML 創(chuàng)建 Chain 鏈,然后實(shí)現(xiàn)如上的效果只需要對 textView3
修改屬性 android:layout_width="0dp"
并且設(shè)置新屬性 app:layout_constraintHorizontal_weight="1"
,如下:
<?xml version="1.0" encoding="utf-8"?>
<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="match_parent"
tools:context="com.stylingandroid.scratch.MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toStartOf="@+id/textView2"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="TextView" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toStartOf="@+id/textView3"
app:layout_constraintStart_toEndOf="@+id/textView"
app:layout_constraintTop_toTopOf="parent"
tools:layout_editor_absoluteX="141dp"
tools:text="TextView" />
<TextView
android:id="@+id/textView3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toEndOf="@+id/textView2"
app:layout_constraintTop_toTopOf="parent"
tools:text="TextView" />
</android.support.constraint.ConstraintLayout>
這里 app:layout_constraintHorizontal_weight
屬性設(shè)置的值與 LinearLayout
中設(shè)置的 android:layout_weight
是一樣的值并且用法一樣,將會(huì)根據(jù)所有子組件的設(shè)置的權(quán)重比分割剩余的空間。
RelativeLayout
主要被用于包裝布局根據(jù) views 組件之間的關(guān)系或與父組件的關(guān)系來布局的子 views 。其實(shí)如果你對 RelativeLayout
和 ConstraintLayout
都熟悉的話,就會(huì)感覺 RelativeLayout
其實(shí)只是 ConstraintLayout
的更基礎(chǔ)版本,ConstraintLayout
的很多概念來源其實(shí)就是 RelativeLayout
。事實(shí)上,你還可以認(rèn)為 ConstraintLayout
就是加強(qiáng)版的 RelativeLayout
,因?yàn)槟銓εf的 Android 布局組件的熟悉,這將是很好的學(xué)習(xí)了解 ConstraintLayout
的思想體系模型。
因?yàn)?RelativeLayout
就是基于描述各個(gè)子 Views 之間的關(guān)系,而對各個(gè)子 Views 添加約束來實(shí)現(xiàn)相同的關(guān)系以及展現(xiàn)其實(shí)也很相似簡易實(shí)現(xiàn)。舉例,創(chuàng)建布局“ X 位于 Y 之上”的約束就對應(yīng)于 RelativeLayout
中的 android:layout_above
屬性:
上面已經(jīng)提到了 RelativeLayout
和 ConstraintLayout
的基本特性概念非常相似。你可以通過查閱我的另一篇文章來熟悉 ConstraintLayout
的基礎(chǔ),然后使用如下面的表格中對應(yīng)的屬性來轉(zhuǎn)換 RelativeLayout
中的屬性到 ConstraintLayout
。
RelativeLayout 屬性 |
ConstraintLayout 屬性 |
---|---|
android:layout_alignParentLeft="true" |
app:layout_constraintLeft_toLeftOf="parent" |
android:layout_alignParentLeft="true" |
app:layout_constraintLeft_toLeftOf="parent" |
android:layout_alignParentStart="true" |
app:layout_constraintStart_toStartOf="parent" |
android:layout_alignParentTop="true" |
app:layout_constraintTop_toTopOf="parent" |
android:layout_alignParentRight="true" |
app:layout_constraintRight_toRightOf="parent" |
android:layout_alignParentEnd="true" |
app:layout_constraintEnd_toEndOf="parent" |
android:layout_alignParentBottom="true" |
app:layout_constraintBottom_toBottomOf="parent" |
android:layout_centerHorizontal="true" |
app:layout_constraintStart_toStartOf="parent" 和 app:layout_constraintEnd_toEndOf="parent" |
android:layout_centerVertical="true" |
app:layout_constraintTop_toTopOf="parent" 和 app:layout_constraintBottom_toBottomOf="parent" |
android:layout_centerInParent="true" |
app:layout_constraintStart_toStartOf="parent" , app:layout_constraintTop_toTopOf="parent" , app:layout_constraintEnd_toEndOf="parent" , 和 app:layout_constraintBottom_toBottomOf="parent" |
這里要注意,相對父組件的居中沒有一對一即是只用一條屬性能設(shè)置同樣效果的,而是通過設(shè)置相同的約束條件到相對的兩個(gè)邊緣來實(shí)現(xiàn)。水平居中,意味著需要設(shè)置兩個(gè)相同的約束條件到水平的左和友邊緣對齊父組件,而垂直居中,則是需要設(shè)置兩個(gè)相同的約束條件到垂直的上下邊緣對齊父組件,自然而然的在兩個(gè)方向上都居中的話,則是需要設(shè)置兩對相同的約束條件在水平和垂直方向,即是四個(gè)約束條件對齊。提醒一下大家,在這里可以通過設(shè)置約束條件的 bias
來設(shè)置 View 組件垂直或水平對齊到父組件的百分比位置,如下圖所示:
RelativeLayout 屬性 |
ConstraintLayout 屬性 |
---|---|
android:layout_toLeftOf |
app:layout_constraintRight_toLeftOf |
android:layout_toStartOf |
app:layout_constraintEnd_toStartOf |
android:layout_above |
app:layout_constraintBottom_toTopOf |
android:layout_toRightOf |
app:layout_constraintLeft_toRightOf |
android:layout_toEndOf |
app:layout_constraintStart_toEndOf |
android:layout_below |
app:layout_constraintTop_toBottomOf |
android:layout_alignLeft |
app:layout_constraintLeft_toLeftOf |
android:layout_alignStart |
app:layout_constraintStart_toStartOf |
android:layout_alignTop |
app:layout_constraintTop_toTopOf |
android:layout_alignRight |
app:layout_constraintRight_toRightOf |
android:layout_alignEnd |
app:layout_constraintEnd_toEndOf |
android:layout_alignBottom |
app:layout_constraintBottom_toBottomOf |
android:layout_alignBaseline |
app:layout_constraintBaseline_toBaselineOf |
這里提醒一下大家,很多 ConstraintLayout
能夠?qū)崿F(xiàn)的約束條件在 RelativeLayout
中不能實(shí)現(xiàn),比如對齊 View 的基線到另一個(gè) View 的上或者下邊緣。之所以沒有列出來也是因?yàn)?RelativeLayout
中并沒有相對應(yīng)的屬性實(shí)現(xiàn)。
GONE
Views
RelativeLayout
實(shí)現(xiàn)的屬性中,ConstraintLayout
沒有實(shí)現(xiàn)的屬性只有一個(gè) android:layout_alignWithParentIfMissing
,這個(gè)屬性將會(huì)讓 View 組件能夠在對齊對象不顯示 GONE
的時(shí)候,對齊到父組件。舉個(gè)例子,如果 A View 需要設(shè)置左對齊到toRightOf
另一個(gè) View (這個(gè)就命名為 B ) ,當(dāng)B不顯示的時(shí)候,就會(huì)左邊對齊到父組件。
ConstraintLayout
在這點(diǎn)上跟 RelativeLayout
或者說大多數(shù)布局都不同,它會(huì)考慮顯示為 GONE
的組件的位置并且針對不顯示任何東西的 View 的約束 Constraint 仍然有效。唯一的缺陷是這個(gè) GONE
的 View 的寬高是 0,而且外邊距 margin 也被忽略不考慮。
為了適應(yīng)這種場景的情況下,ConstraintLayout
擁有一個(gè)屬性 app:layout_goneMargin[Left|Start|Top|Right|End|Bottom]
可以用于當(dāng)約束對象是一個(gè) GONE
View 的時(shí)候,設(shè)置外邊距 margin 。在下面的例子中,當(dāng)按鈕消失 gone 的時(shí)候,原本存在于輸入框?qū)Π粹o的屬性 start_toEndOf
的 24dp
的外邊距啟用了另一個(gè)屬性 app:layout_marginGoneStart="56dp"
,如下動(dòng)態(tài)圖所示:
PercentLayout
通常被用于響應(yīng)式布局設(shè)計(jì),當(dāng)需要根據(jù)父組件來縮放子組件到百分比的情況。
首先我們要看的特性是,子組件要實(shí)現(xiàn)占據(jù)父組件的寬度或者高度的固定百分比。它在 PercentLayout
中是通過屬性 app:layout_widthPercent
和 app:layout_heightPercent
來實(shí)現(xiàn)的(此處的命名空間 app 是因?yàn)?PercentLayout 的庫引入是來自于 support library)。要實(shí)現(xiàn)該特性的話,我們可以通過 ConstraintLayout
中的 Guidelines 參照線來實(shí)現(xiàn)。假如我們需要實(shí)現(xiàn) app:layout_widthPercent="25%"
的特性,我們可以首先創(chuàng)建一個(gè)參照線,移動(dòng)到 25%
處:
然后我們就需要?jiǎng)?chuàng)建一個(gè) View 將它的創(chuàng)建約束到父組件的 start
邊緣以及 end
約束到參照線。在此處,我們沒有使用 left
而使用 start
是為了更友好的支持 RTL 語言(從右到左布局,right to left)
同時(shí),我們還需要注意的是我們需要設(shè)置 android:layout_width
是被設(shè)置成了 0dp
或者 match_constraint
(源碼層面,他們是一樣的)。然后移除這個(gè) View 的外邊距,那么這個(gè) View 的寬度就會(huì)自動(dòng)設(shè)置成父組件的 25%
,進(jìn)一步操作如下圖所示:
以上例子的 XML 源碼如下:
<?xml version="1.0" encoding="utf-8"?>
<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="match_parent">
<android.support.constraint.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.25" />
<TextView
android:id="@+id/textView3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="0dp"
android:layout_marginStart="0dp"
tools:text="TextView"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintStart_toStartOf="parent" />
</android.support.constraint.ConstraintLayout>
實(shí)際上真正對齊百分比寬高是由 Guidline 完成的,百分比寬的 TextView 只是創(chuàng)建了一個(gè)約束到參照線 Guideline就能實(shí)現(xiàn)固定的百分比寬高。
PercentLayout
還可以讓我們實(shí)現(xiàn)相對于父組件的百分比外邊距 margin 。相比上面百分比寬高的例子,我們一樣需要在指定百分比位置設(shè)置一個(gè) Guideline參照線,但不是設(shè)置 View 的寬度約束到參照線,而是設(shè)置 View 的 start
邊緣約束到參照線。舉個(gè)例子,如果我們需要設(shè)置的效果是 app:layout_marginStartPercent="25%"
,我們創(chuàng)建一個(gè)在 25%
位置的參照線,然后設(shè)置 View 的 start
邊緣約束到參照線,如下圖所示:
然后,在這個(gè)例子中我們還設(shè)置這個(gè) View 的寬度 android:layout_width="wrap_content"
,然后移除各個(gè)方向的外邊距 margin ,然后 View 就會(huì)有相對于父組件的 25% 寬度外邊距 margin。
在 XML 中,參照線 Guidline 是跟上面的例子一樣的設(shè)置,如下:
<?xml version="1.0" encoding="utf-8"?>
<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="match_parent">
<android.support.constraint.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.25" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
app:layout_constraintStart_toStartOf="@+id/guideline"
tools:text="TextView" />
</android.support.constraint.ConstraintLayout>
區(qū)別在于,我們的 View 如何設(shè)置約束到這個(gè)參照線,在這個(gè)例子,我們需要設(shè)置 app:layout_constraintStart_toStartOf="@+id/guideline"
然后如上面編輯器中說的一樣設(shè)置 android:layout_width
為 wrap_content
和 android:layout_marginStart
為 0dp
。
最后一個(gè)特性就是實(shí)現(xiàn) PercentLayout
的橫縱比特性,通過它可以讓高度固定比例為寬度的函數(shù),或者反過來。關(guān)于 ConstraintLayout
如何實(shí)現(xiàn)橫縱比尺寸,我有另一篇文章 更詳細(xì)的講解了這個(gè)特性。首先我們設(shè)置一個(gè)固定的比例,然后設(shè)置這個(gè) View 的寬高為 match_constraint
或 0dp
:
然后我們設(shè)置好水平方向的兩個(gè)約束條件,然后至少保留一個(gè)垂直方向的約束不設(shè)置,那么我們的組件 View 高度就會(huì)是依賴于寬度的函數(shù),然后通過移動(dòng)參照線來縮放 View 的寬度的時(shí)候就會(huì)發(fā)現(xiàn)高度也會(huì)相應(yīng)的根據(jù)函數(shù)變化。
在 XML 中,真正設(shè)置了寬高比的屬性是 app:layout_constraintDimensionRatio
為想要的值,其他規(guī)則跟在視圖編輯器中是一樣的。
<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="match_parent"
tools:context="com.stylingandroid.scratch.MainActivity">
<View
android:id="@+id/imageView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:layout_constraintDimensionRatio="h,15:9"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<android.support.constraint.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.39" />
</android.support.constraint.ConstraintLayout>
最后提醒一下,沒懂的小伙伴可以看看另一篇文章 ConstraintLayout基礎(chǔ)系列之尺寸橫縱比 dimensions。
更多建議: