5.3 ConstraintLayout
所有叫“Layout”的控件都是用于排版的,就是它能决定所包含的子控件的位置。这些Layout控件有个特点:可以包含多个子控件。不同的Layout控件排列子控件的方式不一样。ConstraintLayout是既好用又强大的一个,能够应付复杂的需求,而且运行效率很高,一些由多个简单Layout组合实现的界面应该改由一个ConstraintLayout来实现。当然它也不是万能的。
我们现在的界面就是采用了ConstraintLayout作为根容器,如图5-26所示。
图5-26
红色叹号图标表示有错误,与imageView控件在同一行,说明错误就出在imageView上。用鼠标点一下这个图标,会在底部出现一个窗口,显示详细错误信息,如图5-27所示。
图5-27
错误标题的意思是:在ConstraintLayout中缺少Constraint(约束)。详细内容的第一段意思是:这个控件没有被约束。它只有设计时位置,于是在运行时会跳到(0,0)坐标处,除非添加了约束。那什么是约束呢?下节分解!
5.3.1 ConstraintLayout的原理
Constraint是“约束”的意思。我们可以为ConstraintLayout的子控件添加约束,那添加什么约束呢?位置上的约束。App要面对的设备屏幕有大有小、有方有圆、有宽有窄,要想设计一套界面来适应不同的屏幕非常难,比如不可能用固定距离的方式来保持一个控件在横向上居中。有了ConstraintLayout后可以克服这种困难,可以为一个控件添加一个“保持横向居中”的约束,无论在任何屏幕上都能横向居中。
可以设置什么样的约束呢?例如:
- 设置子控件左边或右边与ConstraintLayout的左边或右边对齐,以保持子控件居左或居右。
- 设置子控件下边或上边与ConstraintLayout的上边或下边对齐,以保持子控件居上或居下。
- 设置子控件在ConstraintLayout中横向居中、纵向居中或者横向纵向都居中。
- 设置子控件在ConstraintLayout中居中偏左、偏右、偏上或偏下。
- 设置同属于一个ConstraintLayout的子控件A在子控件B的上面、下面、左边或右边。
- 设置同属于一个ConstraintLayout的子控件A与子控件B左边对齐、右边对齐、上边对齐或下边对齐。
还可以设置子控件本身的约束,比如:
- 宽和高保持n:m的比率。
- 宽或高为某个固定的值。
- 宽或高由内容决定,比如文本控件的大小由文本中文字的个数决定,图像控件的大小由图像的实际大小决定。
5.3.2 子控件在ConstraintLayout中居左或居右
当前的页面中,TextView控件已经居中了。我们把它删掉,用ImageView来试一下。删除一个控件很简单,选中它并右击,在出现的快捷菜单中选择“Delete”命令,也可以选中它直接按Delete键。但是,有时可能因为种种原因不好选中控件,这时可以从控件树中选中,如图5-28所示。
图5-28
删掉控件之后,只剩下图像了。现在未给图像控件加任何约束,所以它就在我们当初放置的位置上。我们可以为图像添加靠左的限制,使其靠左显示。选中图像,在图像上会出现一些帮助设计的图形,移动鼠标到图像左边界中央的小圈圈上,如图5-29所示。
图5-29
从这个小圈圈中拖出一条线,代表约束。这里要靠左,所以把这条线往父控件的左边界拖,当拖到左边界时图像的边框竟然动了!不要惊慌,只需松手即可,出现如图5-30所示的效果。
小圈圈中多了个点,表示在图像的这条边上添加了约束。这个约束是图像的左边界到ConstraintLayout左边界的约束。在属性栏的简洁模式下,也可以看到约束被添加了,如图5-31所示。
在四条边中,只有左边添加了约束。数字8其实是“8dp”,这里把单位隐藏了,意思是这个控件的左边界与ConstraintLayout的左边界不要靠得太紧,留出8dp的空白。在属性栏显示所有属性的模式下,也可以看到这个约束的设置,如图5-32所示。
虽然可以直接通过为相应的属性设置值来添加约束,但是不太直观,还是尽量用鼠标拖动。
图5-30
图5-31
图5-32
在组件树中依然能看到imageView有错误,还缺少约束。为什么呢?原因很简单,坐标有两个,即横坐标和纵坐标,现在只设了横坐标,还没设纵坐标,在纵向上Layout系统依然不知道该把imageView往哪个位置放。由于默认位置是0,因此运行时会跑到最上面。若想让它靠下显示,就添加下边界的约束,如图5-33所示。
图5-33
运行App,图像是不是靠左下角了?这种Layout设计方式真的很人性化,简单又好玩!至于怎样让图像靠其他位置,读者可以自己尝试,这里就不讲了。
5.3.3 子控件在ConstraintLayout中横向居中
只要在前面的基础上再添加一个靠右的约束就行了,如图5-34所示。
图5-34
这个效果是不是很直观?Constraint就像弹簧,如果控件左右都有弹簧并且受力相等,它就位于中央了。
上下居中就不再讲了,读者可以自己尝试。
5.3.4 子控件在ConstraintLayout中居中偏左
现在图像左右居中了,还不理想,想让它居中再偏左一点,最好在四分之一处居中而不是在二分之一处居中。没问题,可以用图5-35中的设置。
图5-35
这个柄上有一个数字“50”,表示左右两边约束的力量比值,现在是50:50,拖动它试试。比如拖到25的位置(左25:右75),效果如图5-36所示。
约束就像弹簧,左右弹簧的力量进行对比,哪边力大,就偏向哪边。纵向上没有类似的柄,因为纵向上只有向下拉的弹簧,没有向上拉的弹簧,无法设置其力量对比,只要加上向上约束即可。
图5-36
5.3.5 子控件A在子控件B的上面
为了演示两个控件之间的相对位置约束,我们需要再添加一个新的控件,比如添加一个按钮,最终让按钮位于图像的上面。在此之前,需要为图像控件添加纵向的约束,先让它横、纵向都居中,如图5-37所示。
图5-37
再拖一个按钮进来(见图5-38)。这个按钮由于没有约束,因此运行时会跑到左上角。下面为它添加约束,从按钮的下边界拖动约束到图像的上边界,让它在图像的上面,如图5-39所示。
图5-38
图5-39
5.3.6 子控件A与子控件B左边对齐
在上一节的页面中,按钮在横向上没有约束,默认靠左。看着不太舒服,我们可以让按钮的左边与图像的左边对齐,也就是为按钮的左边界与图像的左边界拖出一条约束线,如图5-40所示。
图5-40
两条优美的曲线揭示了约束的存在。不过,仔细看的话,会发现按钮的左边与图像的左边还有一点差距,没有完全对齐,其实是按钮的margin属性在起作用,只要把它的左margin改为0dp即可,如图5-41所示。
图5-41
5.3.7 设置子控件的宽和高
以图像控件为例,当前宽和高为固定值,从属性栏中可以看出来(见图5-42)。
图5-42
注意红线框出的图形,这样就表示固定值,那么值是多少呢?由“layout_width”和“layout_height”属性的值决定。在红框中的图形上点一下,就会发现图形发生了变化,如图5-43所示。
图5-43
图形变成了弹簧的样子,表示宽度变成了弹性值,即宽度是可变的,同时可以看到layout_width的值变成了“0dp”,此时只要两边没有其他控件来挤占它的空间,那么它就会充满整个空间,此时在预览图中可以看到图像的宽度充满了整个父控件。
5.3.8 子控件的宽和高保持一定比例
设置图像控件宽高比为2:1,就是说图像被缩放时控件的宽高比例不变。
为了更容易看出效果,我们给图像控件设置一下背景(设置控件的background属性)。可以为它设置一种颜色,也可以设置一幅图像。先选中图像控件,再在属性栏中单击图5-44所示的按钮。出现资源选择对话框,选择一种颜色即可(见图5-45)。
图5-44
图5-45
设置背景后,再设一下图像控件的宽高比。首先选中图像控件,然后在属性栏中单击上面红色箭头所指的位置,如图5-46所示。
图5-46
在下面箭头所指的位置出现ratio(比率)输入控件(默认值是1:1),图像控件变成方的。注意,此时控件的layout_width和layout_height属性值是有一定要求的。如果这两个属性的值全不是0dp,那么比率就不起作用了,至少要有一个是0dp才起作用,另一个既可以是固定的数值,也可以是match_parent或wrap_content。如果既设置了宽和高的值又设置了比例,明显是有冲突的,该怎么解决呢?其实就是优先级的问题,谁优先级高谁起作用。
把比例改成2:1(宽是2,高是1),则会出现图5-47所示的效果。
图5-47
实际上图像太宽,已超出了显示区,于是把高度改小一些,改成100dp(注意此时应保证宽度为match_constraint。match_constraint是一个常量,它的值就是“0dp”)。最后layout文件的源码如下: