8.1  Android的五大布局

Android SDK支持了5种布局。这5种布局是FrameLayout(框架布局)、LinearLayout(线性布局)、RelativeLayout(相对布局)、TableLayout(表格布局)和AbsoluteLayout(绝对布局)。利用这5种布局,可以将屏幕上的视图随心所欲地摆放,而且视图的大小和位置会随着手机屏幕大小的变化做出调整。

8.1.1 框架布局(FrameLayout)

源代码目录:src/ch08/FrameLayout

框架布局是最简单的布局方式,所有添加到这个布局中的视图都以层叠的方式显示。第一个添加到框架布局中的视图显示在最底层,最后一个添加到框架布局中的视图被放在最顶层,上一层的视图会覆盖下一层的视图。这种显示方式类似堆栈,栈顶的视图显示在最顶层,而栈底的视图显示在最底层。因此,也可以将FrameLayout称为堆栈布局。

框架布局在XML布局文件中使用<FrameLayout>标签进行配置,如果使用Java代码,需要创建android.widget.FrameLayout对象。下面是典型的框架布局配置代码。

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"

  android:layout_width="fill_parent" android:layout_height="fill_parent">

  <TextView android:id="@+id/textview" android:layout_width="wrap_content"

    android:layout_height="wrap_content" />

  <Button android:id="@+id/button" android:layout_width="wrap_content"

    android:layout_height="wrap_content" />

</FrameLayout>

从框架布局的特性看,很像Photoshop中的图层。如果将框架布局中的视图放在不同的位置,大小也不同,就可以做出合成图的效果。图8-1是用Photoshop做的一个效果图,这个效果实际上是由3个图层(见右侧的图层列表)合成的。这3个图层分别是:背景、图层1(超人)、图层2(小鸟),我们也可以利用框架布局和ImageView控件做出同样的效果。

 

▲图8-1 Photoshop中的图层

现在准备3个图像文件(background.jpg、superman.png和bird.png),分别用3个ImageView控件显示。首先将background.jpg显示在第一个ImageView控件中(作为最底层的背景),superman.png和bird.png的放置顺序可以随意(因为这两个图并不重合)。完整的XML布局文件的代码如下:

源代码文件:src/ch08/FrameLayout/res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"

  android:layout_width="fill_parent"

  android:layout_height="fill_parent">

  <!-- 最底层的背景图 -->

  <ImageView android:layout_width="fill_parent"

     android:layout_height="wrap_content"android:background="@drawable/background"

     android:layout_gravity="center" />

  <!-- 显示小鸟图像 -->

  <ImageView android:layout_width="63dp"

     android:layout_height="46dp" android:background="@drawable/bird"

     android:layout_gravity="center"android:layout_marginTop="80dp"/>

  <!-- 显示超人图像 -->

  <ImageView android:layout_width="85dp"

     android:layout_height="85dp" android:background="@drawable/superman"

     android:layout_gravity="center"android:layout_marginBottom="80dp"/>

</FrameLayout>

在布局代码中有几个属性需要说明一下。

android:layout_width:当前视图的宽度,该属性可以设置固定的值,如20px、100dp等,但也可以设置3个枚举值:wrap_content、fill_parent和match_parent。其中wrap_content表示框架的宽度会随着视图中内容的大小自动调整。例如,对于TextView控件来说,会根据控件内文本的长度自动调整控件的宽度。而fill_parent和match_parent的含义完全一样,只是match_parent只有在Android 2.2(API Level= 8)及以上版本才支持(因为该属性的名称更能体现该属性的功能)。这两个枚举值都表示当前视图尽可能充满父视图的水平区域,但该属性的作用仍然会受到android:layout_weight属性的影响,该属性会在本章后面的部分详细介绍。

android:layout_height:当前视图的高度。该属性值与android:layout_height属性值的规则完全一样,只是前者表示当前视图的高度,android:layout_width和android:layout_height是必选属性。所有的视图标签都有这两个属性,而且同名属性的含义完全一样。

android:layout_gravity:当前视图在父视图中的位置。在本例中该属性的值是center,表示在垂直和水平方向居中。

android:layout_marginTop:当前视图上边缘到某条基线的距离。例如,当前的ImageView控件是居中的,那么这条基线就是屏幕的中位线,所以80dp就是ImageView控件顶端到这条基线的距离,小鸟的图像会在基线下面的位置显示。

