Android 基础 — 2、几种常用的布局 | 青训营笔记

89 阅读6分钟

这是我参与「第四届青训营」笔记创作的第 4 天

此篇笔记是 Android 基础 —— 几种常用的布局

本篇笔记分为 4 部分

  1. 线性布局LinearLayout
  2. 相对布局RelativeLayout
  3. 网格布局GridLayout
  4. 网格布局GridLayout

二、几种常用的布局

本篇笔记将介绍常见的几种布局用法,包括在某个方向上顺序排列的线性布局,参照其他视图的位置相对排列的相对布局,像表格那样分行分列显示的网格布局,以及支持通过滑动操作拉出更多内容的滚动视图。

线性布局LinearLayout

在前几篇笔记中,XML 文件用到了 LinearLayout 布局,它的学名为线性布局。顾名思义,线性布局就像是用一根线把它的内部视图串起来,故而内部视图之间的排列顺序是固定的,要么从左到右排列,要么从上到下排列。

XML 文件中,LinearLayout 通过属性 android:orientation 区分两种方向,其中从左到右排列叫作水平方向,属性值为 horizontal

从上到下排列叫作垂直方向,属性值为 vertical 。如果 LinearLayout 标签不指定具体方向,则系统默认该布局为水平方向排列,也就是默认 android:orientation="horizontal"

下面做个实验,让 XML 文件的根节点挂着两个线性布局,第一个线性布局采取 horizontal 水平方向,第二个线性布局采取 vertical 垂直方向。然后每个线性布局内部各有两个文本视图,通过观察这些文本视图的排列情况,从而检验线性布局的显示效果。详细的 XML 文件内容如下代码块所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        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:gravity="center"
            android:text="横排第一个"
            android:textColor="#000000"
            android:background="#ff0000"
            android:textSize="20sp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:layout_weight="1"
            android:text="横排第二个"
            android:background="#ff0000"
            android:textColor="#000000"
            android:textSize="20sp" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:text="竖排第一个"
            android:background="#0000ff"
            android:textColor="#000000"
            android:textSize="20sp" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:background="#0000ff"
            android:text="竖排第二个"
            android:textColor="#000000"
            android:textSize="20sp" />
    </LinearLayout>

</LinearLayout>

image.png

除了方向之外,线性布局还有一个权重概念,所谓权重,指的是线性布局的下级视图各自拥有多大比例的宽高。

比如一块蛋糕分给两个人吃,可能两人平均分,也可能甲分三分之一,乙分三分之二。两人平均分的话,先把蛋糕切两半,然后甲分到一半,乙分到另一半,此时甲乙的权重比为 1:1 。甲分三分之一、乙分三分之二的话,先把蛋糕平均切成三块,然后甲分到一块,乙分到两块,此时甲乙的权重比为 1:2

就线性布局而言,它自身的尺寸相当于一整块蛋糕,它的下级视图们一起来分这个尺寸蛋糕,有的视图分得多,有的视图分得少。分多分少全凭每个视图分到了多大的权重,这个权重在 XML 文件中通过属性 android:layout_weight 来表达。android:layout_height="wrap_content" android:text="横排第一个" android:textSize="17sp" android:textColor="#000000" /> 把线性布局看作蛋糕的话,分蛋糕的甲乙两人就相当于线性布局的下级视图。

假设线性布局平均分为左、右两块,则甲视图和乙视图的权重比为 1:1 ,意味着两个下级视图的 layout_weight 属性都是 1 。不过视图 有宽高两个方向,系统怎知 layout_weight 表示哪个方向的权重呢?所以这里有个规定,一旦设置了 layout_weight 属性值,便要求 layout_width0dp 或者 layout_height0dp 。如果 layout_width0dp ,则 layout_weight 表示水平方向的权重,下级视图会从左往右分割线性布局;如果 layout_height0dp ,则 layout_weight 表示垂直方向的权重,下级视图会从上往下分割线性布局。按照左右均分的话,线性布局设置水平方向 horizontal ,且甲乙两视图的 layout_width 都填 0dp ,layout_weight 都填 1

