10.布局
布局是一种可用于放置很多控件的容器,它可以按照一定的规律调整内部控件的位置,从而编写出精美的界面。
当然,布局的内部除了放置控件外,也可以放置布局,通过多层布局的嵌套,我们就能够完成一些比较复杂的界面实现,下图很好地展示了它们之间的关系。
下面我们来详细讲解下Android中的几种基本的布局。新建一个UILayoutTest项目。
1、RelativeLayout(相对布局)
RelativeLayout,可以通过相对定位的方式让控件出现在布局的任何位置。相对定位的方式有两种:
(1)控件相对于父布局进行定位
使用RelativeLayout布局方式,控件中定位的属性非常多,不过这些属性都是有规律可循的,其实并不难理解和记忆。修改activity_main.xml中的代码,如下所示:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:text="按钮1" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:text="按钮2" /> <Button android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="按钮3" /> <Button android:id="@+id/button4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:text="按钮4" /> <Button android:id="@+id/button5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" android:text="按钮5" /> </RelativeLayout>
以上代码很好理解,我们让按钮1和父布局的左上角对齐,按钮2和父布局的右上角对齐,按钮3居中显示,按钮4和父布局的左下角对齐,按钮5和父布局的右下角对齐。
属性值 |
属性 |
说明 |
取值:true / false |
android:layout_alignParentLeft |
表示让一个控件和父布局左对齐 |
android:layout_alignParentRight |
表示让一个控件和父布局右对齐 |
|
android:layout_alignParentTop |
表示让一个控件和父布局顶端对齐 |
|
android:layout_alignParentBottom |
表示让一个控件和父布局底端对齐 |
|
android:layout_centerInParent |
表示让一个控件位于父布局的中心 |
|
android:layout_centerHorizontal |
表示控件在父控件中水平居中 |
|
android:layout_centerVertical |
表示控件在父控件中垂直居中 |
运行程序,效果如图所示。
如果觉得按钮太靠近屏幕边缘,我们还可以通过给相对布局指定内边距属性。
属性值 |
属性 |
说明 |
取值:具体的像素值 如 20dip,40px |
android:paddingTop |
设置布局顶部内边距的距离 |
android:paddingBottom |
设置布局底部内边距的距离 |
|
android:paddingLeft |
设置布局底部内边距的距离 |
|
android:paddingRight |
设置布局右边内边距的距离 |
|
android:padding |
设置布局四周内边距的距离 |
(2)控件相对于控件进行定位
我们先留下“按钮3”,修改activity_main.xml中的代码,如下所示:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="按钮3" /> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/button3" android:layout_toLeftOf="@id/button3" android:text="按钮1" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@id/button3" android:layout_toRightOf="@id/button3" android:text="按钮2" /> <Button android:id="@+id/button4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/button3" android:layout_toLeftOf="@id/button3" android:text="按钮4" /> <Button android:id="@+id/button5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/button3" android:layout_toRightOf="@id/button3" android:text="按钮5" /> </RelativeLayout>
将按钮1、按钮2、按钮4、按钮5定义在按钮3之后,然后分别指定他们的一些属性。
属性值 |
属性 |
说明 |
取值:必须为id的引用 如"@id/button1" |
android:layout_above |
表示让一个控件位于另一个控件的上方 |
android:layout_below |
表示让一个控件位于另一个控件的下方 |
|
android:layout_toLeftOf |
表示让一个控件位于另一个控件的左侧 |
|
android:layout_toRightOf |
表示让一个控件位于另一个控件的右侧 |
注意,当一个控件去引用另一个控件的id时,该控件一定要定义在引用控件的后面,不然会出现找不到id的情况,另外,要使用@id/button3表示被引用的那个控件id。重新运行程序,效果如图。
RelativeLayout中还有另外一组相对于控件进行定位的属性。
属性值 |
属性 |
说明 |
取值:必须为id的引用 如"@id/button1" |
android:layout_alignLeft |
表示让一个控件的左边缘和另一个控件的左边缘对齐 |
android:layout_alignRight |
表示让一个控件的右边缘和另一个控件的右边缘对齐 |
|
android:layout_alignTop |
表示让一个控件的顶端和另一个控件的顶端对齐 |
|
android:layout_alignBottom |
表示让一个控件的底端和另一个控件的底端对齐 |
如果你觉得离基准控件太近了,我们可以通过设置控件外边距属性来调整两个控件之间的距离。这些属性值是具体的边距数值,例如,20dp。
属性值 |
属性 |
说明 |
取值:具体的像素值 如 20dip,40px |
android:layout_marginTop |
设置当前控件上边界与某控件的距离 |
android:layout_marginBottom |
设置当前控件底边界与某控件的距离 |
|
android:layout_marginLeft |
设置当前控件左边界与某控件的距离 |
|
android:layout_marginRight |
设置当前控件右边界与某控件的距离 |
|
android:layout_margin |
设置当前控件四周外边距 |
2、LinearLayout(线性布局)
LinearLayout,是一种非常常用的布局。正如它名字所描述的一样,这个布局会将它所包含的控件在线性方向上依次排列。
我们之前的学习,都是使用的LinearLayout布局方式来排列控件的。
既然是线性排列,肯定就有排列的方向,我们通过android:orientation属性指定了排列方向,vertical(V)是垂直方向上排列,horizontal(H)是水平方向上排列。修改activity_main.xml中的代码,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="按钮1" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="按钮2" /> <Button android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="按钮3" /> </LinearLayout>
我们在LinearLayout中添加了三个Button,每个Button的长和宽都是wrap_content,并指定了排列方向是vertical。现在运行一下程序,效果如图所示。
然后我们修改一下LinearLayout的排列方向,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > …… </LinearLayout>
将android:orientation属性的值改成了horizontal,这就意味着要让LinearLayout中的控件在水平方向上依次排列,当然如果不指定android:orientation属性的值,默认的排列方向就是horizontal。重新运行一下程序,效果如图所示。
这里需要注意,如果LinearLayout的排列方向是horizontal(H),内部的控件就绝对不能将宽度指定为match_parent,因为这样的话单独一个控件就会将整个水平方向占满,其他的控件就没有可放置的位置了。
同样的道理,如果LinearLayout的排列方向是vertical(V),内部的控件就不能将高度指定为match_parent。
了解了LinearLayout的排列规律,我们再来学习一下它的几个关键属性的用法吧。
首先来看android:layout_gravity属性,它和我们上一节中学到的android:gravity属性看起来有些相似,这两个属性有什么区别呢?
其实从名字上就可以看出,android:gravity是用于指定文字在控件中的对齐方式,而android:layout_gravity是用于指定控件在布局中的对齐方式。
android:layout_gravity的可选值和android:gravity差不多,但是需要注意,当LinearLayout的排列方向是horizontal时,只有垂直方向上的对齐方式才会生效,因为此时水平方向上的长度是不固定的,每添加一个控件,水平方向上的长度都会改变,因而无法指定该方向上的对齐方式。
同样的道理,当LinearLayout的排列方向是vertical时,只有水平方向上的对齐方式才会生效。修改activity_main.xml中的代码,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="top" android:text="按钮1" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:text="按钮2" /> <Button android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="bottom" android:text="按钮3" /> </LinearLayout>
由于目前LinearLayout的排列方向是horizontal,因此我们只能指定垂直方向上的排列方向,将第一个Button的对齐方式指定为top,第二个Button的对齐方式指定为center_vertical,第三个Button的对齐方式指定为bottom。重新运行程序,效果如图。
接下来我们学习下LinearLayout中的另一个重要属性,android:layout_weight。
这个属性允许我们使用比例的方式来指定控件的大小,它在手机屏幕的适配性方面可以起到非常重要的作用。
比如我们正在编写一个消息发送界面,需要一个文本编辑框和一个发送按钮,修改activity_main.xml中的代码,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <EditText android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:hint="输入消息内容" /> <Button android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:text="发送" /> </LinearLayout>
你会发现,这里竟然将EditText和Button的宽度都指定成了0,这样文本编辑框和按钮还能显示出来吗?
不用担心,由于我们使用了android:layout_weight属性,此时控件的宽度就不应该再由android:layout_width来决定,这里指定成0是一种比较规范的写法。
然后我们在EditText和Button里都将android:layout_weight属性的值指定为1,这表示EditText和Button将在水平方向平分宽度,重新运行下程序,你会看到如图所示的效果。
我们还可以通过指定部分控件的layout_weight值,来实现更好的效果。修改activity_main.xml中的代码,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <EditText android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:hint="输入消息内容" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="发送" /> </LinearLayout>
这里我们仅指定了EditText的android:layout_weight属性,并将Button的宽度改回wrap_content。
这表示Button的宽度仍然按照wrap_content来计算,而EditText则会占满屏幕所有的剩余空间。
使用这种方式编写的界面,不仅在各种屏幕的适配方面会非常好,而且看起来也更加舒服,重新运行程序,效果如图所示。
但是,android:layout_weight会引起争议,是因为在设置该属性的同时,设置android:layout_width为wrap_content和match_parent会造成两种截然相反的效果。我们以几个TextView为例。
【实例1】宽度为match_parent
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:background="#aa0000" android:text="1" android:textSize="20sp" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="2" android:background="#00aa00" android:text="2" android:textSize="20sp" /> </LinearLayout>
可以看到权重为1的占了三分之二。
【实例2】 宽度为wrap_content
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:background="#aa0000" android:text="1" android:textSize="20sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="2" android:background="#00aa00" android:text="2" android:textSize="20sp" /> </LinearLayout>
左边 TextView占比三分之一了。
为什么呢?我们来了解一下android:layout_weight的真实含义是:一旦控件设置了该属性,那么该控件的宽度 等于 原有宽度(android:layout_width) 加上 剩余空间的占比!
假设屏幕宽度为L,在两个view的宽度都为match_parent的情况下,那么这两控件的宽度都为L,那么剩余宽度为L - (L+L) = -L(负数,没关系,先计算),左边的控件占比三分之一(1/(1+2)),所以左边控件实际宽度是L + (-L)*1/3 = (2/3)L,右边的控件占比三分之二(2/(1+2)),所以有边控件实际宽度是L + (-L)*2/3 = (1/3)L,所以,就是2:1的比列显示了。
但是如果是wrap_content,每个控件的宽度就不是L(整个屏宽)了,就是能够显示开文本内容的宽度了,然后会把剩下来的屏幕空间按照1:2的比列再分配给这两个控件。两个控件分别显示1个字符(“1”、“2”),整个屏幕剩余空间L-2,第一个控件的宽度1 + 1/3(L-2) =1 + 1/3L - 2/3 ≈ 1/3L,第二个控件宽度1 + 2/3(L-2) = 1 + 2/3L - 4/3 ≈ 2/3L,就是1:2的比列显示了。
Google官方推荐,当使用android:layout_weight属性时,应将android:layout_width设为0dp即可。这样weight就可以理解为占比了。
【扩展思考1】如果有三个TextView,android:layout_width="wrap_content",这时分别设置三个TextView的android:layout_weight为1,2,3呢?
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" android:background="#aa0000" android:text="1" android:textSize="20sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="2" android:background="#00aa00" android:text="2" android:textSize="20sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="3" android:background="#0000aa" android:text="3" android:textSize="20sp" /> </LinearLayout>
按照上面的理解,系统先给3个TextView分配他们的宽度值 wrap_content(宽度足以包含他们的内容1,2,3即可),然后会把剩下来的屏幕空间按照1:2:3的比列分配给3个TextView,所以就出现了上面的图像。
【扩展思考2】如果这三个TextView,还是android:layout_width="match_parent",但是这时分别设置三个TextView的android:layout_weight为1,2,3,就会显示如下:
第三个直接不显示了,为什么呢?一起来按上面方法算一下吧:
系统先给3个TextView分配他们所要的宽度match_parent,也就是说每一都是填满他的父控件,这里就是屏幕的宽度,那么这时候的剩余空间 = 1个L - 3个L = -2个L(L指的是屏幕宽度),那么第一个TextView的实际所占宽度应该 = L + 他所占剩余空间的权重比列1/6 * 剩余空间大小(-2 L)= 2/3L,同理,第二个TextView的实际所占宽度 = L + 2/6*(-2L) = 1/3L,第三个TextView的实际所占宽度 = L + 3/6*(-2L) = 0L,所以就是2:1:0的比列显示了,第三个就直接没有空间了。
【扩展思考3】如果这三个TextView,android:layout_width="match_parent",这时分别设置三个TextView的android:layout_weight为1,2,2,显示如下:
三个TextView都显示,比例为3:1:1,你会发现 1的权重小,反而分的多了,这是为什么呢???
真正的原因是layout_width="match_parent"的原因造成的。依照上面理解我们来分析:
系统先给3个TextView分配他们所要的宽度match_parent,也就是说每一都是填满他的父控件,这里就是整个屏幕的宽度,那么这时候的剩余空间 = 1个L - 3个L = -2个L(L指的是屏幕宽度)。那么,第一个TextView的实际所占宽度应该 = L + 他所占剩余空间的权重比列1/5 * 剩余空间大小(-2 L) = 3/5L,同理,第二个TextView的实际所占宽度 = L + 2/5*(-2L) = 1/5L,第三个TextView的实际所占宽度 = L + 2/5*(-2L) = 1/5L,所以就是3:1:1的比列显示了。
3、TableLayout(表格布局)
TableLayout允许我们使用表格的方式来排列控件,这种布局不是很常用,你只需要了解一下它的基本用法就可以了。
既然是表格,那就一定会有行和列,在设计表格时我们尽量应该让每一行都拥有相同的列数,这样的表格也是最简单的。
不过有时候事情并非总会顺从我们的心意,当表格的某行一定要有不相等的列数时,就需要通过合并单元格的方式来应对。
比如我们正在设计一个登录界面,允许用户输入账号密码后登录,就可以将activity_main.xml中的代码改成如下所示:
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <TableRow> <TextView android:layout_height="wrap_content" android:text="账号:" /> <EditText android:layout_height="wrap_content" android:hint="请输入账号" /> </TableRow> <TableRow> <TextView android:layout_height="wrap_content" android:text="密码:" /> <EditText android:layout_height="wrap_content" android:hint="请输入密码" android:inputType="textPassword" /> </TableRow> <TableRow> <Button android:layout_height="wrap_content" android:layout_span="2" android:text="登录" /> </TableRow> </TableLayout>
在TableLayout中每加入一个TableRow就表示在表格中添加了一行,然后在TableRow中每加入一个控件,就表示在该行中加入了一列,TableRow中的控件是不能指定宽度的。这里我们将表格设计成了三行两列的格式,第一行有一个TextView和一个用于输入账号的EditText,第二行也有一个TextView和一个用于输入密码的EditText,我们通过将android:inputType属性的值指定为textPassword,把EditText变为密码输入框。可是第三行只有一个用于登录的按钮,前两行都有两列,第三行只有一列,这样的表格就会很难看,而且结构也非常不合理。这时就需要通过对单元格进行合并来解决这个问题,使用android:layout_span="2"让登录按钮占据两列的空间,就可以保证表格结构的合理性了。重新运行程序,效果如图所示。
不过从图中可以看出,当前的登录界面并没有充分利用屏幕的宽度,右侧还空出了一块区域,这也难怪,因为在TableRow中我们无法指定控件的宽度。这时使用android:stretchColumns属性就可以很好地解决这个问题,它允许将TableLayout中的某一列进行拉伸,以达到自动适应屏幕宽度的作用。修改activity_main.xml中的代码,如下所示:
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:stretchColumns="1"> …… </TableLayout>
这里将android:stretchColumns的值指定为1,表示如果表格不能完全占满屏幕宽度,就将第二列进行拉伸。
没错!指定成1就是拉伸第二列,指定成0就是拉伸第一列,不要以为这里我写错了哦。重新运行程序,效果如图所示。
除此之外,还有两个属性:
- android:collapseColumns="":隐藏
- android:shrinkColumns="":收缩
属性值如果是"*",表示可以应用于所有列。
4、GridLayout(网格布局)
网格布局是Android 4.0(API 14)之后新增的布局方式,它使用虚细线将布局划分为行、列和单元格,也支持一个控件在行、列上都有交错排列。
GridLayout使用的其实是跟LinearLayout类似,只不过是修改了一下相关的标签而已,所以对于我们来说,掌握GridLayout还是很容易的事情。
GridLayout的布局策略简单分为以下三个部分:
(1)它与LinearLayout布局一样,也分为水平和垂直两种方式,默认是水平布局,一个控件挨着一个控件从左到右依次排列,但是通过指定android:columnCount设置列数的属性后,控件会自动换行进行排列。另一方面,对于GridLayout布局中的子控件,默认按照wrap_content的方式设置其宽高显示,这只需要在GridLayout布局中显式声明即可。
(2)若要指定某控件显示在固定的行或列,只需设置该子控件的android:layout_row和android:layout_column属性即可,但是需要注意:android:layout_row="0"表示从第一行开始,android:layout_column="0"表示从第一列开始,计数都是从0开始。
(3)如果需要设置某控件跨越多行或多列,只需将该子控件的android:layout_rowSpan或者android:layout_columnSpan属性设置为数值(表明该控件跨越的行数或列数),再设置其layout_gravity属性为fill即可(表明该控件填满所跨越的整行和整列,还有另外两个属性值:fill_horizontal表明水平方向填满;fill_vertical表明垂直方向填满)。修改activity_main.xml中的代码,我们来做一个计算器界面。
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/gridLayout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:columnCount="4" android:orientation="horizontal" android:rowCount="5" > <Button android:text="1" /> <Button android:text="2" /> <Button android:text="3" /> <Button android:text="/" /> <Button android:text="4" /> <Button android:text="5" /> <Button android:text="6" /> <Button android:text="×" /> <Button android:text="7" /> <Button android:text="8" /> <Button android:text="9" /> <Button android:text="-" /> <Button android:layout_columnSpan="2" android:layout_gravity="fill" android:text="0" /> <Button android:text="." /> <Button android:layout_gravity="fill" android:layout_rowSpan="2" android:text="+" /> <Button android:layout_columnSpan="3" android:layout_gravity="fill" android:text="=" /> </GridLayout>
如果要让每个控件平分且行充满屏幕,我们需要在逻辑代码中通过计算给控件来设置宽度。代码如下:
public class MainActivity extends AppCompatActivity { private GridLayout gridLayout; private int columnCount; //列数 private int screenWidth; //屏幕宽度 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); DisplayMetrics dm = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(dm); screenWidth = dm.widthPixels; gridLayout = (GridLayout) findViewById(R.id.gridLayout); columnCount = gridLayout.getColumnCount(); for (int i = 0; i < gridLayout.getChildCount(); i++) { Button button = (Button) gridLayout.getChildAt(i); button.setWidth(screenWidth / columnCount); } } }
5、FrameLayout(帧布局)
FrameLayout相比于前面的布局就简单太多了,因此它的应用场景也很少,我们会在后面学习碎片时用到它。这种布局像层叠的方式一样,所有的控件都会摆放在布局的左上角。让我们通过例子来看一看吧,修改activity_main.xml中的代码,如下所示:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="第一层" android:background="#ffff00" android:textColor="#000000" android:textSize="50sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="第二层" android:background="#ff0000" android:textColor="#ffff00" android:textSize="40sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="第三层" android:background="#0000ff" android:textColor="#ff00ff" android:textSize="30sp" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="第四层" android:background="#000000" android:textColor="#00ffff" android:textSize="20sp" /> </FrameLayout>
可以看出FrameLayout中4个TextView控件,这4个控件分别指定了背景色、字体颜色、字体大小。运行后可以看到,4个TextView控件都是位于布局的左上角。控件显示的顺序是按照添加的顺序显示的,最后添加的在最上面。
我们还可以通过给子控件设置android:layout_gravity属性来调整控件的位置。
例如,android:layout_gravity="right"
6、AbsoluteLayout(绝对布局)
通过绝对布局,可以任意设置视图的位置。绝对布局使用<AbsoluteLayout>标签定义,我们可以使用控件的android:layout_x和android:layout_y属性设置横坐标和纵坐标,用于控件的定位。
<AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_x="30dp" android:layout_y="30dp" android:text="按钮1" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_x="60dp" android:layout_y="90dp" android:text="按钮2" /> </AbsoluteLayout>
绝对布局已经不建议使用了,因为Android设备的屏幕大小和长宽比例有很大差异,使用绝对布局很难适应各种屏幕尺寸。
7、ConstraintLayout约束布局
ConstraintLayout是Android Studio 2.3以上默认的布局模板。
在传统的Android开发当中,界面基本都是靠编写XML代码完成的,虽然Android Studio也支持可视化的方式来编写界面,但是操作起来并不方便,我们也一直都不推荐使用可视化的方式来编写Android应用程序的界面。
而ConstraintLayout就是为了解决这一现状而出现的。它和传统编写界面的方式恰恰相反,ConstraintLayout非常适合使用可视化的方式来编写界面,但并不太适合使用XML的方式来进行编写。当然,可视化操作的背后仍然还是使用的XML代码来实现的,只不过这些代码是由Android Studio根据我们的操作自动生成的。
另外,ConstraintLayout还有一个优点,它可以有效地解决布局嵌套过多的问题。
我们平时编写界面,复杂的布局总会伴随着多层的嵌套,而嵌套越多,程序的性能也就越差。
ConstraintLayout则是使用约束的方式来指定各个控件的位置和关系的,它有点类似于RelativeLayout(相对布局),但远比RelativeLayout要更强大。
(1)约束布局的基本使用
新建一个ConstraintLayoutTest项目,确保Android Studio是2.3或以上版本。
为了要使用ConstraintLayout,我们需要在app/build.gradle文件中添加ConstraintLayout的依赖,如果使用Android Studio默认创建布局,应该是已经添加好,我们检查一下即可,如下所示。
dependencies {
…
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
}
目前ConstraintLayout库最新的版本是2.0.4。
现在打开res/layout/activity_main.xml文件,由于这是一个新建的空项目,Android Studio会自动帮我们创建好一个布局,如下图所示。
我们发现默认创建的布局样式就是ConstraintLayout,我们可以通过布局文件右上角的切换按钮,切换布局显示视图为【Design】。
这时,我们想要向ConstraintLayout布局中添加一个按钮,那么只需要从左侧的Palette区域拖一个Button进去就可以了。
但是由于我们还没有给Button添加任何的约束,因此Button并不知道自己应该出现在什么位置。
现在我们在预览界面上看到的Button位置并不是它最终运行后的实际位置,如果一个控件没有添加任何约束的话,它在运行之后会自动位于界面的左上角,如图所示。
那么下面我们就来给Button添加约束,每个控件的约束都分为垂直和水平两类,一共可以在四个方向上给控件添加约束,如下图所示。
上图中Button的上下左右各有一个圆圈,这圆圈称为锚点,使用锚点来确定各控件之间的对齐规则,要创建一个约束,需要在指定锚点上点击并按住鼠标, 然后拖到另一个控件的约束锚点上就可以松开鼠标完成约束创建。比如说,想让Button位于布局的右下角,我们可以选中右边的圆圈拖动指向右侧,选中下面的圆圈拖动指向底部。如下图所示。
这样我们就给Button的右边和下边添加了约束,因此Button就会将自己定位到布局的右下角了。
类似地,如果我们想要让Button居中显示,那么就需要给它的上下左右都添加约束。这就是添加约束最基本的用法了。
如何删除Button的约束呢?删除约束的方式一共有三种。
第一种用于删除一个单独的约束,用鼠标单击某个约束的圆圈,使其变为选中状态,这个时候按下Delete键就能删除了,或者从右侧的属性对话框中选择某条约束,删除也可,如下图所示。
第二种用于删除某一个控件的所有约束,选中一个控件,然后右键选择“Clear Constraints of Selection”命令,就能删除当前控件的所有约束了,如下所示。
第三种用于删除当前界面中的所有约束,点击工具栏中的删除约束图标即可,如图所示。
除此之外,我们还可以使用约束让一个控件相对于另一个控件进行定位。比如说,我们希望让这个Button位于原来的TextView的正下方,并且间距64dp。我们可以这样做,让Button的右约束指向TextView的右圆圈,Button的左约束指向TextView的左圆圈,Button的上约束指向TextView的下圆圈,拖动Button往下,调整间距64dp,左右位置可以通过属性对话框的水平滑块微调。
这样,Button始终位于TextView下64dp的位置,不管TextView如何移动,Button始终跟随它。
我查看Code视图,会发现有这样一系列属性,用来表示位置的属性。
- layout_constraintTop_toTopOf : 该控件的顶部与另一个控件的顶部对齐
- layout_constraintTop_toBottompOf : 该控件的顶部与另一个控件的底部对齐
- layout_constraintBottom_toTopOf : 该控件的底部与另一个控件的顶部对齐
- layout_constraintBottom_toBottomOf : 该控件的底部与另一个控件的底部对齐
- layout_constraintLeft_toLeftOf : 该控件的左侧与另一个控件的左侧对齐
- layout_constraintLeft_toRightOf : 该控件的左侧与另一个控件的右侧对齐
- layout_constraintRight_toLeftOf : 该控件的右侧与另一个控件的左侧对齐
- layout_constraintRight_toRightOf : 该控件的右侧与另一个控件的右侧对齐
- layout_constraintStart_toStartOf:该控件的左侧与另一个控件的左侧对齐
- layout_constraintStart_toEndOf:该控件的左侧与另一个控件的右侧对齐
- layout_constraintEnd_toEndOf:该控件的右侧与另一个控件的右侧对齐
- layout_constraintEnd_toStartOf:该控件的右侧与另一个控件的左侧对齐
我们可以定义控件和父容器(整个约束布局)的约束关系,如下:
- layout_constraintStart_toStartOf="parent":该控件与父容器左对齐
- layout_constraintEnd_toEndOf="parent":该控件与父容器右对齐
- layout_constraintTop_toTopOf="parent" :该控件与父容器顶部对齐
- layout_constraintBottom_toBottomOf="parent":该控件与父容器底部对齐
还有一种基线约束,其意为控件之间的文本基线约束,换句话说就是文本对齐,当然这种这种约束是有文本控件才有的约束,Layout是没有基线约束的。
创建基线约束只需要右击控件,然后选择“Show Baseline”就会出现控件的基线,然后链接所需要对齐的其他控件就可以了。
- layout_constraintBaseline_toBaselineOf:该控件的内部文字与另一个控件的内部文字对齐
我们还可以使用Autoconnect自动连接创建约束,在工具栏有一个磁铁都一样图标。
这个就是开启或关闭自动约束的开关,也叫Autoconnect。Autoconnect会判断我们的意图,并自动给控件添加约束。
不过Autoconnect是无法保证百分百准确判断出我们的意图的,如果自动添加的约束并不是你想要的话,还可以在任何时候进行手动修改。
可以把它当成一个辅助工具,但不能完全靠它去添加控件的约束,使用起来也很有局限性。
还使用Inference推理操作创建约束,Inference也是用于自动添加约束的,但它比Autoconnect的功能要更为强大,使用起来也很方便。
因为AutoConnect只能给当前操作的控件自动添加约束,而Inference会给当前界面中的所有元素自动添加约束,可以一键自动生成所有控件的约束,功能按钮如图所示。
接下来我们先将各个控件按照界面设计的位置进行摆放,摆放完成之后点击一下工具栏上的Infer Constraints按钮,就能为所有控件自动添加约束了。
(2)Inspector检查器
右侧Attributes区域的上半部分,这部分也被称为Inspector(检查器)。
首先可以看到,在Inspector中有一个纵向的轴和一个横向的轴,这两个轴也是用于确定控件的位置的。
原有的TextView的上下左右各添加了一个约束,然后TextView就能居中显示了,其实就是因为这里纵横轴的值都是50。
如果调整了纵横轴的比例,那么TextView的位置也会随之改变。
其次,位于Inspector最中间的那个正方形区域,它是用来控制控件大小的。一共有三种模式可选,每种模式都使用了一种不同的符号表示,点击符号即可进行切换。
表示wrap_content,这个我们很熟悉了,不需要进行什么解释。
表示Fixed固定值,也就是给控件指定了一个固定的长度或者宽度值。
表示AnySize(match constraits),它有点类似于match_parent,但和match_parent并不一样,是属于ConstraintLayout中特有的一种大小控制方式。
下面我们来重点讲解一下。
首先需要说明,在ConstraintLayout中是有match_parent的,只不过用的比较少,因为ConstraintLayout的一大特点就是为了解决布局嵌套,既然没有了布局嵌套,那么match_parent也就没有多大意义了。
而AnySize(match constraits)就是用于在ConstraintLayout中顶替match_parent的,先看一下我们怎样使用AnySize实现和match_parent同样的效果吧。
比如说我想让TextView的宽度充满整个布局,如下所示。
那可能有人会问,AnySize和match_parent有什么区别呢?
其实最大的区别在于,match_parent是用于填充满当前控件的父布局,占用父布局所有可用空间;而AnySize(match constraits)是用于填充满当前控件的约束规则,可以理解为填满剩下的空间。
举个例子更好理解,我们在约束布局中左右摆放两个Button,左边Button1的右约束是添加到右边Button2上的,当我们给Button1的左右约束设置为AnySize时,右边Button2的位置会影响到Button1的宽度,如下图所示。
(3)Guidelines参考线
Guidelines参考线可以帮助我们在添加约束布局的时候更方便的确定控件的位置,可以创建垂直或者水平方向的Guidelines,创建后可以点击选择Guidelines位置的方式,默认是距离左边多少dp,点击后可以切位为距离右边的距离和屏幕的百分比距离。
如果我们要两个按钮水平居中就需要用到Guidelines,添加一个垂直参考线。给左边的Button1添加了底部约束,距离底部24dp,右边约束于Guidelines;右边的Button2则左边约束于Guidelines,而底部约束于左边Button底部,这样两个按钮就水平居中对齐了。
左边Button1的约束设置 右边Button2的约束设置
(4)Chains链条
Chains为同一个方向(水平或者垂直)上的多个子View提供一个类似群组的概念。
其他的方向则可以单独控制。
Chain的属性由该群组的第一个View上的属性所控制(第一个View被称之为Chain head链头)。
我们在ConstraintLayout中摆放三个按钮,首先我们选中这三个按钮,右键创建一个水平方向的Chain,或者通过工具栏也可以快速的创建一个水平Chain。
代码如下:
<androidx.constraintlayout.widget.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=".MainActivity"> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button1" app:layout_constraintEnd_toStartOf="@+id/button2" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintHorizontal_chainStyle="spread" app:layout_constraintStart_toStartOf="parent" tools:layout_editor_absoluteY="77dp" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button2" app:layout_constraintEnd_toStartOf="@+id/button3" app:layout_constraintStart_toEndOf="@+id/button" tools:layout_editor_absoluteY="77dp" /> <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_constraintStart_toEndOf="@+id/button2" tools:layout_editor_absoluteY="77dp" /> </androidx.constraintlayout.widget.ConstraintLayout>
我们刚才设置的三个Button,两两添加了依赖,类似下图:
横向的相当于组成了一个链(Chains)。在这个链的最左侧的元素成为Chain head链头,我们可以在其身上设置一些属性,来决定这个链的展示效果,Chain head有很多属性如下:
- layout_constraintHorizontal_chainStyle
- layout_constraintHorizontal_weight
- layout_constraintVertical_chainStyle
- layout_constraintVertical_weight
其中,layout_constraintVertical_chainStyle是设置到Chain head(链头)上的,指定不同的style会改变里面所有View的布局方式,有如下3种Style:
- spread:“展开”,是默认的Style,里面的所有View会分散开布局;
- spread_inside:和spread类似,只不过两端的两个View和父容器直接不占用多余空间,多余空间在子View之间分散;
- packed:“聚集”,所有的子View都居中聚集在一起;
在packed模式下,我们可以通过设置bias属性(layout_constraintHorizontal_bias和layout_constraintVertical_bias)来控制聚集的位置。
另外,在spread模式下,如果我们将View的宽度设置为0dp,我们可以使用layout_constraintHorizontal_weight属性设置View宽度占所有剩余空间的占比,和LinearLayout的weight类似。
来一张官网可以实现的效果,大家可以自己再深入的研究一下。