android:layout_marginBottom:当前视图下边到距某条基线的距离。

本例还涉及一个计量单位dp,这个并不等同于像素点。在本节只要知道使用这个单位的长度、位置等属性值会根据屏幕的分辨率自动调整。关于Android的各种计量单位和常用属性将在后面的资源部分详细介绍。

除了使用ImageView控件来显示背景图外,还可以直接使用FrameLayout来显示背景图。因此,上面的XML布局文件可以改成如下形式:

源代码文件:src/ch08/FrameLayout/res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical" android:layout_width="wrap_content"

android:layout_height="wrap_content" android:background="@drawable/background"

android:layout_gravity="center">

  <!-- 显示小鸟图像 -->

  <ImageView android:layout_width="63dp" android:layout_height="46dp"

     android:background="@drawable/bird" android:layout_gravity="center"

     android:layout_marginTop="80dp" />

  <!-- 显示超人图像 -->

  <ImageView android:layout_width="85dp" android:layout_height="85dp"

     android:background="@drawable/superman" android:layout_gravity="center"

     android:layout_marginBottom="80dp" />

</FrameLayout>

布局资源文件必须放在res/layout或相应的本地化资源目录本地化目录会在后面的本地化一章详细介绍。中,通常在窗口类的onCreate方法中使用setContentView方法装载布局资源。该方法可以传递布局资源ID。

public class Main extends Activity

{ 

  @Override

  public void onCreate(Bundle savedInstanceState)

  {

    super.onCreate(savedInstanceState);

    // 装载框架布局

    setContentView(R.layout.main);

  }

}

运行程序,会看到如图8-2所示的显示效果,看看是否与图8-1所示的效果一样呢!

 

▲图8-2 使用FrameLayout 实现的图层效果

8.1.2 线性布局(LinearLayout)

源代码目录:src/ch08/LinearLayout

线性布局是最常用的布局方式。线性布局在XML布局文件中使用<LinearLayout>标签进行配置,如果使用Java代码,需要创建android.widget.LinearLayout对象。

线性布局可分为水平线性布局和垂直线性布局。通过android:orientation属性可以设置线性布局的方向,该属性的可取值是horizontal和vertical,默认值是horizontal。当线性布局的方向是水平时,所有在<LinearLayout>标签中定义的视图都沿着水平方向线性排列。当线性布局的方向是垂直时,所有在<LinearLayout>标签中定义的视图都沿着垂直方向线性排列。

<LinearLayout>标签有一个非常重要的gravity属性,该属性用于控制布局中视图的位置。gravity属性可取的主要值如表8-1所示。如果设置多个属性值,需要使用“ ”进行分隔。在属性值和“ ”之间不能有其他空格、制表符等字符。

 

表8-1 gravity属性的取值

下面的代码就是线性布局的一个简单应用,该布局在屏幕上添加3个按钮,并将它们右对齐。

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:orientation="vertical" android:layout_width="fill_parent"

android:layout_height="fill_parent" android:gravity="right">

    <Button android:layout_width="wrap_content"

     android:layout_height="wrap_content" android:text="按钮1" />

    <Button android:layout_width="wrap_content"

     android:layout_height="wrap_content" android:text="按钮2" />

    <Button android:layout_width="wrap_content"

     android:layout_height="wrap_content" android:text="按钮3" />

</LinearLayout>

使用上面的XML布局文件后,将得到如图8-3所示的效果。如果将gravity属性值改成center,将得到如图8-4所示的效果。

 

▲图8-3 按钮右对齐

 

▲图8-4 按钮中心对齐

<LinearLayout>标签中的子标签还可以使用layout_gravity和layout_weight属性来设置每一个视图的位置。

layout_gravity属性的可取值与gravity属性的可取值相同,表示当前视图在布局中的位置。layout_weight属性需要设置一个非负整数值,如果该属性值大于0,线性布局会根据水平或垂直方向以及不同视图的layout_weight属性值占所有视图的layout_weight属性值之和的比例为这些视图分配自己所占用的区域,视图将按相应比例拉伸。例如,在<LinearLayout>标签中有两个<Button>标签(button1和button2),其中button1的layout_weight属性值是1,button2的layout_weight属性值是2,并且<LinearLayout>标签的orientation属性值是horizontal,那么button1的宽度会占屏幕宽度的2/3,而button2的宽度会占屏幕宽度的1/3。这是由于layout_weight属性的值越小,占的比重越大。