相对布局RelativeLayout

线性布局的下级视图是顺序排列着的,另一种相对布局的下级视图位置则由其他视图决定。相对布局名为 RelativeLayout ,因为下级视图的位置是相对位置,所以得有具体的参照物才能确定最终位置。如果 不设定下级视图的参照物,那么下级视图默认显示在 RelativeLayout 内部的左上角。 用于确定下级视图位置的参照物分两种,一种是与该视图自身平级的视图;另一种是该视图的上级视图 (也就是它归属的 RelativeLayout )。

为了更好地理解上述相对属性的含义,接下来使用 RelativeLayout 及其下级视图进行布局来看看实际效 果图。下面是演示相对布局的 XML 文件例子:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="300dp">

    <TextView
        android:id="@+id/tv_center"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:background="#ffffff"
        android:text="我在中间"
        android:textColor="#0000ff"
        android:textSize="10sp" />

    <TextView
        android:id="@+id/tv_center_horizontal"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:background="#ffffff"
        android:text="我在水平中间"
        android:textColor="#0000ff"
        android:textSize="10sp" />

    <TextView
        android:id="@+id/tv_center_vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:background="#ffffff"
        android:text="我在垂直中间"
        android:textColor="#0000ff"
        android:textSize="10sp" />

    <TextView
        android:id="@+id/tv_parent_left"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:background="#ffffff"
        android:text="我与上级左边对其"
        android:textColor="#0000ff"
        android:textSize="10sp" />

    <TextView
        android:id="@+id/tv_parent_right"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:background="#ffffff"
        android:text="我与上级右边对其"
        android:textColor="#0000ff"
        android:textSize="10sp" />

    <TextView
        android:id="@+id/tv_parent_top"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:background="#ffffff"
        android:text="我与上级顶部对其"
        android:textColor="#0000ff"
        android:textSize="10sp" />

    <TextView
        android:id="@+id/tv_parent_bottom"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:background="#ffffff"
        android:text="我与上级底部对其"
        android:textColor="#0000ff"
        android:textSize="10sp" />

    <TextView
        android:id="@+id/tv_center_left"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toLeftOf="@id/tv_center"
        android:layout_alignTop="@id/tv_center"
        android:background="#ffffff"
        android:text="我在中间左边"
        android:textColor="#0000ff"
        android:textSize="10sp" />

    <TextView
        android:id="@+id/tv_center_right"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/tv_center"
        android:layout_alignBottom="@id/tv_center"
        android:background="#ffffff"
        android:text="我在中间右边"
        android:textColor="#0000ff"
        android:textSize="10sp" />

    <TextView
        android:id="@+id/tv_center_above"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/tv_center"
        android:layout_alignLeft="@id/tv_center"
        android:background="#ffffff"
        android:text="我在中间上面"
        android:textColor="#0000ff"
        android:textSize="10sp" />

    <TextView
        android:id="@+id/tv_center_below"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/tv_center"
        android:layout_alignRight="@id/tv_center"
        android:background="#ffffff"
        android:text="我在中间下面"
        android:textColor="#0000ff"
        android:textSize="10sp" />

</RelativeLayout>

image.png

网格布局GridLayout

虽然线性布局既能在水平方向排列,也能在垂直方向排列,但它不支持多行多列的布局方式,只支持单行(水平排列)或单列(垂直排列)的布局方式。若要实现类似表格那样的多行多列形式,可采用网格布局 GridLayout

网格布局默认从左往右、从上到下排列,它先从第一行从左往右放置下级视图,塞满之后另起一行放置其余的下级视图,如此循环往复直至所有下级视图都放置完毕。为了判断能够容纳几行几列,网格布局新增了 android:columnCountandroid:rowCount 两个属性,其中 columnCount 指定了网格的列数,即每行能放多少个视图;rowCount 指定了网格的行数,即每列能放多少个视图。

