原文:ConstraintLayout layouts 作者:Mark Allison 、 Sebastiano Poggi
本文將列舉講述如何使用 ConstraintLayout
來代替常見的三種布局 LinearLayout 、 RelatvieLayout 、 PercentLayout
的用法,本文使用的 Android Studio 都是 2.4 alpha 7
版本的,而 ConstraintLayout 庫是使用的 1.0.2
。
LinearLayout
的基本用法就是將子組件 View 在水平或者垂直方向浮動對齊,基于屬性 orientation
來設置。在視圖編輯器中使用 ConstraintLayout 要實現這個特性非常簡單,假如要實現相同的垂直方向浮動對齊,步驟很簡單,就是添加 View 然后將每一個 View 的上邊緣添加約束向到它位置上的另一個 View 即可,如下圖:
在 XML 中實現該特性也僅僅是為每一個 View 實現一個約束屬性 app:layout_constraintTop_toBottomOf
到整個浮動布局中在它之前的 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 權重特性的話,我們需要創(chuàng)建約束 Chain 鏈,詳細可以看看我的另一篇文章,表現如下圖:
Chain 鏈創(chuàng)建后,我們只需要在屬性視圖中為每個需要設置 weight 權重的鏈組件修改 layout_width
為 match_constraint
或者 0dp
(兩者是一樣的),然后再設置對應的權重值到 weight
的配置屬性,因為這個例子中我們使用的是水平的 Chain 鏈,所以設置權重的時候設置的屬性是 horizontal_weight
,如下圖。
最后,我們就可以再 blueprint 藍圖視圖下看到如下的展現:
首先要如之前的教程一樣,在 XML 創(chuàng)建 Chain 鏈,然后實現如上的效果只需要對 textView3
修改屬性 android:layout_width="0dp"
并且設置新屬性 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
屬性設置的值與 LinearLayout
中設置的 android:layout_weight
是一樣的值并且用法一樣,將會根據所有子組件的設置的權重比分割剩余的空間。
RelativeLayout
主要被用于包裝布局根據 views 組件之間的關系或與父組件的關系來布局的子 views 。其實如果你對 RelativeLayout
和 ConstraintLayout
都熟悉的話,就會感覺 RelativeLayout
其實只是 ConstraintLayout
的更基礎版本,ConstraintLayout
的很多概念來源其實就是 RelativeLayout
。事實上,你還可以認為 ConstraintLayout
就是加強版的 RelativeLayout
,因為你對舊的 Android 布局組件的熟悉,這將是很好的學習了解 ConstraintLayout
的思想體系模型。
因為 RelativeLayout
就是基于描述各個子 Views 之間的關系,而對各個子 Views 添加約束來實現相同的關系以及展現其實也很相似簡易實現。舉例,創(chuàng)建布局“ X 位于 Y 之上”的約束就對應于 RelativeLayout
中的 android:layout_above
屬性:
上面已經提到了 RelativeLayout
和 ConstraintLayout
的基本特性概念非常相似。你可以通過查閱我的另一篇文章來熟悉 ConstraintLayout
的基礎,然后使用如下面的表格中對應的屬性來轉換 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" |
這里要注意,相對父組件的居中沒有一對一即是只用一條屬性能設置同樣效果的,而是通過設置相同的約束條件到相對的兩個邊緣來實現。水平居中,意味著需要設置兩個相同的約束條件到水平的左和友邊緣對齊父組件,而垂直居中,則是需要設置兩個相同的約束條件到垂直的上下邊緣對齊父組件,自然而然的在兩個方向上都居中的話,則是需要設置兩對相同的約束條件在水平和垂直方向,即是四個約束條件對齊。提醒一下大家,在這里可以通過設置約束條件的 bias
來設置 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
能夠實現的約束條件在 RelativeLayout
中不能實現,比如對齊 View 的基線到另一個 View 的上或者下邊緣。之所以沒有列出來也是因為 RelativeLayout
中并沒有相對應的屬性實現。
GONE
Views
RelativeLayout
實現的屬性中,ConstraintLayout
沒有實現的屬性只有一個 android:layout_alignWithParentIfMissing
,這個屬性將會讓 View 組件能夠在對齊對象不顯示 GONE
的時候,對齊到父組件。舉個例子,如果 A View 需要設置左對齊到toRightOf
另一個 View (這個就命名為 B ) ,當B不顯示的時候,就會左邊對齊到父組件。
ConstraintLayout
在這點上跟 RelativeLayout
或者說大多數布局都不同,它會考慮顯示為 GONE
的組件的位置并且針對不顯示任何東西的 View 的約束 Constraint 仍然有效。唯一的缺陷是這個 GONE
的 View 的寬高是 0,而且外邊距 margin 也被忽略不考慮。
為了適應這種場景的情況下,ConstraintLayout
擁有一個屬性 app:layout_goneMargin[Left|Start|Top|Right|End|Bottom]
可以用于當約束對象是一個 GONE
View 的時候,設置外邊距 margin 。在下面的例子中,當按鈕消失 gone 的時候,原本存在于輸入框對按鈕的屬性 start_toEndOf
的 24dp
的外邊距啟用了另一個屬性 app:layout_marginGoneStart="56dp"
,如下動態(tài)圖所示:
PercentLayout
通常被用于響應式布局設計,當需要根據父組件來縮放子組件到百分比的情況。
首先我們要看的特性是,子組件要實現占據父組件的寬度或者高度的固定百分比。它在 PercentLayout
中是通過屬性 app:layout_widthPercent
和 app:layout_heightPercent
來實現的(此處的命名空間 app 是因為 PercentLayout 的庫引入是來自于 support library)。要實現該特性的話,我們可以通過 ConstraintLayout
中的 Guidelines 參照線來實現。假如我們需要實現 app:layout_widthPercent="25%"
的特性,我們可以首先創(chuàng)建一個參照線,移動到 25%
處:
然后我們就需要創(chuàng)建一個 View 將它的創(chuàng)建約束到父組件的 start
邊緣以及 end
約束到參照線。在此處,我們沒有使用 left
而使用 start
是為了更友好的支持 RTL 語言(從右到左布局,right to left)
同時,我們還需要注意的是我們需要設置 android:layout_width
是被設置成了 0dp
或者 match_constraint
(源碼層面,他們是一樣的)。然后移除這個 View 的外邊距,那么這個 View 的寬度就會自動設置成父組件的 25%
,進一步操作如下圖所示:
以上例子的 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>
實際上真正對齊百分比寬高是由 Guidline 完成的,百分比寬的 TextView 只是創(chuàng)建了一個約束到參照線 Guideline就能實現固定的百分比寬高。
PercentLayout
還可以讓我們實現相對于父組件的百分比外邊距 margin 。相比上面百分比寬高的例子,我們一樣需要在指定百分比位置設置一個 Guideline參照線,但不是設置 View 的寬度約束到參照線,而是設置 View 的 start
邊緣約束到參照線。舉個例子,如果我們需要設置的效果是 app:layout_marginStartPercent="25%"
,我們創(chuàng)建一個在 25%
位置的參照線,然后設置 View 的 start
邊緣約束到參照線,如下圖所示:
然后,在這個例子中我們還設置這個 View 的寬度 android:layout_width="wrap_content"
,然后移除各個方向的外邊距 margin ,然后 View 就會有相對于父組件的 25% 寬度外邊距 margin。
在 XML 中,參照線 Guidline 是跟上面的例子一樣的設置,如下:
<?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 如何設置約束到這個參照線,在這個例子,我們需要設置 app:layout_constraintStart_toStartOf="@+id/guideline"
然后如上面編輯器中說的一樣設置 android:layout_width
為 wrap_content
和 android:layout_marginStart
為 0dp
。
最后一個特性就是實現 PercentLayout
的橫縱比特性,通過它可以讓高度固定比例為寬度的函數,或者反過來。關于 ConstraintLayout
如何實現橫縱比尺寸,我有另一篇文章 更詳細的講解了這個特性。首先我們設置一個固定的比例,然后設置這個 View 的寬高為 match_constraint
或 0dp
:
然后我們設置好水平方向的兩個約束條件,然后至少保留一個垂直方向的約束不設置,那么我們的組件 View 高度就會是依賴于寬度的函數,然后通過移動參照線來縮放 View 的寬度的時候就會發(fā)現高度也會相應的根據函數變化。
在 XML 中,真正設置了寬高比的屬性是 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基礎系列之尺寸橫縱比 dimensions。
更多建議: