详解三种常见布局:LinearLayout、RelativeLayout及FrameLayout

前言

一个界面中常常会包含多个控件,如果没有布局,任由控件随意摆放,界面是不会美观的。

而布局是一种可以放置很多控件的容器,它可以按照一定的规则来摆放其内部控件的位置。当前布局中也可以放置布局,经过多次嵌套,我们就能完成较为复杂的界面。不过,要注意布局层级深度,每一次嵌套,测量和布局的耗时都会增加,导致渲染性能差。

比如下面这张图,就能够很好地表示它们之间的关系。

我们来学习一下三种最基本的布局。先创建一个名为 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 值(如 topcenter_verticalbottom)才会生效,水平方向的 layout_gravity 值(如 startcenter_horizontalend)会被忽略。这是因为在水平排列的 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_alignStartandroid:layout_alignEndandroid:layout_alignTopandroid: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,用来直接在图片上叠加一个播放按钮。

相关推荐
puffysang331 小时前
Android 编译FFmpeg4.3.1并集成x264
android
whysqwhw1 小时前
Transcoder代码学习
android
雨白1 小时前
详解 RecyclerView:从基础到布局与点击事件
android
开开心心_Every1 小时前
免费且好用的PDF水印添加工具
android·javascript·windows·智能手机·pdf·c#·娱乐
张风捷特烈1 小时前
每日一题 Flutter#2 | 如何理解 Widget 的不可变性
android·flutter·面试
一起搞IT吧2 小时前
相机Camera日志分析之二十四:高通相机Camx 基于预览1帧的process_capture_request三级日志分析详解
android·图像处理·数码相机
小鱼人爱编程2 小时前
进入外包,我犯了所有程序员都会犯的错!
android·前端·程序员
工业互联网专业2 小时前
基于Android的一周穿搭APP的设计与实现 _springboot+vue
android·vue.js·spring boot·毕业设计·源码·课程设计·一周穿搭app
移动开发者1号3 小时前
Android动画的小小使用
android·kotlin
诚丞成4 小时前
穿越文件之海:Linux链接与库的奇幻旅程,软硬连接与动静态库
android·linux·服务器