下面是运用网格布局的 XML 布局样例,它规定了一个两行两列的网格布局,且内部容纳四个文本视图。 XML 文件内容如下所示:

<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:columnCount="2"
    android:rowCount="2">

    <TextView
        android:layout_width="180dp"
        android:layout_height="60dp"
        android:layout_columnWeight="1"
        android:background="#ffcccc"
        android:gravity="center"
        android:text="浅红色"
        android:textColor="#000000"
        android:textSize="17sp" />

    <TextView
        android:layout_width="180dp"
        android:layout_height="60dp"
        android:layout_columnWeight="1"
        android:background="#ffaa00"
        android:gravity="center"
        android:text="橙色"
        android:textColor="#000000"
        android:textSize="17sp" />

    <TextView
        android:layout_width="180dp"
        android:layout_height="60dp"
        android:layout_columnWeight="1"
        android:background="#00ff00"
        android:gravity="center"
        android:text="绿色"
        android:textColor="#000000"
        android:textSize="17sp" />

    <TextView
        android:layout_width="180dp"
        android:layout_height="60dp"
        android:layout_columnWeight="1"
        android:background="#660066"
        android:gravity="center"
        android:text="深紫色"
        android:textColor="#000000"
        android:textSize="17sp" />

</GridLayout>

image.png

由图可见,App 界面的第一行分布着浅红色背景与橙色背景的文本视图,第二行分布着绿色背景与深紫色背景的文本视图,说明利用网格布局实现了多行多列的效果。

滚动视图ScrollView

手机屏幕的显示空间有限,常常需要上下滑动或左右滑动才能拉出其余页面内容,可惜一般的布局节点都不支持自行滚动,这时就要借助滚动视图了。

与线性布局类似,滚动视图也分为垂直方向和水平方向两类,其中垂直滚动视图名为 ScrollView ,水平滚动视图名为 HorizontalScrollView 。这两个滚动视图的使用并不复杂,主要注意以下 3 点:

  1. 垂直方向滚动时,layout_width 属性值设置为 match_parentlayout_height 属性值设置为 wrap_content
  2. 水平方向滚动时, layout_width 属性值设置为 wrap_contentlayout_height 属性值设置为 match_parent
  3. 滚动视图节点下面必须且只能挂着一个子布局节点,否则会在运行时报错Caused by: java.lang.IllegalStateException:ScrollView can host only one direct child

下面是垂直滚动视图 ScrollView 和水平滚动视图 HorizontalScrollViewXML 例子:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <HorizontalScrollView
        android:layout_width="wrap_content"
        android:layout_height="200dp">

        <!--        水平方向的线性布局 两个子视图的颜色分别为青色和黄色-->
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="horizontal">

            <View
                android:layout_width="300dp"
                android:layout_height="match_parent"
                android:background="#aaffff" />

            <View
                android:layout_width="300dp"
                android:layout_height="match_parent"
                android:background="#ffff00" />

        </LinearLayout>


    </HorizontalScrollView>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <!--        水平方向的线性布局 两个子视图的颜色分别为青色和黄色-->
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <View
                android:layout_width="match_parent"
                android:layout_height="400dp"
                android:background="#00ff00" />

            <View
                android:layout_width="match_parent"
                android:layout_height="400dp"
                android:background="#ffffaa" />

        </LinearLayout>
    </ScrollView>


</LinearLayout>

image.png

运行测试 App ,可知 ScrollView 在纵向滚动,而 HorizontalScrollView 在横向滚动。 有时 ScrollView 的实际内容不够,又想让它充满屏幕,怎么办呢?

如果把 layout_height 属性赋值为 match_parent ,结果还是不会充满,正确的做法是再增加一行属性 android:fillViewport (该属性为 true 表示允许填满视图窗口),属性片段举例如下:

android:layout_height="match_parent"   
android:fillViewport="true"