ConstraintLayout初探

什么是ConstraintLayout

ConstraintLayout 是 Google I/O 2016 上发布全新的布局方式。虽然已经发布了这么长时间,但是基于已有的布局方式基本满足需求,工作中SDK开发UI涉及比较少, ConstraintLayout 一直没有深度使用过。前几天的一次内部分享提到了ConstraintLayout,于是决定体验一把ConstraintLayout布局方式给我们开发过程带来的新的变化。ConstraintLayout 是以支持库的形式提供的,使用之前必须添加依赖。

优势

1、效率高

关于性能高的原因,Google开发者公众号中这篇文章讲的很清楚,感兴趣的同学可以仔细阅读下。最主要的原因是,使用约束极大简化了xml中元素的嵌套层级,从而提升了View绘制过程中的效率。

2、可视化编辑

基于约束可以更方便的使用可视化布局,拖拖拽拽就可以马上生成想要的页面,但是对于大部分开发者来说,还是习惯使用在xml里手动布局。

使用

这是 Play商店 中的一个页面,我们来用 ConstraintLayout实现一个item

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
56
57
<android.support.constraint.ConstraintLayout
android:id="@+id/first"
android:layout_width="match_parent"
android:layout_height="100dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">

<TextView
android:id="@+id/sort"
android:layout_width="20dp"
android:layout_height="match_parent"
android:gravity="center"
android:text="1"
app:layout_constraintLeft_toLeftOf="parent" />

<ImageView
android:id="@+id/icon"
android:layout_width="60dp"
android:layout_height="60dp"
android:background="@mipmap/ic_launcher"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@+id/sort"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:gravity="left"
android:text="YouTube"
app:layout_constraintLeft_toRightOf="@+id/icon"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/company"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="5dp"
android:text="Google LLC"
app:layout_constraintLeft_toRightOf="@+id/icon"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginTop="15dp"
android:text="25 MB • 4.3★"
app:layout_constraintLeft_toRightOf="@+id/icon"
app:layout_constraintTop_toBottomOf="@+id/company" />
</android.support.constraint.ConstraintLayout>

一层布局就搞定,很简洁,这也是性能优势的主要原因。

  • 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

可以看到有很多的约束条件可以用,其实这些约束顾名思义,基本上一看就知道是什么意思。比如下面两个TextView B 设置了 layout_constraintLeft_toRightOf (B的左是A的右)即B在A的右侧。这些属性非常简单,想了解更多,请访问这里

1
2
3
4
5
6
7
8
<TextView
android:id="@+id/A"
.../>

<ImageView
android:id="@+id/B"
...
app:layout_constraintLeft_toRightOf="@+id/A"/>

一些有趣的点

ConstraintLayout中设置依赖的条件很多,不再一一介绍,下面谈一下在学习的过程中遇到的产生疑惑的地方或官方提到的注意的点。

1、layout_constraintLeft_toLeftOflayout_constraintStart_toStartOf有什么区别

在水平方向上我们通常认为layout_constraintLeft_toLeftOflayout_constraintStart_toStartOf 是一样的,但是为什么会有这两个属性呢?一般情况下这两个属性是一样的。但是有些国家的布局或者说阅读习惯是从右向左的,再用layout_constraintLeft_toLeftOf就不合适了,这时候layout_constraintStart_toStartOf就派上用场了。下面一张图可能看着更直观,图片来源于StackOverFlow

2、 约束中不能有循环依赖关系

也就是假如有三个TextView A、B、C,不能出现某个属性A依赖B,B依赖C,C又依赖A

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
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
android:id="@+id/cycle1"
android:layout_width="60dp"
android:layout_height="30dp"
android:text="cycle1"
app:layout_constraintLeft_toRightOf="@+id/cycle3"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/cycle2"
android:layout_width="60dp"
android:layout_height="30dp"
android:text="cycle2"
app:layout_constraintLeft_toRightOf="@+id/cycle1"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/cycle3"
android:layout_width="60dp"
android:layout_height="30dp"
android:text="cycle3"
app:layout_constraintLeft_toRightOf="@+id/cycle2"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>

像以上三个 TextViewlayout_constraintLeft_toRightOf 就是循环依赖的情况,这样显示就会错乱。

3、 layout_goneMarginLeft

