ConstraintLayout是谷歌推出的一個(gè)新布局,字面意思是約束布局,距離發(fā)布已經(jīng)有一段時(shí)間了,下面會(huì)有一個(gè)復(fù)雜布局的代碼對(duì)比。
對(duì)于ConstraintLayout,有篇文章寫了關(guān)于它的性能優(yōu)勢(shì):解析ConstraintLayout的性能優(yōu)勢(shì)
我們知道,當(dāng)我們的布局越來(lái)越復(fù)雜的時(shí)候,所使用的嵌套就越來(lái)多,性能自然而然的就會(huì)有所下降,而ConstraintLayout恰恰就是為了這個(gè)操作而誕生的。
對(duì)于ConstraintLayout的可視化操作可以說(shuō)是非常牛逼啊,參考ConstraintLayout完全解析,但是我個(gè)人不習(xí)慣用可視化的去拖拽控件,所以在這里來(lái)寫在xml上面寫各種屬性。
事前準(zhǔn)備
引入ConstraintLayout
在Android Studio 2.3以后,默認(rèn)創(chuàng)建的布局就是ConstraintLayout布局,如果不是2.3以后的版本,在build.gradle
文件中引入ConstraintLayout,當(dāng)前版本是1.0.2
:
dependencies {
compile 'com.android.support.constraint:constraint-layout:1.0.2'
// 3.0以前使用compile,3.0以后使用implementation
// implementation 'com.android.support.constraint:constraint-layout:1.0.2'
}
轉(zhuǎn)換成ConstraintLayout
如果你需要將原來(lái)的布局轉(zhuǎn)成ConstraintLayout布局的話,可以在打開(kāi)xml文件后,點(diǎn)擊Design
選項(xiàng),然后找到Component Tree
窗口,最后右鍵布局,選擇Convert layout to ConstraintLayout
選項(xiàng)
對(duì)比布局代碼
下面我們來(lái)通過(guò)一個(gè)布局,來(lái)對(duì)比ConstraintLayout和其他布局來(lái)實(shí)現(xiàn)下面的布局內(nèi)容的代碼:
首先有除了ConstraintLayout之外的布局來(lái)實(shí)現(xiàn),代碼太長(zhǎng),所以省去屬性,有能力的可以自己去寫一下:
<RelativeLayout>
<ImageView />
<FloatingActionButton />
<RelativeLayout>
<TextView />
<LinearLayout>
<TextView />
<RelativeLayout>
<EditText />
</RelativeLayout>
</LinearLayout>
<LinearLayout>
<TextView />
<RelativeLayout>
<EditText />
</RelativeLayout>
</LinearLayout>
<TextView />
</RelativeLayout>
<LinearLayout >
<Button />
<Button />
</LinearLayout>
</RelativeLayout>
夠復(fù)雜的,RelativeLayout布局嵌套LinearLayout布局,里面又嵌套多個(gè)布局,嚴(yán)重影響布局的繪制。
那么我們來(lái)看使用ConstraintLayout布局之后的代碼是怎么樣的:
<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="top.jowanxu.constraintlayoutdemo.MainActivity">
<ImageView
android:id="@+id/banner"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@color/colorAccent"
android:gravity="center"
app:layout_constraintDimensionRatio="16:6"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/fab_margin"
app:layout_constraintBottom_toBottomOf="@+id/banner"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.98"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/banner"
app:srcCompat="@android:drawable/ic_dialog_email" />
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:text="Singapore"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Title"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/banner" />
<TextView
android:id="@+id/camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:padding="10dp"
android:text="Camera"
android:textAppearance="@style/TextAppearance.AppCompat.Tooltip"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/title" />
<EditText
android:id="@+id/cameraEdit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:hint="Leica M Typ 240"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/camera"
app:layout_constraintTop_toTopOf="@+id/camera" />
<TextView
android:id="@+id/settings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:padding="10dp"
android:text="Settings"
android:textAppearance="@style/TextAppearance.AppCompat.Tooltip"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cameraEdit" />
<EditText
android:id="@+id/settingsEdit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:hint="f/4 16s ISO 200"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/settings"
app:layout_constraintTop_toTopOf="@+id/settings" />
<TextView
android:id="@+id/content"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="10dp"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:text="Singapore offically the Republic of Singapore.Singapore offically the Republic of Singapore.Singapore offically the Republic of Singapore.Singapore offically the Republic of Singapore.Singapore offically the Republic of Singapore."
android:textAppearance="@style/Base.TextAppearance.AppCompat.Body1"
app:layout_constraintBottom_toTopOf="@+id/discard"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/settingsEdit" />
<Button
android:id="@+id/upload"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="UPLOAD"
app:layout_constraintBottom_toBottomOf="@+id/discard"
app:layout_constraintEnd_toStartOf="@+id/discard" />
<Button
android:id="@+id/discard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginEnd="10dp"
android:text="DISCARD"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</android.support.constraint.ConstraintLayout>
簡(jiǎn)單概括就是,沒(méi)有一層嵌套,而且是一個(gè)完全扁平的層次結(jié)構(gòu):
<ConstraintLayout>
<ImageView/>
<FloatingActionButton/>
<TextView/>
<TextView/>
<EditText/>
<TextView/>
<EditText/>
<TextView/>
<Button/>
<Button/>
</ConstraintLayout>
開(kāi)始
ConstraintLayout所包含的約束有:
Dimension constraints
Relative positioning
Centering positioning
Margins
Visibility behavior
- Chains
Dimensions constraints
Dimensions constraints字面意思就是尺寸約束,可以設(shè)置ConstraintLayout布局的大小,和設(shè)置布局里面控件的尺寸約束。
Minimum dimensions on ConstraintLayout
ConstraintLayout布局與其他布局一樣可以設(shè)置最大尺寸和最小尺寸:
android:minWidth
android:minHeight
android:maxWidth
android:maxHeight
Widgets dimension constraints
設(shè)置布局里面控件的尺寸約束,我們知道,在普通布局里面,我們每個(gè)控件的寬高,都是在三個(gè)類型里面設(shè)置值的:
- 固定的值(如
100dp
),對(duì)應(yīng)圖上a
-
wrap_content
,對(duì)應(yīng)圖上a
-
match_parent
,對(duì)應(yīng)圖上b
,設(shè)置margin
對(duì)應(yīng)c
但是在ConstraintLayout布局里面,控件的寬高變?yōu)椋?/p>
- 固定的值(如
100dp
),對(duì)應(yīng)圖上a
-
wrap_content
,對(duì)應(yīng)圖上a
-
match_constraint
,也就是0dp
,對(duì)應(yīng)圖上b
,設(shè)置margin
對(duì)應(yīng)c
與其他布局不同的是ConstraintLayout里面沒(méi)有match_parent
,而是用0dp
也就是match_constraint
替換了他,看下官網(wǎng)怎么說(shuō)的:
Important: MATCH_PARENT is not recommended for widgets contained in a ConstraintLayout. Similar behavior can be defined by using MATCH_CONSTRAINT with the corresponding left/right or top/bottom constraints being set to "parent".
意思是,ConstraintLayout已經(jīng)棄用MATCH_PARENT
了,通過(guò)使用MATCH_CONSTRAINT
,同時(shí)設(shè)置控件的left/right
或者top/bottom
來(lái)約束parent
來(lái)達(dá)到與MATCH_PARENT
一樣的效果,如:
android:layout_width="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
下面舉個(gè)栗子,將RelativeLayout和ConstraintLayout寫一下下面的布局
[圖片上傳失敗...(image-b12e85-1516623694099)]
RelativeLayout里面
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:text="Button" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toEndOf="@+id/button"
android:layout_alignParentEnd="true"
android:text="asdfasdfsdfasdfasdfasdfasdfasdfsadfsdafasdfasdfasdf" />
然后ConstraintLayout布局
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="asdfasdfsdfasdfasdfasdfasdfasdfsadfsdafasdfasdfasdf"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/button1"
app:layout_constraintTop_toTopOf="parent" />
為什么會(huì)出現(xiàn)這樣的情況呢,跟我們預(yù)期的不一樣,當(dāng)一個(gè)控件的兩邊都有約束的時(shí)候,會(huì)將這個(gè)控件居中,當(dāng)這個(gè)控件寬度或者高度特別大的時(shí)候(超出屏幕),則會(huì)將左右兩邊超出的距離相同,上面說(shuō)了,要達(dá)到MATCH_PARENT
效果,需要將尺寸設(shè)置為MATCH_CONSTRAINT
也就是0dp
,我們來(lái)看一下結(jié)果:
Radio
ConstraintLayout里面可以設(shè)置控件的比例,對(duì)應(yīng)寬高比width:height
,屬性為:
- layout_constraintDimensionRatio
如果我們要把上面的16:6這樣的寫出來(lái),那么我們先要將左右的約束設(shè)置成parent
,同時(shí)設(shè)置寬高為MATCH_CONSTRAINT
,然后設(shè)置layout_constraintDimensionRatio
為16:6
:
<Button
android:id="@+id/button1"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:text="Button1"
app:layout_constraintDimensionRatio="16:6"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
而在LinearLayout
上設(shè)置卻要這樣寫:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:weightSum="22">
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="6"
android:text="Button" />
</LinearLayout>
對(duì)比下,差別還是很明顯的,ConstraintLayout用起來(lái)還是非常舒服的。
當(dāng)然,還支持單方向的比例,相對(duì)應(yīng)的方向的尺寸設(shè)置為MATCH_CONSTRAINT
:
<!-- 寬度比例,對(duì)應(yīng)的寬度即為200dp -->
android:layout_width="0dp"
android:layout_height="100dp"
app:layout_constraintDimensionRatio="W,2:1"
<!-- 高度比例,對(duì)應(yīng)的高度即為100dp -->
android:layout_width="200dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="H,2:1"
Relative positioning
Relative positioning字面意思是相對(duì)定位,這個(gè)的屬性類似與相對(duì)布局(RelativeLayout)的屬性,屬性的值包括parent
和控件的id
如@+id/button
,包含:
layout_constraintLeft_toLeftOf
layout_constraintLeft_toRightOf
layout_constraintRight_toLeftOf
layout_constraintRight_toRightOf
layout_constraintTop_toTopOf
layout_constraintTop_toBottomOf
layout_constraintBottom_toTopOf
layout_constraintBottom_toBottomOf
layout_constraintBaseline_toBaselineOf
layout_constraintStart_toEndOf
layout_constraintStart_toStartOf
layout_constraintEnd_toStartOf
layout_constraintEnd_toEndOf
通過(guò)上面我們可以看出,這里的屬性是layout_constraintXX_toYYOf
的,這里的XX
和YY
分別表示什么樣的方向?
我們先來(lái)通過(guò)一個(gè)方向的屬性來(lái)了解,其他以此類推:
layout_constraintStart_toStartOf
、layout_constraintStart_toEndOf
和layout_constraintEnd_toStartOf
、layout_constraintEnd_toEndOf
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button2"
app:layout_constraintStart_toEndOf="@+id/button1"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button4"
app:layout_constraintEnd_toStartOf="@+id/button3"
app:layout_constraintStart_toEndOf="@+id/button2"
app:layout_constraintTop_toTopOf="parent" />
我們來(lái)看預(yù)覽圖:
從上面看出,layout_constraintXX_toYYOf
屬性的XX
表示當(dāng)前控件的位置,YY
則表示需要約束的控件的位置,下面是一個(gè)控件的各邊表示:
Centering positioning and bias
字面意思就是居中定位,當(dāng)我們?cè)谙鄬?duì)布局到時(shí)候,如果要居中一個(gè)控件的時(shí)候
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="Button" />
在ConstraintLayout里面,可以這樣將控件居中
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="button"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"/>
預(yù)覽圖都是顯示居中
[圖片上傳失敗...(image-27caff-1516623694099)]
因?yàn)樵贑onstraintLayout里面,每個(gè)約束都是類似于在對(duì)應(yīng)方向上,有相反的力 去拉控件,而在這里,則會(huì)水平居中顯示。
bias
上面說(shuō)到每個(gè)約束都是一個(gè)拉力,而
bias
則表示這個(gè)拉力在兩邊的偏重,對(duì)應(yīng)橫向豎向偏重:
layout_constraintHorizontal_bias
layout_constraintVertical_bias
同樣用上面的栗子,加入app:layout_constraintHorizontal_bias="0.9"
屬性,對(duì)應(yīng)左邊拉力偏重90%
,右邊拉力偏重10%
:
Margins
ConstraintLayout里面的margin
與普通的屬性一樣,只是值不能為負(fù)數(shù)。
Note that a margin can only be positive or equals to zero, and takes a Dimension.
Margins when connected to a GONE widget
設(shè)置一個(gè)控件為GONE
時(shí)的margin
值,下面是包含的屬性:
layout_goneMarginStart
layout_goneMarginEnd
layout_goneMarginLeft
layout_goneMarginTop
layout_goneMarginRight
layout_goneMarginBottom
舉個(gè)栗子:
<!-- 省去一些代碼 -->
<Button
android:id="@+id/button1"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="@+id/button2"
android:layout_marginStart="60dp"
app:layout_constraintStart_toEndOf="@+id/button1"
app:layout_goneMarginStart="160dp" />
<Button
android:id="@+id/button3"
app:layout_constraintStart_toStartOf="@+id/button1"
app:layout_constraintTop_toBottomOf="@+id/button2" />
<Button
android:id="@+id/button4"
android:layout_marginStart="60dp"
app:layout_constraintStart_toEndOf="@+id/button3"
app:layout_constraintTop_toBottomOf="@+id/button2"
app:layout_goneMarginStart="160dp" />
預(yù)覽圖:
當(dāng)我們將button3
的設(shè)置為GONE
的時(shí)候,結(jié)果為:
[圖片上傳失敗...(image-b5a768-1516623694100)]
對(duì)比可以看出:
- 當(dāng)設(shè)置了
goneMargin
屬性時(shí)候,約束的控件如果不是GONE
的時(shí)候,則不會(huì)生效; - 當(dāng)
goneMargin
屬性和margin
屬性同時(shí)存在的時(shí)候,margin
屬性不會(huì)生效。
Visibility behavior
字面意思是可見(jiàn)性行為,當(dāng)一個(gè)控件設(shè)置為GONE
的時(shí)候,在布局計(jì)算的時(shí)候仍會(huì)加進(jìn)去,在布局過(guò)程中,將被解析成一個(gè)點(diǎn),所有的margin
也將為0,但是對(duì)于其他控件的約束仍然存在。
如果我們需要上面的圖,在A
被隱藏后,仍然保持B
的位置不變,那么我們就要設(shè)置B
的goneMargin
的值為A
的寬度和margin
與B
的margin
值的和,也就是
goneMarginStart
= A.width
+ A.marginStart
+ B.marginStart
<!-- 示例代碼 -->
<Button
android:id="@+id/button1"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="Button1"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button2"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="Button2"
app:layout_constraintStart_toEndOf="@+id/button1"
app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginStart="120dp" />
總結(jié)
ConstraintLayout比傳統(tǒng)布局的性能更出色,而且ConstraintLayout對(duì)于一些復(fù)雜的布局具有天然的優(yōu)勢(shì),所以還沒(méi)有使用ConstraintLayout的同學(xué),趕緊加入到里面來(lái)吧,ConstraintLayout還有一些內(nèi)容將在下一篇文章介紹(Chain
和Guideline
)。