如果layout_weight属性值为0,视图会按原大小显示(不会被拉伸)。对于其余layout_weight属性值大于0的视图,系统将会减去layout_weight属性值为0的视图的宽度或高度,再用剩余的宽度和高度按相应的比例来分配每一个视图所占的宽度和高度。

下面给出一个稍微复杂的线性布局的例子。在这个例子中将屏幕垂直分成相等的两部分,在第1部分的四角和中心分别放一个按钮,第2部分的最下方是一个文本输入框(EditText),放置一个ImageView控件,用于显示图像。图8-5是最终的显示效果。

图8-6是布局中各个标签位置的示意图。我们可以看出,首先使用了两个<LinearLayout>标签将屏幕从垂直方向分成了两个相等部分。这两个<LinearLayout>标签应将android:height属性值设为fill_parent,并且要将android:layout_weight属性值设为相等的值,如都设为1。

 

▲图8-5 线性布局的例子

 

▲图8-6 线性布局的排列位置

在第1部分又使用了5个<LinearLayout>标签将屏幕分成了5份,前两份和后两份分别要使用android:layout_weight属性进行等分,然后在每一个<LinearLayout>标签中使用android:gravity属性设置其中按钮的位置。

在第2部分的<LinearLayout>标签中包含了两个标签:<ImageView>和<EditText>。其中<EditText>放置在最下面,而<ImageView>则充满了<EditText>上方所有的空间。因此,<ImageView>的android:height属性值要设为fill_parent,不过这样一来<ImageView>就会在垂直方向充满第2部分的整个空间,也就是说,把<EditText>挤没了。要想让<EditText>仍然可以显示在最下方,必须要设置<ImageView>标签的android:layout_weight属性。由于<EditText>上方只有一个<ImageView>标签,因此,android:layout_weight属性只要设为合法的值即可,在本例将该属性值设为1。这样一来,<ImageView>就会先考虑其他未设置android:layout_weight属性的标签(<EditText>就未设置该属性)的高度,然后再充满剩余的空间。这种方法经常被使用在屏幕最下方的按钮上。下面来看看完整的XML布局文件。

源代码文件:src/ch08/LinearLayout/res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

  android:orientation="vertical" android:layout_width="fill_parent"

  android:layout_height="fill_parent">

  <LinearLayout android:orientation="vertical"

    android:layout_width="fill_parent" android:layout_height="fill_parent"

    android:layout_weight="1">

    <!-- 设置最上面两个按钮 -->

    <LinearLayout android:orientation="horizontal"

      android:layout_width="fill_parent" android:layout_height="fill_parent"

      android:layout_weight="1">

      <!-- 包含左上角按钮的LinearLayout标签 -->

      <LinearLayout android:orientation="vertical"

        android:layout_width="fill_parent" android:layout_height="fill_parent"

        android:layout_weight="1">

        <Button android:layout_width="wrap_content"

          android:layout_height="wrap_content" android:text="左上按钮"

          android:layout_gravity="left" />

      </LinearLayout>

      <!-- 包含右上角按钮的LinearLayout标签 -->s

      <LinearLayout android:orientation="vertical"

        android:layout_width="fill_parent" android:layout_height="fill_parent"

        android:layout_weight="1">

        <Button android:layout_width="wrap_content"

          android:layout_height="wrap_content" android:text="右上按钮"

          android:layout_gravity="right" />

      </LinearLayout>

    </LinearLayout>

    <!-- 包含中心按钮的LinearLayout标签 -->

    <LinearLayout android:orientation="vertical"

      android:layout_width="fill_parent" android:layout_height="fill_parent"

      android:layout_weight="1" android:gravity="center">

      <Button android:layout_width="wrap_content"

        android:layout_height="wrap_content" android:text="中心按钮" />

    </LinearLayout>

    <!-- 设置最下面两个按钮 -->

    <LinearLayout android:orientation="horizontal"

      android:layout_width="fill_parent" android:layout_height="fill_parent"

      android:layout_weight="1">

      <!-- 包含左下角按钮的LinearLayout标签 -->

      <LinearLayout android:orientation="vertical"

        android:layout_width="fill_parent" android:layout_height="fill_parent"

        android:layout_weight="1"android:gravity="left bottom">

        <Button android:layout_width="wrap_content"

          android:layout_height="wrap_content" android:text="左下按钮" />

      </LinearLayout>

      <!-- 包含右下角按钮的LinearLayout标签 -->

      <LinearLayout android:orientation="vertical"

        android:layout_width="fill_parent" android:layout_height="fill_parent"

        android:layout_weight="1" android:gravity="right bottom">

        <Button android:layout_width="wrap_content"

          android:layout_height="wrap_content" android:text="右下按钮" />

      </LinearLayout>

    </LinearLayout>

  </LinearLayout>

  <LinearLayout android:orientation="vertical"

    android:layout_width="fill_parent" android:layout_height="fill_parent"

    android:layout_weight="1">

    <!-- 在第二部分上方显示的ImageView标签 -->

    <ImageView android:layout_width="fill_parent"

      android:layout_height="fill_parent" android:src="@drawable/background"

      android:layout_weight="1" />

    <!-- 在第二部分最下方显示的EditText标签 -->

    <EditText android:layout_width="fill_parent"

      android:layout_height="wrap_content" android:hint="请在这里输入文本" />

  </LinearLayout>