这个属性是什么意思呢?我们非常熟悉的是 layout_marginLeft 我们来看下面的例子,当v1
visibilityvisible 的时候左边距就是10dp,当 visibilitygone 的时候
左边距就是50dp

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
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
android:id="@+id/v1"
android:layout_width="80dp"
android:layout_height="30dp"
android:background="@color/green"
android:gravity="center"
android:text="visible"
android:textColor="@color/white"
android:visibility="gone"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/v2"
android:layout_width="80dp"
android:layout_height="30dp"
android:layout_marginLeft="10dp"
android:background="@color/green"
android:gravity="center"
android:text="gone margin"
android:textColor="@color/white"
app:layout_constraintLeft_toRightOf="@+id/v1"
app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginLeft="50dp" />

</android.support.constraint.ConstraintLayout>
4、 不可能完成的任务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<android.support.constraint.ConstraintLayout
android:id="@+id/impossible"
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/green"
android:gravity="center"
android:text="impossible"
android:textColor="@color/white"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />

</android.support.constraint.ConstraintLayout>

按照上边的布局 TextView 的宽是wrap_content 同时layout_constraintLeft_toLeftOflayout_constraintRight_toRightOf的值都是parent,这样设置的结果好像是不可能完成的任务,那么这样设置会如何显示呢?答案就是TextView 会水平居中。

5、Chain

把多个View设置到一个链上,这时对一个View的操作会对这个链上的View产生影响,如何把多个View加到一个Chain上呢,举个栗子。

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
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">

<TextView
android:id="@+id/chain_1"
android:layout_width="60dp"
android:layout_height="30dp"
android:layout_marginLeft="100dp"
android:background="@color/green"
android:gravity="center"
android:text="chain 1"
android:textColor="@color/white"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/chain_2"
app:layout_constraintTop_toTopOf="parent" />

<TextView
android:id="@+id/chain_2"
android:layout_width="60dp"
android:layout_height="30dp"
android:background="@color/green"
android:gravity="center"
android:text="chain 2"
android:textColor="@color/white"
app:layout_constraintLeft_toRightOf="@+id/chain_1"
app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

第一个TextViewapp:layout_constraintRight_toLeftOf="@+id/chain_2"和第二个TextViewapp:layout_constraintLeft_toRightOf="@+id/chain_1",这两个属性互相关联,这样这两个TextView就属于一个链,注意这可不是循环依赖。这样给第一个TextView设置android:layout_marginLeft="100dp",这个链上的整体都会产生marginLeft。

One More Thing

通过上面的几个例子,可以看到ConstraintLayout很强大,但是它能干的事情仅限于此吗?不!
它还提供ConstraintSet这个类,可以很方便的重新设置约束规则。ConstraintSet是布局中约束的集合。

它有几个方法非常有用

1
2
clone //复制当前的布局 
applyTo //应用新的布局

我们可以利用ConstraintSet动态的改变布局,甚至是利用约束的变化形成动画,举个简单的例子。布局文件比较简单,具体效果下面可以看到,然后用代码来动态设置约束。

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
public class AnimationActivity extends AppCompatActivity {

private ConstraintLayout constraintLayout;
//两个ConstraintSet对象,用于保存原始的和新的约束
private ConstraintSet newConstraintSet = new ConstraintSet();
private ConstraintSet originConstraintSet = new ConstraintSet();

Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {

//通过动态设置约束形成动画效果
if (tag) {
TransitionManager.beginDelayedTransition(constraintLayout);
newConstraintSet.setMargin(R.id.animation_left_eye, ConstraintSet.START, 20);
newConstraintSet.setMargin(R.id.animation_right_eye, ConstraintSet.END, 20);
newConstraintSet.applyTo(constraintLayout);
} else {
TransitionManager.beginDelayedTransition(constraintLayout);
originConstraintSet.applyTo(constraintLayout);
}

tag = !tag;

return false;
}
});

private boolean tag = true;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_animation);

constraintLayout = findViewById(R.id.main);

//先把原始的约束条件复制给两个Set
originConstraintSet.clone(constraintLayout);
newConstraintSet.clone(constraintLayout);


Timer timer = new Timer();

timer.schedule(new TimerTask() {
@Override
public void run() {

handler.sendEmptyMessage(0);

}
}, 0, 1000);

}
}

效果就是下面的样子啦。

注意:如果要用ConstraintSet必须给xml文件中的所有View都设置id,否则会有如下异常 java.lang.RuntimeException: All children of ConstraintLayout must have ids to use ConstraintSet