第3章
Android常用布局
目前使用Android Studio开发设计UI(User Interface,用户接口)时还无法提供类似Visual Studio所见即所得的图形界面设计方式,但依靠线性布局(LinearLayout)、表格布局(TableLayout)、相对布局(RelativeLayout)、帧布局(FrameLayout)、绝对布局(AbsoluteLayout)、网格布局(GridLayout)和约束布局(ConstraintLayout)等已经能开发出各式各样 UI界面。本章节通过案例来学习各种布局的特点和相关属性设置。
在Android的UI开发中需要了解长度单位的几种表示方式。
视频讲解
3.1Android长度单位
Android布局设计的长度单位没有完全统一。常见的单位有px、dp、sp、pt、mm和in共6种。在布局文件的长度相关属性值中输入数字后,
图31智能提示中的长度单位
弹出智能提示中的长度单位,如图31所示。智能弹出提示框中会显示6种长度单位供开发人员选择。
以下是与长度相关的技术术语。
(1) px: 即像素(pixels),1px代表屏幕上一个物理的像素点。
(2) dp: 独立像素密度(Density Independent Pixels),早期叫dip),与像素无关。
(3) sp: 主要用于设置字体尺寸,会随着系统的字体大小而改变,即同样大小的dp和sp字体,在Android设置中改变字体大小后,以sp为单位的字体会随系统字体大小改变而改变,以dp为单位的字体大小不会改变。正常字体1dp=1sp,大字体和超大字体1sp>1dp。以下是布局文件代码。
【main.xml】
01
02
06
07
12
13
18
以上代码按系统默认字体大小的效果如图32所示。
系统字体改成大字体后的效果如图33所示。
(4) in: 英寸,1in=2.54cm,一般用于屏幕对角线尺寸单位。
(5) pt: 磅,1in/72的长度,1pt=1in*2.54cm/72in≈0.035cm。
(6) 分辨率: 如果屏幕的分辨率是 1080*1920,是指水平方向上的像素数是1080px,垂直方向上像素数是1920px,屏幕分辨率如图34所示,根据勾股定律对角线则为2203px。
图32系统默认字体大小的效果
图33系统字体改成大字体后的效果
图34屏幕分辨率
(7) 屏幕像素密度: 图34的对角线的像素数为2203px,如果是5英寸屏(指对角线尺寸),屏幕像素密度为2203÷5=440; 如果是6寸屏,屏幕像素密度为2203÷6=367。如此一来就会有很多不同的屏幕像素密度,同样的图片在屏幕中显示所占比例也就不同。为此Android引入像素密度与逻辑密度的概念。
(8) 像素密度与逻辑密度: 像素密度(dot per inch,dpi)就是每英寸的像素点数,不同的像素密度对应不同的Android dpi名称。如像素密度是160,意思是每英寸像素数160px,对应的dpi名称为mdpi。Android Studio在构建项目时会自动建立一个名为HelloAndroid\app\src\main\res\mipmapmdpi的目录,目录中默认提供的图片分辨率为48×48。Android Studio同时也会建立其他Android dpi名称的目录,让不同分辨率、不同尺寸的Android设备自行调用不同目录中的图片文件,以保证不同参数的屏幕尽可能显示相似界面。像素密度是40的倍数。像素密度与逻辑密度如表31所示。
表31像素密度与逻辑密度
Android dpi名称分辨率默认图片尺寸像素密度比例逻辑密度
xxxhdpi3840*2160192*192640164
xxhdpi1920*1080144*144480123
xhdpi1280*72096*9632082
hdpi480*80072*7224061.5
mdpi480*32048*4816041
ldpi320*24036*3612030.75
逻辑密度是以160dpi为基准,其他像素密度与160dpi的比值,或者是像素密度对应的比例值除以4,也是dp转px的系数。
dp与px的换算关系为: px=dp*dpi/160,如160dpi的mdpi,1dp*160dpi/160=1px,对于基准160dpi而言,逻辑密度density=1(160dpi/160dpi或者比例4/4)。又如240dpi的hdpi,1dp*240dpi/160=1.5px,对于基准160dpi而言,逻辑密度density=1.5(240dpi/160dpi或者比例为6/4)。换言之,要将mdpi的图片在hdpi上也能同比例显示,只需将mdpi下的图片放大1.5倍即可。在实际的设计中还可以使用Java代码获取屏幕分辨率然后换算比例进行屏幕动态布局,以此保证在不同分辨率的屏幕下都能按同样比例显示。以下是获取屏幕相关尺寸参数代码。
【FirstActivity.java】
01public class FirstActivity extends Activity
02{
03
04@Override
05public void onCreate(Bundle savedInstanceState)
06{
07super.onCreate(savedInstanceState);
08setContentView(R.layout.main);
09
10String str = "";
11DisplayMetrics dm = new DisplayMetrics();
12dm = this.getApplicationContext().getResources().getDisplayMetrics();
13str += "屏幕分辨率为:" + dm.widthPixels
+ " * " + dm.heightPixels + "\n";
14str += "水平方向分辨率:" + dm.widthPixels + "px\n";
15str += "垂直方向分辨率:" + dm.heightPixels + "px\n";
16str += "逻辑密度:" + dm.density + "\n";
17str += "xdpi:" + dm.xdpi + "像素/英寸\n";
18str += "ydpi:" + dm.ydpi + "像素/英寸\n";
19Log.i("xj", str);
20}
21}
程序运行结果如下:
I:屏幕分辨率为:1080 * 1776
水平方向分辨率:1080px
垂直方向分辨率:1776px
逻辑密度:3.0
xdpi:480.0像素/英寸
ydpi:480.0像素/英寸
将上面运行结果xdpi的480除以基准像素密度160,得到的结果3就是逻辑密度。
视频讲解
3.2线 性 布 局
线性布局是Android早期开发版本的默认布局,使用LinearLayout标签,通过设置android:orientation属性值为horizontal(水平)或vertical(垂直)来将其内的控件按照水平方向或垂直方向依次排列。排列的控件不用指定位置的相关属性(简单即是美的体现),控件显示位置与在线性布局中出现的先后顺序相关。线性布局可以互相嵌套形成更复杂的结构。布局文件代码如下:
【main.xml】
01
02
07
08
12
13
20
21
28
29
36
37
38
42
43
50
51
58
59
66
67
74
75
76
77
线性布局的运行结果如图35所示。
从布局代码和组件树(见图36)所示的结构可以看出,整个布局的结构为,最外层是一个垂直线性布局,内嵌一个水平线性布局(含3个TextView)和一个垂直线性布局(含4个TextView)。TextView控件的android:layout_width属性常用值有match_parent(早期版本为fill_parent,将TextView控件宽度设置为父容器宽度)、wrap_content(按TextView的显示文本内容长度来设置宽度,如果文本内容显示宽度超过父容器宽度则将宽度设为父容器宽度,多余的文本在下一行中显示)和具体数值宽度(如第30行的200dp)。android:layout_height属性用于设置高度,与设置宽度概念类似。
图35线性布局的运行结果
图36组件树
显示界面标题栏下的第一行分别显示红、绿、蓝3种颜色的TextView,前两个TextView按文字长度显示,第三个TextView定义的宽度为200dp,大于文字长度,所以TextView控件中文字离控件右边缘有一段距离。
后面几行显示了不同文字长度的TextView在android:layout_width宽度属性分别设置为match_parent和wrap_content时的显示差异。读者可修改代码观察运行结果中TextView的宽度和折行变化。
在图36的组件树右侧单击图标按钮可以修改控件是否可见。图标提示控件代码有警示信息。本案例中将TextView中的文字直接以字符串形式赋予了android:text,而Android Studio建议采用“@string/字符串变量名”的方式定义TextView的值。在Code视图中警示方式会变为将相应属性背景色变为黄色,如图37所示。在Design视图中单击橙色三角形,在弹出信息栏中单击Fix按钮(如果是Code视图,则将光标置于字符串中,按lt+Enter快捷键,在上下文菜单中选择Extract string resource菜单),在弹出的界面中输入字符串变量名,系统自动在strings.xml中注册相应的字符串变量名和对应的字符串值。如果弹出的三角形是红色,代表控件的属性设置中有错误。开发人员可以用鼠标拖动组件树中的控件位置来改变控件显示顺序,Code视图中的代码也会自动调整顺序。右击控件树中的线性布局,在弹出的快捷菜单中可选择转换为其他布局方式。
Code视图如图37所示,android:background属性用来设置背景色。android:textColor属性用来设置文字颜色,颜色值使用6位十六进制数表示,每2位为一组,分别表示红、绿、蓝,合成的颜色效果在左侧行号后显示为颜色方块或在Design视图中查看效果。
用鼠标双击Code视图行号后的颜色方块弹出调色盘,如图38所示,可实现可视化的颜色调配选择。如果在图38中选择Resources选项卡,可选择系统自定义的颜色,如第16行的“@android:color/holo_red_dark”,代表使用Android自定义的颜色。Design视图中显示的效果与实际运行结果可能会有差异,以实际运行结果为准。
图37Code视图
图38调色盘
3.3边 线 和 角
所有的布局方式(含LinearLayout)设定的边界都是没有线条标识的,如果想给相关布局画出边线可以采用以下方式:
【main.xml】
01
02
08
09
15
在线性布局标签属性中添加第5行的代码,设置线性布局背景使用drawable目录下的shape_conner.xml文件(此时shape_conner.xml文件被当成一个图片文件使用)。
【shape_conner.xml】
01
02
03
04
05
06
11
12
15
第4行定义了背景色。
第6~10行定义了屏幕4个角的转角半径,如果半径设为0则为直角。
第13行定义了线条的宽度。
第14行定义了线条的颜色。
给布局添加边线和角的运行结果如图39所示。
图39给布局添加边线和角的运行结果
当前屏幕为圆角的手机越来越多,而大多数模拟器的4个角是直角,如果设计时就处理边角显示适配,则可以考虑采用此方法。
3.4layout_weight
Android布局中设置控件的宽度一般使用android:layout_width属性,有时会采用属性android:layout_weight与android:layout_width配合使用。android:layout_weight的作用是设定同一父容器内控件的长度或宽度的占比。先通过一个布局代码来看实际运行结果,再分析与显示结果不同的原因。
视频讲解
【main.xml】
01
02
06
07
11
12
18
19
25
26
27
31
32
38
39
45
46
47
整个布局的结构是一个垂直线性布局内嵌两个水平线性布局,每个水平布局中又放置两个按钮,“按钮1”和“按钮3”的layout_weight设为1,“按钮2”和“按钮4”的layout_weight设为2。“按钮1”宽度占屏幕的1/3,“按钮2”宽度占屏幕的2/3,这与设想的相符。“按钮3”宽度占屏幕的2/3,“按钮4”宽度占屏幕的1/3,这是怎么回事呢?产生差异的原因是按钮布局android:layout_width属性是wrap_content还是match_parent。
(1) 当android:layout_width="wrap_content"时(假设按钮的文本内容长度没有超过屏幕占比),两个按钮占屏幕一行,每个按钮按各自占比设置宽度,如此例中layout_weight分别为1和2,则总和为3,“按钮1”占1/3,“按钮2”占2/3。
图310layout_width结合layout_
weight运行结果
(2) 当android:layout_width="match_parent"时,各按钮的宽度等于父容器宽度加上剩余空间的占比。设父容器宽度为L,“按钮3”和“按钮4”的android:layout_width="match_parent",所以两个按钮宽度都应该为L,剩余宽度就为父容器宽度减去两个按钮的宽度: L-(L+L)=-L。“按钮1”占1/3,所以“按钮3”的实际宽度是L(父容器宽度)+(-L)(剩余宽度)*1/3=L+(-L)*1/3=2L/3。同理,“按钮4”的实际宽度为L/3。
由此可以看出,Android在长度设置上除了长度单位不同外,还要考虑不同属性之间的影响。layout_weight属性并不能精确地控制控件的宽度(或高度),还会受控件内文字长度的影响(即使文字长度未超过屏幕占比)。如果想精确控制各控件的长度对齐,需考虑使用其他布局。
layout_width结合layout_weight运行结果如图310所示。
【提问】删除第35和42行将如何显示?
3.5绝 对 布 局
绝对布局使用android:layout_x和android:layout_y来设定屏幕水平方向和垂直方向坐标,这种定位方式简单直接,但对于不同分辨率的屏幕,绝对布局的显示效果会有差异,这也是不推荐使用绝对布局的原因。一种解决方式是先获取屏幕的分辨率,然后按照百分比计算绝对布局的x、y坐标。在Android Studio中查看绝对布局源码,单词AbsoluteLayout会出现一条中画线,将鼠标指针放在单词AbsoluteLayout上,弹出弃用提示,如图311所示。按下快捷键Alt+Shift+Enter,AbsoluteLayout标签属性中会自动添加一行属性tools:ignore="Deprecated"来忽略弃用提示,此时会看到单词AbsoluteLayout上的中画线消失。“tools:”标识并不影响布局设计,只是对界面设计人员起到辅助的作用。
图311弃用提示
以下是绝对布局的源码和运行结果。
【main.xml】
01
02
07
08
14
15
16
22
23
24
30
31
32
39
40
41
48
49
50
第11~12行TextView的定位为(0,0),从运行结果上可以看出原点在标题栏下方的显示区左上角(不是整个屏幕的左上角)。
第24~39行中定义的两个TextView坐标非常接近,显示的文本也就有部分重叠。这是其他布局方式难以达到的效果(帧布局和约束布局除外)。这也是为什么还是有一部分开发人员喜欢使用绝对布局,特别是针对单一设备,此时不用考虑屏幕尺寸和分辨率带来的差异。
绝对布局运行结果如图312所示。
图312绝对布局运行结果
3.6相 对 布 局
在新版的Android Studio中,相对布局已经归入Legacy控件栏中,意味着以后可能会弃用。这里仍然讲解相对布局,其一是因为很多网络上的案例仍在使用相对布局; 其二是相对布局中的一些属性和概念也可以用于其他布局中。相对布局的常用属性有以下4类。
(1) 当前控件与父容器的相对位置,属性值为true或者false。与父容器相对位置如图313所示,粗线代表控件对齐的边。layout_alignParentStart属性默认对应layout_alignParentLeft,layout_alignParentEnd属性默认对应layout_alignParentRight,此时默认布局方向是lefttoright。如果采用righttoleft布局方向,则layout_alignParentStart属性对应layout_alignParentRight,layout_alignParentEnd属性对应layout_alignParentLeft。本书后续涉及带Start和End的属性按默认lefttoright布局方向解释为Left和Right。新版Android Studio推荐使用Start和End替代Left和Right。
图313与父容器相对位置
(2) 当前控件与参考控件的相对位置,属性值必须为参考控件id的引用名“@id/idname”。与参考控件相对位置如图314所示。
图314与参考控件相对位置
(3) 属性值是具体的长度或像素,如android:layout_marginTop="100dp",指明当前控件离父容器上边缘100dp距离。
(4) 定义控件边界的空白宽度和控件内部填充宽度属性,相关属性如图315所示,属性值为长度或像素值。
图315margin与padding
【main.xml】
01
02
05
06
12
13
19
20
28
29
36
37
44
45
图316相对布局运行结果
第10行指明TextView与父容器的上边缘间隔为100dp。
第24行指明button1在文本框editText1的下方。第25行指明button1与父容器右对齐。第26行让button1右侧与对齐边线保持80dp的间隔。
第33行指明button2与button1底部对齐。第34行指明button2与父容器左对齐。
第41行指明button3对齐父容器的底部,第42行指明button3的左边与button2的右边缘对齐。相对布局运行结果如图316所示。
案例库中还列出了使用Java代码来实现动态设定相对布局,由于还未讲解按钮监听器的使用,读者可在学习相应章节后自行查看、使用相应代码。
【提问】第42行换成android:layout_toEndOf="@+id/button1"会如何?
如果将第42行的button2改为button1,运行后会发现button3不见了。这是因为button1在第25行指明是右对齐屏幕右边缘,虽然第26行留出了80dp的间隔,但其他控件如果与button1右边缘对齐,还是要以没有间隔的位置为准,所以button3的位置在屏幕右侧边缘之外导致看不见了。
视频讲解
3.7帧布局
帧布局为每个加入其中的控件创建一个区域(称为一帧),这些帧会根据layout_gravity属性执行相应对齐。未设置layout_gravity属性值时,控件默认在父容器的左上角。符号“|”用于定义同时拥有多个属性值。layout_gravity属性值定位示意图如图317所示。
图317layout_gravity属性值定位示意图
【main.xml】
01
02
06
07
13
14
20
21
30
31
39
47
图318帧布局运行结果
第7~19行的两个TextView没有定义layout_gravity,将叠加显示在父容器(在此例中TextView的父容器为FrameLayout)的左上角。叠加的顺序是后定义的控件显示在之前定义的控件之上。
第25行TextView同时指定为垂直方向正中和水平方向正中,显示的效果是父容器的中心位置。
第35行定义为bottom,此时变更为bottom|end也是相同的效果,因为当前TextView的layout_width属性定义为match_parent,意味着TextView控件宽度与父容器等宽,所以再加上右对齐属性还是显示为与父容器等宽的右对齐。
案例中用两种方式(Android自带颜色和十六进制)定义颜色,6位十六进制数折合二进制是24位,也就是平时所说的24位色。帧布局运行结果如图318所示。
视频讲解
3.8表 格 布 局
表格布局是按照行列的表格方式排列布局,其中TableRow用于同一行内多个控件对象的排列,如果没有定义TableRow,则一个控件对象占用表格的一行。为了控制表格的拉伸和收缩,可设置以下属性。
(1) android:collapseColumns: 设置需要被隐藏列的序号,相应列不可见。
(2) android:shrinkColumns: 设置允许收缩列的序号。
(3) android:stretchColumns: 设置允许拉伸列的序号。
【注】列的序号是从0开始的。
下面的案例设计了一个规整的表格界面,代码如下:
【main.xml】
01
02
07
08
14
20
21
22
23
29
30
36
37
42
43
44
45
46
51
52
57
58
63
64
65
66
67
72
73
78
79
84
85
86
87
88
93
94
99
100
105
106
第6行指明表格布局的0~2列是可拉伸的,本案例中表格布局有3列,默认这3列平分父容器宽度。当某列有单元格的字符超出平分表格列格宽度时,此列的宽度会自动扩展,其他列相应收缩,直至其左侧列宽度等于文本宽度或者其右侧列被挤出父容器之外。如果删除此行,则后续TableRow中控件按指定宽度或默认warp_conent显示。
第8~19行定义的TextView没有在TableRow标签内。表格布局中没有在TableRow标签内的控件默认都独占一行,所以两个TextView分别占了两行。
第21~42行属于TableRow标签范围,TableRow标签内定义的TextView都在同一行。
第27行的android:gravity用于TextView控件内部的文字对齐,android:layout_gravity用于当前控件对父容器的对齐。详细讲解参见“4.1.3 layout_gravity与gravity”中的案例。
表格布局运行结果如图319所示。
图319表格布局运行结果
下面的案例设计了一个不规则表格界面,代码如下:
【main.xml】
01
05
06
12
13
18
19
20
21
26
27
32
33
38
39
40
41
46
47
48
49
54
55
60
61
66
67
68
69
74
75
76
77
82
83
88
89
90
91
图320不规则表格布局运行结果
第10行指明第2列是可收缩的(从0开始计算)。
第11行指明第3列是可拉伸的。
第33~37行定义的按钮文字比较多,其在表格布局的第3列,按钮宽度根据文字长度而拉伸,相应定义为可收缩的第2列按钮的宽度被压缩,其超出按钮宽度的文字将折行显示。
自第41行起重新定义了一个表格布局,其中第45行定义第2列可折叠隐藏。第55~59行定义的按钮被隐藏,所以表2只显示了两列。
第69行定义新的表格布局,其中第73行定义为第2列可拉伸,在TableRow中定义了两个按钮,第2个按钮的宽度虽然设置为wrap_content,但因为缺第3列导致可拉伸的第2列填满剩余表格行宽度。
不规则表格布局运行结果如图320所示。
3.9网 格 布 局
在新版的Android Studio中,网格布局已经归入Legacy控件栏。官方更推荐表格布局作为类似场景中的布局。网格布局的默认行列高度和宽度是统一的,可以通过调整布局容器大小,设置android:layout_width或行列的权重来改变。以下案例设计一个简单计算器界面,具体代码如下:
01
07
08
12
13
16
17
20
21
22
25
26
29
30
33
34
37
38
41
42
45
46
49
50
53
54
57
58
61
62
67
68
71
72
77
78
83
图321网格布局运行结果
第4行定义网格布局有4列,第6行定义网格布局有6行,最后形成一个6行4列的网格。
第10行定义EditText可以拉伸4列宽度,再结合第11行实现EditText占4列宽度。同样第74~75行指明加号按钮占两行。
网格布局运行结果如图321所示。
从本案例可以看出,网格布局更适用于较为规整的行列表格,网格布局中的控件按指定的行列顺序依次排列,但相应的灵活性也比表格布局低。网格布局还存在控件间隙不一致的缺陷。
3.10约 束 布 局
3.10.1约束布局基础
视频讲解
从 Android Studio 2.3版本起,约束布局是Android Studio布局文件的默认布局。其他布局方式在实现复杂一些的布局设计时存在多种或多个布局嵌套的情况,设备调用这样的布局文件就需要花费更多的时间。约束布局在灵活性和可视化方面比其他布局方式更胜一筹(与号称宇宙第一IDE的Visual Studio的图形化界面设计相比还有差距)。为减少布局嵌套,使用约束布局的属性更接近于相对布局属性。因此,很多资料在讲解约束布局时采用与相对布局类似的方式,不可避免地就要讲解一堆约束布局的定位属性。约束布局与其他布局的最大区别在于支持图形化拖放操作。可以在布局文件的Design视图中采用鼠标拖放操作结合属性栏窗口设置完成约束布局的界面设计,大幅简化布局代码输入和控件间定位关系的人为判断。
【注】约束布局可以实现图形化拖放设计,但不是真正的所见即所得。Design视图中的效果与实际运行结果还是有所不同的,经常出现的问题是定位属性设置错误或缺项。
选择布局文件main.xml,在Design视图的工具栏中拖放两个Button按钮到Design界面中,如图322所示。
图322约束布局Design视图
此时由于未加入约束定位,组件树中的控件都会用红色惊叹号标识。运行程序,两个按钮会显示在屏幕左上角,坐标为(0,0)。
图323控件句柄
选中按钮控件,显示控件句柄,如图323所示。4个角上的正方形句柄用于调整控件的尺寸,圆形句柄用于设置控件的定位。
在“按钮1”左侧圆形句柄上按住鼠标左键并拖向屏幕左侧,此时“按钮1”会自动靠到屏幕左侧,意味着“按钮1”与屏幕左侧对齐。选中“按钮1”再次向右拖动到如图324所示的位置,4个方框圈出数字为132,单位为dp,代表“按钮1”相对父容器(此时为屏幕左边缘)的距离为132dp。也可以在属性栏或布局栏(图中右侧中间的Constraint Widget)中修改数字改变距离值。以上操作对应两个属性:
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="132dp"
注意,一个前缀是“app:”,另一个前缀是“android: ”。前者引用app目录下build.gradle文件中androidx.constraintlayout:constraintlayout定义的属性; 后者引用系统定义的属性。
图324“按钮1”相对父容器水平方向定位
图325设定“按钮2”相对“按钮1”
的垂直方向定位
同样,选择“按钮1”上方的圆形句柄定位屏幕上边缘距离。重新运行程序,“按钮1”将出现在设定的位置。“按钮2”还是在屏幕左上角。可以采用同样的方式对“按钮2”进行操作,也可以采用相对“按钮1”的位置进行定位。例如,将“按钮2”定位在“按钮1”下方50dp位置,“按钮2”右侧离屏幕右侧100dp。为实现上述定位要求,先选中“按钮2”上方的圆形句柄并拖动到“按钮1”(此时“按钮1”上会出现上、下两个圆形句柄)下方的圆形句柄,调整距离值为50dp。如果进行此操作,鼠标左键释放时指向“按钮1”区域而非圆形句柄,会弹出如图325的上下文菜单,可选择“按钮2”是对齐“按钮1”顶部还是底部。
选择“按钮2”右侧的圆形句柄并拖动到屏幕右侧,调整距离值为100dp,“按钮2”的定位如图326所示。
图326“按钮2”的定位
约束布局运行结果如图327所示。
设计图与运行图相比,两个按钮的显示位置还是有差异,这是因为设计图并不是按实际设备的屏幕分辨率来设定的,距离真正的所见即所得还有一定的差距。当控件定位属性不全时,组件树会有红色圆形警告标识,Code视图的控件标签也会标红,同时会多出一个前缀为“tools:”的属性。例如,tools:layout_editor_absoluteX="180dp"指明当前控件在Design视图中的X轴坐标是180dp。此属性只在Design视图中起辅助定位时使用,在运行时还是被忽略的,即运行时控件在X轴坐标还是0(对齐屏幕左侧)。
如果对“按钮1”在水平方向分别将左边缘和右边缘依次定位到屏幕两侧,Android Studio会将“按钮1”在水平方向自动置中,定位标尺直线变为折线,其含义是最终定位还需考虑其他定位属性。Design视图显示水平方向双重定位,如图328所示。运行程序时,“按钮1”也会显示在屏幕水平方向正中央。
图327约束布局运行结果
图328水平方向双重定位
此时“按钮1”隐含以下属性(此时布局文件中不会显示此属性):
app:layout_constraintHorizontal_bias="0.5"
图329layout_constraintHorizontal_bias属性
只要将“按钮1”水平拖动,如图329所示,将多出属性layout_constraintHorizontal_bias,其代表左右定位尺寸(左右圆形句柄到定位基线的距离,定位基线可能是屏幕边缘,也可能是其他控件边缘)的偏离率,0.5代表按钮左右定位长度相等,小于0.5时按钮偏向左边,大于0.5时按钮偏向右边。可以在属性栏中直接修改偏离率,也可以在Inspector(layout_constraintHorizontal_bias属性,如图329标注Constraint Widget的图形部分)中直接拖动带数字的进度条修改偏离率,还可以通过直接拖动“按钮1”的方式或在Code视图中修改偏离率。
如果希望“按钮1”无论怎么左右移动,按钮左侧到屏幕左边缘均至少保留50dp的间隙,可先选中“按钮1”,然后在Inspector左侧文本框中输入50,layout_marginStart属性如图330所示。“按钮1”左侧折线连接一段50dp的直线,相应的layout_marginStart属性为50dp,此时偏离率是不计算这50dp直线长度的,即使app:layout_constraintHorizontal_bias属性值等于0,“按钮1”左侧还是会保留50dp的空白,此时拖动“按钮1”到50dp时就无法再向左边移动。
图330layout_marginStart属性
图331Inspector
选中控件的圆形句柄,按delete键可以删除选中的定位。如果选中的是控件,按delete键会删除整个控件(含控件定位属性)。
Inspector如图331所示。
表示wrap_content,Code视图中的属性为android:layout_width="wrap_content"。
表示固定值,给控件指定了一个固定的长度或者宽度值。
表示任意长度或者宽度值。以控件宽度为例,Code视图中控件的宽度属性变为android:layout_width="0"。配合其他定位属性,控件宽度可能为wrap_content(左右圆形句柄只有一个用于定位)、match_parent(左右圆形句柄都用于定位)或任意长度(左右圆形句柄都用于定位,且同时定义了layout_marginStart或layout_marginEnd)。
在“按钮1”上右击,弹出控件快捷菜单,如图332所示。
选择Show Baseline菜单,会在“按钮1”上显示Baseline(即基准线),如图333所示。单击“按钮1”的基准线并拖放到“按钮2”的基准线位置就可实现基准线对齐。基准线对齐主要用在多个高度不同的控件间实现文字对齐(如果按钮中文字显示为多行或两个按钮的字体大小不同,基准线对齐是将第一行文字底部对齐)。
图332控件快捷菜单
图333Baseline
选择Clear Constraints of Selection菜单,将删除选中控件的所有约束布局属性。
选择Convert view菜单,弹出转换控件窗口,如图334所示。选择想变更的控件类型(Android中称之为View),可以在保留定位数据的情况下变更控件类型。
其他菜单项是常用选项,如复制、粘贴等,还有几个菜单项在设计图上方的工具栏中有相同功能按钮。相关功能可参看后续内容。
Transforms如图335所示。在Transforms中可设置View的X、Y、Z轴的旋转和坐标参照点的偏移。
图334转换控件窗口
图335Transforms
图335中对Z轴旋转了45°,对应属性android:rotation="45"。如果定义在按钮控件内,则对应按钮控件旋转45°。如果定义在ConstraintLayout标签内,则ConstraintLayout标签内的所有控件都会旋转45°。
【main.xml】
01
02
08
09
18
19
29
30
第7行定义约束布局内所有控件都旋转-45°。此时button1和button2都旋转-45°。
第25行定义button2旋转90°,扣除约束布局旋转的-45°,最终效果是button2旋转45°。
android:rotation属性运行结果如图336所示。其前缀是android,属于android命名空间,所以也可以用在其他布局中,如线性布局。android:rotation是以控件中心且垂直于屏幕为轴心的Z轴旋转。android:rotationX和android:rotationY分别对应控件X方向中心轴和Y方向中心轴旋转。
视频讲解
3.10.2Barrier
在实际布局中可能会遇到Barrier定位,如图337所示。希望“按钮3”布置在“按钮1”和“按钮2”最右侧40dp的位置,即如果“按钮2”比“按钮1”更靠右,则“按钮3”左侧距离“按钮2”右侧40dp; 如果“按钮1”比“按钮2”更靠右,则“按钮3”左侧距离“按钮1”右侧40dp。
图336android:rotation属性运行结果
图337Barrier定位
为此约束布局引入了Barrier的概念,增加了Barrier标签,其中定义以下两个属性:
app:barrierDirection="right"
app:constraint_referenced_ids="button1,button2"
以上两行是在“按钮1”与“按钮2”右侧建立Barrier,Barrier类似一堵墙,两个按钮谁更靠右,这堵墙就以谁为边界。而“按钮3”左侧定位以这个墙为基准。使用图形化界面建立Barrier方法如下: 建立“按钮1”与“按钮2”并完成相应定位。选中“按钮1”,单击按钮,弹出如图338所示的界面,选择Add Vertical Barrier菜单添加一个垂直方向Barrier。
默认添加的Barrier与“按钮1”左侧对齐。修改Barrier属性,如图339所示,在barrier属性栏中修改barrierDirection属性为right(或者是end)、constraint_referenced_ids属性为“button1,button2”(默认只有button1)。如果事先已经明确“按钮1”和“按钮2”共同建立Barrier,也可同时选中“按钮1”和“按钮2”,然后再添加垂直方向的Barrier,constraint_referenced_ids属性自动填写为“button1,button2”。
图338添加垂直方向Barrier
图339修改barrier属性
选中“按钮3”,添加左侧定位到任意控件右边缘,使“按钮3”的Declared Attributes属性栏中多出一项layout_constraintStart_toEndOf属性(也可以在All Attributes中查找对应属性),将其改为要对齐的Barrier,将layout_marginStart改为40dp。Android Studio自动生成布局文件,代码如下:
【main.xml】
01
02
07
08
17
18
27
28
37
38
45
46
第35行定义button3左侧对齐到barrier1右侧。
第38~44行定义barrier1,其中第43行定义barrier1阻挡的控件是button1和button2,第42行指明barrier1阻挡方向是右侧。
视频讲解
3.10.3Guideline
之前的控件定位都是基于屏幕(更准确的称呼为控件父容器的约束布局)或者是可见控件,约束布局中引入了一种在运行时看不见的Guideline——定位基准线作为定位补充。添加Guideline如图340所示,分别添加垂直和水平方向的Guideline。
拖动Guideline至所需位置,指定左定位150dp的垂直方向Guideline,如图341所示。
图340添加Guideline
图341指定左定位150dp的垂直方向Guideline
单击左侧向上箭头将切换为向下箭头,此时Guideline按Bottom位置定位,再次单击将切换成百分比符号,代表Guideline使用位置百分比设置自身定位(Guideline属性app:layout_constraintGuide_percent="0.33"是将Guideline设置在屏幕长度或宽度的1/3位置),指定百分比的水平方向Guideline,如图342所示。
【注】目前约束布局版本水平方向的Guideline可通过鼠标单击实现、、三种定位方式循环切换,垂直方向的Guideline的切换还有问题。约束布局的功能在不停地升级,或许下一版本会将这个问题解决。
添加控件并将其定位指向Guideline,使用Guideline定位如图343所示。实际运行时Guideline是不可见的。
图342指定百分比的水平方向Guideline
图343使用Guideline定位
布局代码如下:
【main.xml】
01
02
07
08
14
15
21
22
30
31
第8~13行定义了一个距离屏幕左边界150dp的垂直方向Guideline。
第15~20行定义了一个距离屏幕上端1/3位置的水平方向Guideline。
第22~29行定义了一个按钮,其顶端和左侧分别定位到两个Guideline。由于Guideline是一条线,因此第28行layout_constraintStart_toStartOf换成layout_constraintStart_toEndOf的效果是一样的。
3.10.4Group
使用约束布局的一个目的是减少布局的嵌套。如果想把多个控件设置为隐藏,就需分别设置各控件的可视化属性。使用Group相当于将各控件进行分组,设置Group属性等效于将Group中各控件设置相同属性。在Design视图中添加Group时需先选择要包含的控件,然后如图344所示选择Add Group菜单项添加的Group对象,新添加Group对象取名为group1。
group1在程序运行时是不可见的,要将相应的控件放入group1,最便捷的方式是在组件树中将相应的按钮控件拖放到group1,如图345所示。
图344添加Group
图345将按钮添加到group1
此时group1中包含了button1和button2。Group的关键代码如下:
01
其中,第5行定义group1中包含button1和button2。运行程序,可以看到屏幕上显示button1和button2。改变group1的visibility属性为invisible,再次运行程序,屏幕上不显示button1和button2。需要注意的是,group1的属性栏中有两个visibility属性,如图346所示。
图346visibility属性
上方的visibility属性在Code视图中显示为android:visibility,其设定的值影响相关控件在Android设备上是否显示。下方的visibility属性前有一个符号,在Code视图中显示为tools:visibility,其设定只影响在Android Studio的Design视图中是否显示,并不影响在Android设备上的运行显示。
图347Circle定位示意图
3.10.5Circle
视频讲解
Circle方式定位目前还无法使用图形界面操作,可以在Code视图中输入代码。Circle定位示意图如图347所示。以参考定位控件A中心为圆点,以控件B中心到控件A中心为半径,与垂直向上方向的夹角来定位控件B的位置。
01
02
07
08
17
18
26
27
第23行定义button2按照Circle方式来定位,定位基准为button1。
第24行定义夹角为45°。
第25行定义button1与button2中心点的距离为150dp。
目前,Circle方定位方式还不完善,除了不支持图形化拖曳设计以外,在Code视图下Button标签也会显示为红色,组件树中button2也会用红色惊叹号标识,提示相关约束属性不完备。
视频讲解
3.10.6Chain
Chain用于指定链式约束。Chain示意图如图348所示。图348中,控件A与控件B相互约束形成一个简单的链式约束。
链式约束中的第一个控件称为Chain Head。在整个约束链中只要在Chain Head中定义链式约束类型即可。Chain Head示意图如图349所示。
图348Chain示意图
图349Chain Head示意图
在Head控件中添加layout_constraintHorizontal_chainStyle属性,ChainStyle属性及示意图如图350所示。5种链式约束类型的主要区别是控件间的间隔或控件自身宽度比例。如果Head控件中未设置layout_constraintHorizontal_chainStyle属性,则默认为Spread Chain类型。
图350ChainStyle属性及示意图
以下是根据官方代码编写的案例,分别实现图350的5种链式约束类型。
【main.xml】
01
02
06
07
08
16
17
25
26
34
35
36
45
46
54
55
63
64
65
73
74
83
84
93
94
95
96
105
106
114
115
123
124
125
126
136
137
145
146
154
155
5种链式约束类型运行结果如图351所示。
图3515种链式约束类型运行结果
在Code视图中按钮的文本是小写,但在Design视图和运行结果中都是大写,这是因为从Android 5.0起按钮的属性默认为android:textAllCaps="true"。只要在按钮属性中增加android:textAllCaps="false"即可恢复原有的大小写显示。相应的Java命令为button.setAllCaps(false)。
3.11Space和layout_margin
在布局中有时需要将View与其他View保持一定的间隔,可以使用Space控件来填充View之间的间隔。Space控件在运行时是不可见的。也可以使用View的layout_margin、layout_marginEnd等属性来设定View与其他View的间隔。
【main.xml】
01
02
07
08
13
14
15
16
19
20
26
27
32
33
40
41
46
47
54
第16~18行定义控件Space,其高度为100dp。Space位于button1与button2之间,所以两个按钮在垂直方向上间隔100dp的距离。
第37行定义button4在上下左右4个方向都保留20dp的空白。
将第35行的match_parent换成wrap_content,注意观察button4位置的变化。
Space和layout_margin运行结果如图352所示。
图352Space和layout_margin运行结果