ConstraintLayout IN Jetpack

曾经Android布局就一把梭,RelativeLayout+LinearLayout,直到用ConstraintLayout写了几个页面后发现是真的香,现在任何布局第一选择都是它。

ConstraintLayout是什么?

  • 它属于Android JetPack,当前版本implementation 'com.android.support.constraint:constraint-layout:1.1.3'
  • 作用是可以减少视图布局层级,很复杂的页面一般也就是两个层级能搞定
  • 响应式布局代码更健壮,轻松适配不同分辨率
  • 更好的可视化操作,通过Android Studio的Layout Editor就可以实时拖动编辑并预览布局效果

基本用法

问:布局基本三要素是什么?

答:位置,对齐,大小。

1,位置,View放在哪里

ConstraintLayout的做法和RelativeLayout是差不多的,实际使用起来Constraint Layout的字段语义更符合直觉。

Relative Layout Constraint Layout
垂直居中 android:layout_centerVertical=”true” app:layout_constraintBottom_toBottomOf=”parent” app:layout_constraintTop_toTopOf=”parent”
居下 android:layout_below=”@id/view” app:layout_constraintTop_toBottomOf=”@id/view”
居右 android:layout_toEndOf=”@id/view” app:layout_constraintStart_toEndOf=”@id/view”

2,对齐,整整齐齐还是错落有致,当然是都要

严格来讲Relative Layout里面的子View之间是无法相互对齐的。有两种方式可以间接做到:是基于父视图对齐和嵌套一个ViewGroup比如LinearLayout来对齐。这也就是为什么即使用Relative Layout做的布局也很可能层数过多,出现overDraw。而ConstraintLayout就很优雅了,要领就是Start_toStartOf,End_toEndOf,Top_toTopOf,Bottom_toBottomOf两两结对出现就行

image-20190831210915915

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 上图示例,基于View水平对齐
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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 test"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>

<Button
android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="btn2"
app:layout_constraintEnd_toEndOf="@id/btn1"
app:layout_constraintStart_toStartOf="@id/btn1"
app:layout_constraintTop_toBottomOf="@id/btn1"/>

</androidx.constraintlayout.widget.ConstraintLayout

image-20190831211525496

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 基于View垂直对齐
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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_marginTop="50dp"
android:layout_height="wrap_content"
android:text="Button test"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>

<Button
android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_marginStart="10dp"
android:text="btn2"
app:layout_constraintBottom_toBottomOf="@id/btn1"
app:layout_constraintStart_toEndOf="@id/btn1"
app:layout_constraintTop_toTopOf="@id/btn1"/>

</androidx.constraintlayout.widget.ConstraintLayout>

3,大小控制

基本用法和普通ViewGroup一致,无非就是wrap_content,0dp,margin,padding这些来控制View大小。

熟练掌握这些,已经可以愉快的和ConstraintLayout玩起来了,慢慢的也能体会到它的精妙之处,当然Constraintlayout还有些进阶用法。

进阶用法

1,Guideline(可以影响View的位置,大小,对齐方式)

image-20190831214717727

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 不可见的辅助布局线,水平和垂直,数值和百分比都支持,常用来切割页面,辅助布局
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.27980536"/>

<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_begin="171dp"/>

2,Barrier(主要影响View的位置)

image-20190831230407493

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 同样是不可见的辅助布局线,区别于Guideline,它自己不定义位置,而且根据它绑定的View动态确定位置
// 右边的C始终距离左边的A,B有一个固定的距离margin
// barrierDirection 指定 barrier相对于绑定View的方位
// constraint_referenced_ids 指定绑定子View的id,用逗号分隔
<Button
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:text="Button test"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>

<Button
android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="btn2"
app:layout_constraintStart_toStartOf="@id/btn1"
app:layout_constraintTop_toBottomOf="@id/btn1"/>

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="btn3"
app:layout_constraintStart_toEndOf="@id/barrier"
app:layout_constraintTop_toTopOf="@id/btn1"/>

<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="right"
app:constraint_referenced_ids="btn1,btn2"
/>

3,Bias(偏移,影响位置)

image-20190831232252829

1
2
3
4
5
6
7
8
9
10
11
12
13
// Bias的起作用的前提是:有成对的水平约束(Start_toStartOf和End_toEndOf)或竖直约束(Top_toTopOf和
// Bottom_toBottomOf),并且view没有设置wrap_content和fixed(也就是说还有剩余的位置可供偏移,默认是
// 居中)
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginEnd="8dp"
android:text="btn3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.4"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>

4,Chain(链条,影响位置和对齐)

在ConstraintLayout中生成一个链条,最少只需要两个View相互指定Constraint。作用类似于Flex 布局中的轴axis。

image-20190901220633245

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintEnd_toStartOf="@+id/button3"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>

<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/button2"
app:layout_constraintTop_toTopOf="parent"/>

Chain既然像Flex,那确实是可以像Flex类似的用的:

是是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// layout_constraintHorizontal_chainStyle 默认是spread,还有Packed和Spread Inside可以选,
// layout_constraintHorizontal_weight 和Learnelayout的weight一样用法,需要0dp(match_constraint)

<Button
android:id="@+id/button2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintEnd_toStartOf="@+id/button3"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>

<Button
android:id="@+id/button3"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintEnd_toStartOf="@id/button4"
app:layout_constraintStart_toEndOf="@+id/button2"
app:layout_constraintTop_toTopOf="parent"/>

<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/button3"
app:layout_constraintTop_toTopOf="parent"
/>

image-20190901222300428

5,Ratio(比例,影响大小)

1
2
3
4
5
6
// 根据宽高比例自适应View大小,至少一个被设为0dp(match_constraint),下面的代码你将收获一个全屏的按钮
<Button android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"/>

6,Visibility behavior(影响位置)

aa

1
2
3
// 如上图,A的margin_start 16,B的margin_start 20,那A变gone了,B希望start margin加大一点,只需要
// 加一句
app:layout_goneMarginStart="@dimen/margin_26"

7,Circular positioning(影响位置)

image-20190901224428846

1
2
3
4
5
6
// 比较少能用上,原理就是A根据原点半径和角度来定位B
<Button android:id="@+id/buttonA" ... />
<Button android:id="@+id/buttonB" ...
app:layout_constraintCircle="@+id/buttonA"
app:layout_constraintCircleRadius="100dp"
app:layout_constraintCircleAngle="45" />

8,Keyframe animations(影响大小,位置,但不会改变颜色)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// MotionLayout才是完全体,不过这个也很简单好用了

// MainActivity.kt
private lateinit var constraintLayout: ConstraintLayout
fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.keyframe_one)
constraintLayout = findViewById(R.id.constraint_layout) // member variable
}

fun animateToKeyframeTwo() {
val constraintSet = ConstraintSet()
constraintSet.load(context, R.layout.fragment_key_2)
TransitionManager.beginDelayedTransition(constraintLayout)
constraintSet.applyTo(constraintLayout)
}

// layout/keyframe1.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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:id="@+id/faceLayout"
android:layout_height="match_parent">

<Button
android:id="@+id/button2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

// layout/keyframe2.xml
// Keyframe 2 contains another ConstraintLayout with the final positions
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/keyLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">

<Button
android:id="@+id/button2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

总结:用过就回不去了,没有Constraintlayout甚至不会布局。

参考

https://developer.android.com/reference/android/support/constraint/ConstraintLayout#VirtualHelpers

https://developer.android.com/training/constraint-layout#constrain-to-a-barrier