</LinearLayout>

扩展学习:<FrameLayout>标签的妙用

源代码目录:src/ch08/MiddleRight

现在我们要设计一个布局,有两个按钮,第一个按钮水平居中,第二个按钮右对齐,如图8-7所示。

 

▲图8-7 居中和右对齐的两个按钮

读者可以先不看后面的答案,看看能否设计出如图8-7所示的布局。

由于本节讲的是线性布局,下面我们看看利用线性布局是否能很容易地做出如图8-7所示的效果。如果要两个按钮水平排列,需要将<LinearLayout>标签的android:orientation属性值设为horizontal。如果将<LinearLayout>标签的android:gravity属性值设为 center_horizontal,两个按钮就会连在一起,并且水平居中,如图8-8所示。如果将<LinearLayout>标签的android:gravity属性值设为right,则两个按钮都会右对齐,如图8-9所示。总之,没有办法使用android:gravity属性让两个按钮分别居中和右对齐。而<Button>标签的android:layout_gravity属性值水平居中和右对齐需要将<LinearLayout>标签的android:orientation属性值设为vertical如果是水平线性布局,子视图只能通过android:layout_gravity属性设为垂直居中,而对于垂直线性布局,子视图只能通过android:layout_gravity属性设为水平居中。。因此,通过android:layout_gravity属性也无法达到如图8-7所示的效果。尽管通过更复杂的手段也能用线性布局实现图8-7所示的效果,但有简单的方法为什么不用呢?

 

▲图8-8 两个居中显示的按钮

 

▲图8-9 右对齐的两个按钮

接下来我们推出FrameLayout布局的解决方案。利用FrameLayout布局的图层效果可以很容易地达到目的。也就是说,如图8-7所示的两个按钮分别位于两个图层中,“按钮1”在图层1中居中对齐(将android:layout_gravity属性值设为center_horizontal),“按钮2”在图层2中右对齐(将android:layout_gravity属性值设为right)。这两个按钮在<FrameLayout>标签中先定义谁都没有关系。实现如图8-7所示效果的布局代码如下:

源代码文件:src/ch08/MiddleRight/res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"

  android:layout_width="fill_parent" android:layout_height="fill_parent">

  <Button android:layout_width="wrap_content"

    android:layout_height="wrap_content" android:text="按钮1"

    android:layout_gravity="center_horizontal" />

  <Button android:layout_width="wrap_content"

    android:layout_height="wrap_content" android:text="按钮2"

    android:layout_gravity="right" />

</FrameLayout>

8.1.3 相对布局(RelativeLayout)

源代码目录:src/ch08/RelativeLayout

相对布局可以设置某一个视图相对于其他视图的位置,这些位置包括上、下、左、右。设置这些位置的属性分别是android:layout_above、android:layout_below、android:layout_toLeftOf、android:layout_toRightOf。除此之外,还可以通过android:layout_alignBaseline属性设置视图的底端对齐。

这5个属性的值必须是已存在的资源ID,也就是另一个视图的android:id属性值。下面的代码是一个典型的使用RelativeLayout布局的例子。

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

  android:layout_width="fill_parent" android:layout_height="fill_parent" >

  <TextView android:id="@+id/textview1" android:layout_width="wrap_content"

    android:layout_height="wrap_content" android:textSize="20dp"

    android:text="文本1"/>

  <!-- 将这个TextView放在textview1的右侧 -->

  <TextView android:layout_width="wrap_content"

    android:layout_height="wrap_content" android:textSize="20dp"

    android:text="文件2" android:layout_toRightOf="@id/textview1"/>

