前言
一个界面中常常会包含多个控件,如果没有布局,任由控件随意摆放,界面是不会美观的。
而布局是一种可以放置很多控件的容器,它可以按照一定的规则来摆放其内部控件的位置。当前布局中也可以放置布局,经过多次嵌套,我们就能完成较为复杂的界面。不过,要注意布局层级深度,每一次嵌套,测量和布局的耗时都会增加,导致渲染性能差。
比如下面这张图,就能够很好地表示它们之间的关系。
我们来学习一下三种最基本的布局。先创建一个名为 UILayoutTest
的 Empty Views Activity 项目,其他都选用默认值。
LinearLayout
LinearLayout 又被称为线性布局,它会将它内部的控件在线性的方向上进行排列,排列方式有两种:垂直方式上排列、水平方向上排列。
我们是通过 android:orientation
属性来指定了排列方向的,vertical
代表垂直方向,horizontal
是水平方向。
通过代码演示一下垂直排列:
xml
<?xml version="1.0" encoding="utf-8"?>
<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="Button 1" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 2" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 3" />
</LinearLayout>
我们往界面中添加了三个按钮,并且指定了 LinearLayout 布局的排列方向是 vertical
。
运行效果:
我们修改排列方向为 horizontal
:
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
style="?android:attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
...
</LinearLayout>
我们将 android:orientation
属性值改为了 horizontal
,表示 LinearLayout 布局内部的控件会在水平方向上依次排列。
不指定
android:orientation
属性值的话,默认的排列方向就是水平排列(horizontal
),但建议始终显式指定它的值。
运行效果如下所示:
需要注意,如果 LinearLayout 的排列方向是水平时,那么其内部的控件(子控件 )的宽度就不能设置为 match_parent
,否则,仅需一个控件,就会将整个水平方向上的可用空间占满,如果存在多个,只有第一个会被显示。同样地,如果 LinearLayout 的排列方向是垂直,那么内部的控件的高度就不能设置为 match_parent
。
我们再来看看 android:layout_gravity
属性。你可能知道 android:gravity
属性用于指定控件内容在控件中的对齐方式,而 android:layout_gravity
用于指定控件本身在布局中的对齐方式,它的可选值和 android:gravity
差不多。
但是需要注意,当 LinearLayout 的排列方向是 horizontal
时,其子控件只有垂直方向上的 layout_gravity
值(如 top
、center_vertical
、bottom
)才会生效,水平方向的 layout_gravity
值(如 start
、center_horizontal
、end
)会被忽略。这是因为在水平排列的 LinearLayout 中,每个子控件在水平方向上所拥有的布局空间是不确定的,LinearLayout 每增加一个子控件,每个子控件的可用的水平空间都会变化,所以即使你指定了水平方向上的对齐方式,也是无效的。同样地,当 LinearLayout 的排列方向是 vertical
时,只有水平方向上的 layout_gravity
值才会生效。
修改按钮在水平 LinearLayout 布局中的对齐方式,activity_main.xml
中的代码如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<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="Button 1" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="Button 2" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="Button 3" />
</LinearLayout>
我们将第一个按钮的对齐方式设为了 top
,第二个按钮的对齐方式设为了 center_vertical
,第三个按钮的对齐方式设为了 bottom
。
运行效果:
我们再来看看 android:layout_weight
属性,它允许我们指定子控件在其父布局中的权重,简单来说,就是如何分配父布局的剩余可用空间。
比如我们要编写一个消息发送的界面,其中有一个文本输入框和一个"发送"按钮,我们希望它们平分屏幕宽度,代码如下所示:
xml
<?xml version="1.0" encoding="utf-8"?>
<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:id="@+id/input_message"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Type something" />
<Button
android:id="@+id/send"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Send" />
</LinearLayout>
你会发现我们将 EditText 和 Button 控件的宽度设为了 0dp
,这是为何?
因为我们想让两个控件的尺寸完全由其权重值 来决定。当宽度设为 0dp
时,父布局在测量该控件的尺寸时会把初始宽度视为 0,然后父布局会为宽度设置了固定值或是 wrap_content
,并且没有设置权重值的子控件预留空间,最后将剩余可用的空间 按照各子控件的权重值进行分配。所以,将 layout_width
设为 0dp
并且设置权重值,是一种规范写法,可以让控件的尺寸严格按照权重的比例进行分配。
然后 EditText 和 Button 的权重都为 1,总权重值为 2,所以每个控件所占宽度都是父布局可用宽度的 1/2,所以会平分屏幕。
运行效果:
我们还可以通过只指定部分控件的权重来实现更灵活的显示效果,比如让 EditText 占据除 Button 外的所有剩余空间,代码如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<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:id="@+id/input_message"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:hint="Type something" />
<Button
android:id="@+id/send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send" />
</LinearLayout>
我们设置了 Button 的宽度为 wrap_content
,指定了 EditText 的 android:layout_weight
属性。这样 Button 会先占据其内容所需的宽度,然后,父布局会将剩余的水平空间根据控件的权重比例来分配。而只有一个 EditText 控件设置了权重值,所以 EditText 会获取剩余的水平空间。
运行效果:
RelativeLayout
RelativeLayout 又叫做相对布局。顾名思义,它是通过相对定位的方式来摆放控件的,通过子控件之间的相对位置关系和子控件与父布局的相对位置关系来定位。正因为如此,用来描述方位的属性非常多,但几乎都可以见名知意。
我们通过代码感受一下相对于父布局定位:
xml
<?xml version="1.0" encoding="utf-8"?>
<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_alignParentStart="true"
android:layout_alignParentTop="true"
android:text="Button 1" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentTop="true"
android:text="Button 2" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Button 3" />
<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentStart="true"
android:text="Button 4" />
<Button
android:id="@+id/button5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:text="Button 5" />
</RelativeLayout>
上述布局中有五个按钮,而每个按钮是如何进行布局都很好理解。以 Button 1 和 Button 3 为例,Button 1 通过android:layout_alignParentStart="true"
和 android:layout_alignParentTop="true"
属性表示它在父布局中的位置是左上角,而 Button 3 通过 android:layout_centerInParent="true"
属性表示它位于父布局的中心位置。
运行效果:
并且控件除了可以相对于父布局进行定位,还可以根据其他控件进行定位。代码如下所示:
xml
<?xml version="1.0" encoding="utf-8"?>
<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="Button 3" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/button3"
android:layout_toStartOf="@id/button3"
android:text="Button 1" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@id/button3"
android:layout_toEndOf="@id/button3"
android:text="Button 2" />
<Button
android:id="@+id/button4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/button3"
android:layout_toStartOf="@id/button3"
android:text="Button 4" />
<Button
android:id="@+id/button5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/button3"
android:layout_toEndOf="@id/button3"
android:text="Button 5" />
</RelativeLayout>
在上述代码中,我们首先确定了 Button 3 控件的位置,它位于父布局的中心。后续四个控件都是通过 Button 3 来进行定位的。
android:layout_above
属性可以让一个控件位于另一个控件的上方,使用它时需要填写"另一个控件"id的引用。其他属性也是类似,android:layout_toStartOf
表示让一个控件位于另一个控件的起始侧,android:layout_toEndOf
表示让一个控件位于另一个控件的结束位置。
注意:当你去引用另一个控件的 id 时,当前控件的声明不一定要在被引用控件之后。
运行效果:
此外,你还可以相对于控件的边缘进行对齐。有 android:layout_alignStart
、android:layout_alignEnd
、android:layout_alignTop
和 android:layout_alignBottom
。以 android:layout_alignStart
为例,它表示让一个控件的起始边缘和另一个控件的起始边缘对齐。
RelativeLayout 中的属性虽多,但都很容易理解和使用。
FrameLayout
FrameLayout 又称为帧布局,它的布局规则特别简单:其内部的控件默认会被叠放至布局的左上角,并且后添加的控件会覆盖先添加的控件。
比如我们往该布局中添加了TextView和Button控件,代码如下:
xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is TextView" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button" />
</FrameLayout>
运行效果是这样的:
两个控件都位于左上角,并且重叠在一起了,Button 盖住了 TextView。
当然这种只是默认效果,我们可以在子控件中通过 layout_gravity
属性来指定控件在布局中的对齐方式,比如:
xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:text="This is TextView" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:text="Button" />
</FrameLayout>
运行效果如图所示:
然后就这些了,FrameLayout 相对来说布局规则简单。
但有些场景下,非常有用。比如它可以用来存放 Fragment,用来直接在图片上叠加一个播放按钮。