</RelativeLayout>

下面用相对布局实现如图8-10所示的梅花图案效果。

 

▲图8-10 使用相对布局实现的梅花图案

如图8-10所示界面的基本思想是先将“Button1”放在左上角,然后将“Button2”放在“Button1”的右下侧,最后以“Button2”为轴心,放置“Button3”、“Button4”和“Button5”。在设置完5个按钮后,再将<RelativeLayout>标签的android:gravity属性值设为center。布局的完整代码如下:

源代码文件:src/ch08/RelativeLayout/res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android. com/apk/res/android"

  android:layout_width="fill_parent" android:layout_height="fill_parent"

  android:gravity="center">

  <Button android:id="@+id/button1" android:layout_ width="wrap_content"

    android:layout_height="wrap_content" android:textSize="16dp"

    android:text="Button1" />

  <Button android:id="@+id/button2" android:layout_width="wrap_content"

    android:layout_height="wrap_content" android:textSize="16dp"

    android:text="Button2" android:layout_toRightOf="@id/button1"

    android:layout_below="@id/button1" />

  <Button android:id="@+id/button3" android:layout_width="wrap_content"

    android:layout_height="wrap_content" android:textSize="16dp"

    android:text="Button3" android:layout_toLeftOf="@id/button2"

    android:layout_below="@id/button2" />

  <Button android:id="@+id/button4" android:layout_width="wrap_content"

    android:layout_height="wrap_content" android:textSize="16dp"

    android:text="Button4" android:layout_toRightOf="@id/button2"

    android:layout_above="@id/button2" />

  <Button android:id="@+id/button5" android:layout_width="wrap_content"

    android:layout_height="wrap_content" android:textSize="16dp"

    android:text="Button5" android:layout_toRightOf="@id/button2"

    android:layout_below="@id/button2" />

</RelativeLayout>

8.1.4 表格布局(TableLayout)

源代码目录:src/ch08/TableLayout

表格布局可将视图按行、列进行排列。一个表格布局由一个<TableLayout>标签和若干<TableRow>标签组成。下面我们使用表格布局来实现如图8-11所示的效果。

 

▲图8-11 表格布局的效果

完整的布局代码如下:

源代码文件:src/ch08/TableLayout/res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>

<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"

  android:layout_width="fill_parent" android:layout_height="fill_parent" >

  <TableRow android:paddingTop="20dp" >

    <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content"

      android:src="@drawable/christmas_background1_small" />

    <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content"

      android:src="@drawable/christmas_background2_small" android:paddingLeft="20dp"/>

    <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content"

      android:src="@drawable/christmas_background3_small" android:paddingLeft="20dp"/>

    <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content"

      android:src="@drawable/christmas_background4_small" android:paddingLeft="20dp"/>

  </TableRow>

  <TableRow android:paddingTop="20dp">

    <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content"

      android:src="@drawable/christmas_background5_small" />

    <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content"

      android:src="@drawable/christmas_background6_small" android:paddingLeft="20dp"/>

    <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content"

      android:src="@drawable/christmas_background7_small" android:paddingLeft="20dp"/>

    <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content"

      android:src="@drawable/christmas_background8_small" android:paddingLeft="20dp"/>

  </TableRow>

</TableLayout>

表格布局在实现行列效果中并不常用,一般会使用GridView控件来代替表格布局,该控件将在后面的章节详细介绍。

8.1.5 绝对布局(AbsoluteLayout)

通过绝对布局,可以任意设置视图的位置。绝对布局使用<AbsoluteLayout>标签定义,其中android:layout_x和android:layout_y属性可以设置视图的横坐标和纵坐标,如下面的代码所示:

<?xml version="1.0" encoding="utf-8"?>

<AbsoluteLayout xmlns:android="http://schemas.android.com/apk/res/android"

  android:layout_width="fill_parent" android:layout_height="fill_parent">

  <Button android:layout_width="wrap_content"

    android:layout_height="wrap_content" android:layout_x="40dp" android:layout_y="80dp"

    android:text="按钮" />

</AbsoluteLayout>

绝对布局并不建议使用,因为Android设备的屏幕大小和长宽比例有很大差异,使用绝对布局很难适应各种屏幕尺寸。