第三章 Android界面布局与高级控件

Android布局管理

1.1 LinearLayout线性布局

LinearLayout是一个视图组(ViewGroup),用于使所有子视图(视图组内的组件)在单个方向(垂直或水平)保持对齐。使用android:orientation属性指定其子视图的排列方向。
定义线性布局的标签格式如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    tools:context=".MainActivity">
</LinearLayout>

LinearLayout的所有子视图依次堆叠,因此无论子视图有多宽,设置android:orientation="vertical"时,视图内的组件在垂直(上下)方向上排列,即每行均只有一个子视图;设置android:orientation="horizontal"时,视图内的组件水平排列,水平列表将只有一行高(最高子视图的高度加上内边距)。LinearLayout会考虑子视图之间的边距以及每个子视图的对齐方式(右对齐,居中对齐或左对齐)。

线性布局内的子视图使用布局权重调节所占空间的大小。LinearLayout使用android:layout_weight属性为各个子视图分配权重。此属性会根据视图应在屏幕上占据的空间大小,向视图分配权重值。如果拥有更大的权重值,则视图便可展开,进而填充父视图中的任何剩余空间。子视图可指定权重值,然后系统会按照子视图所声明的权重值比例,为其分配视图组中的任何剩余空间。默认权重为零。

在线性布局中,让每个子视图在垂直布局中占相同大小的屏幕高度,必须将每个视图的android:layout_height设置为“0dp”;水平布局时,让每个子视图在水平方向上占用相同的宽度,必须将每个子视图的android:layout_width设置为“0dp”(针对水平布局)。然后将每个视图的android:layout_weight设置为"1"。

示例1 设置3个按钮平分水平空间

布局文件代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <Button
        android:id="@+id/button1"
        android:text="Button1"
        android:textSize="20sp"
        android:layout_weight="1"
        android:layout_width="0dp"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/button2"
        android:text="Button2"
        android:textSize="20sp"
        android:layout_weight="1"
        android:layout_width="0dp"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/button3"
        android:text="Button3"
        android:textSize="20sp"
        android:layout_weight="1"
        android:layout_width="0dp"
        android:layout_height="wrap_content"/>
</LinearLayout>

程序运行后界面显示如下图所示
在这里插入图片描述
如果要让子元素使用大小不同的屏幕空间,可以为子视图分配不同的权重。例如,有三个文本字段,其中两个声明权重为1,另一个未赋予权重,则没有权重的第三个文本字段将不会扩展,并且仅占据其内容1所需的区域。另一方面,另外两个文本字段将以同等幅度进行扩展,已填充测量三个字段后仍剩余的空间。如果有三个文本字段,其中两个字段声明权重为1,而为第三个字段赋予权重2(而非0),则现声明第三个字段比另外两个字段更为重要,因此,该字段将获得总剩余空间的一半,而其他两个字段均享余下的空间。

示例2 设计SendMessage的界面布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
    android:paddingLeft="16dp"
    android:paddingRight="16dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <EditText
        android:id="@+id/To"
        android:hint="To"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
    <EditText
        android:id="@+id/subject"
        android:hint="Subject"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <EditText
        android:id="@+id/message"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:gravity="top"
        android:hint="message" />

    <Button                      
        android:id="@+id/send"
        android:text="SEND"
        android:layout_gravity="right"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>

To字段、Subject行和Send按钮均仅占用各自所需的高度。此配置允许消息自身占用Activity的剩余高度,显示效果如下图所示。
在这里插入图片描述

1.2 Relativelayout 相对布局

相对布局(Relativelayout )是通过相对定位指定子视图位置,即以其他空间或父容器为参照物,摆放控件位置。
定义相对布局的格式如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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">
  
</RelativeLayout>

为了更好地确定布局中控件的位置,相对布局提供了很多属性,在相对布局中的控件可以使用其属性控制其位置。具体如下表所示。
在这里插入图片描述
示例3 相对布局的使用

布局文件的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:orientation="vertical"
    android:paddingLeft="16dp"
    android:paddingRight="16dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
<Button
    android:id="@+id/Button1"
    android:text="Button1"
    android:layout_alignParentTop="true"
    android:layout_marginTop="100dp"
    android:layout_marginLeft="30dp"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/Button2"
        android:text="Button2"
        android:layout_marginTop="15dp"
        android:layout_toRightOf="@id/Button1"
        android:layout_below="@id/Button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
    <Button
        android:id="@+id/Button3"
        android:text="Button3"
        android:layout_marginTop="15dp"
        android:layout_toRightOf="@id/Button2"
        android:layout_below="@id/Button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</RelativeLayout>

运行后,显示的效果如下图所示。
在这里插入图片描述

1.3 TableLayout表格布局

表格布局采用行、列的形式来管理控件,它不需要明确声明包含多少行,多少列,而是通过在Table布局中添加TableRow布局来控制表格的行数,通过在TableRow布局中添加控件来控制表格的列数。

表格布局的定义如下:

<?xml version="1.0" encoding="utf-8"?>
<TableLayout 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">
    
</TableLayout>

在这里插入图片描述
示例4 表格布局的应用

XML布局文件的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<TableLayout 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"
    android:stretchColumns="1"
    tools:context=".MainActivity">
    <TableRow
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <Button
            android:id="@+id/button1"
            android:text="Button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_column="0" />
    </TableRow>
    
    <TableRow
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <Button
            android:id="@+id/button2"
            android:text="Button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_column="1" />
    </TableRow>
    
</TableLayout>

表格布局设计了两行,每行一个按钮。使用android:stretchColumns="1"属性拉伸第二列,android:layout_column="0"属性表示显示在第一列中。需要注意的是,TableRow不需要设置宽度和高度,其宽度一定是自动填充容器,高度根据内容改变。但对于TableRow的其他控件来说,我们是可以设置宽度和高度的。显示的界面效果如下图所示。
在这里插入图片描述

1.4 FrameLayout 帧布局

帧布局是Android布局中最简单的一种,帧布局为每个加入其中的控件创建了一块空白区域。采用帧布局的方式设计界面时,只能在屏幕左上角显示一个控件;如果添加多个控件,这些控件会依次重叠在屏幕左上角显示,且会透明显示之前的文本。

帧布局的定义代码如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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">
    
</FrameLayout>

示例5 帧布局的应用

布局文件的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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/button1"
        android:background="#00FFFF"
        android:text="Button1"
        android:layout_width="314dp"
        android:layout_height="315dp"/>
    <Button
        android:id="@+id/button2"
        android:background="#D9D919"
        android:text="Button2"
        android:layout_width="216dp"
        android:layout_height="140dp"/>
    <Button
        android:id="@+id/button3"
        android:background="#0000FF"
        android:text="Button3"
        android:layout_width="103dp"
        android:layout_height="wrap_content"/>
</FrameLayout>

运行后显示的界面如下图所示
在这里插入图片描述

1.5 ConstraintLayout 约束布局

ConstraintLayout布局方式使用扁平视图层次结构(无嵌套视图组)创建复杂的大型布局。它与RelativeLayout相似,其中所有的视图均根据同级视图与父布局之间的关系进行布局,但其灵活性要高于RelativeLayout,并且更易于与Android Studio的布局编辑器配合使用。使用ConstraintLayout 约束布局可以对视图进行可视化设计,还能减少其他布局中的布局嵌套,约束布局重在约束。如下图所示的效果,使用约束布局便能很好实现。
在这里插入图片描述
ConstraintLayout的所有功能均可以直接通过布局编辑器的可视化工具来使用,因为布局的API和布局编辑器是专为彼此构建的。因此,完全可以使用ConstraintLayout通过拖放的形式(而非修改XML)来构建布局。

ConstraintLayout是Android Studio2.2新添加的布局,它适合使用可视化的方式编写界面布局。可视化操作的背后仍然是使用XML代码实现的,只不过这些代码1是Android Studio根据我们的操作自动生成的。

要在项目中使用ConstraintLayout,需要按照以下步骤操作引入支持的库:
(1)确保maven.google.com代码库已在模块级build.gradle文件中声明:

repositories{
   google()
}

(2)将该库作为依赖项添加到同一个build.gradle文件中,如以下示例所示。

 implementation("androidx.constraintlayout:constraintlayout:2.1.4")

(3)在工具栏或同步通知中,点击Sync Project with Gradle Files。
使用的开发工具版本会自动导入Constraintlayout约束布局的支持库。布局中元素的定位主要有三种方式:

  1. 父布局内居中或倾斜
    在ConstraintLayout布局中,控件可以通过添加约束的方式确定该控件在父布局(ConstraintLayout)中的相对位置。当相同方向(横向或纵向)上,控件两边同时向Constraintlayout添加约束,则控件在添加约束的方向撒花姑娘居中显示。父布局内居中效果如图3.7所示。

在约束时同向相反的情况下,默认控件是居中的,但是也像拔河一样,两个约束的力大小不等时,就会产生倾斜。横向或纵向的倾斜属性设置如表3.4所示,设置倾斜的效果如图3.8所示。
在这里插入图片描述
2. 相对定位

相对定位是在ConstraintLayout中创建布局的基本构建方法之一。相对定位即一个控件相对于另一个控件进行定位。ConstraintLayout布局中的控件可以在横向和纵向上以添加约束关系的方式进行相对定位,其中,横向边包括Left、Start、Right、End,纵向边包括Top、Bottom、Baseline(文本底部的基准线)。约束布局中相对定位关系的属性如表3.5所示。
在这里插入图片描述
3. 链控制

Chain(链)是一种特殊的约束,使用链能够对一组水平或竖直方向互相关联的控件进行统一管理。一组控件通过双向的约束关系链接起来,就能形成一个Chain。链中的视图有多种分布方式,如图3.10所示。
在这里插入图片描述
图3.10中链的样式有多种,其说明如下:
①Spread:视图是均匀分布的(在考虑外边距之后)。这是默认值。

②Spread inside:第一个和最后一个视图固定在链两端的约束边界上,其余视图均与分布。

③Weighted:当链设置为Spread或Spread inside时,可以通过将一个或多个视图设置为"match constraints"(0dp)来填充剩余空间。默认情况下,设置为"match constraints"的每个视图之间的空间均与分布,但可以使用layout_constraintHorizontal_weight和layout_constraintVertical_weight属性为每个视图分配重要性权重。

④Packed:视图打包在一起(在考虑外边距之后)。可以通过更改链的头视图偏差调整整条链的偏差(左/右或上/下)。

链的头视图(水平链中最左侧的视图以及垂直链中最顶部的视图)以XML格式定义链的样式。通过选择链中的任意视图,然后点击出现在该视图下方的链按钮,在Spread、Spreadinside和Packed之间进行切换。

例如在设计视图中,将Button1、Button2、Button3三个按钮设计为一组水平链,然后选择链的头Button1设计顶端边缘对齐,设计的效果如图3.11所示。
在这里插入图片描述

2. AdapterView及其子类

AdapterView是一个抽象基类,是Android中非常重要的一个组件,其派生的子类在用法上十分相似,只是显示的界面有所不同。AdapterView和子类的关系如图3.12所示。
在这里插入图片描述可以看出,AdapterView继承自ViewGroup,它本质上也是容器。通过适配器Adapter(后续内容会讲解)提供列表项,利用AdapterView的setAdapter(Adapter)方法将列表项加入AdapterView容器中。

2.1 ListView

ListView是手机系统中使用非常广泛的一种组件,它以垂直列表的形式显示所有的列表项。生成列表视图有如下两种方式:

(1)直接使用ListView进行创建。

(2)创建一个继承ListActivity的Activity(相当于该Activity显示的组件为ListView)。

一旦在程序中获得ListView,接下来就需要为ListView设置它要显示的列表项。ListView通过setAdapter(Adapter)方法为之提供Adapter,并由Adapter提供要显示的内容。

先来看AbsListView提供的常用XML属性,如表3.6所示。
在这里插入图片描述在这里插入图片描述
示例6 使用ListView展示课程列表信息,运行效果如下图所示。
在这里插入图片描述
要实现界面,首先需要在布局文件中添加ListView组件,然后再values文件夹中添加一个新的courses.xml文件用于存储字符串数组,在ListView组件中通过android:entries=“@array/course_name”指定字符串数组为ListView组件的数据源,将数组的内容显示到ListView中。values下的courses.xml中的代码如下;

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="course_name">
        <item>Java程序设计</item>
        <item>计算机网络技术</item>
        <item>数据结构与算法</item>
        <item>计算机体系结构</item>
        <item>Android系统开发</item>
    </string-array>
    
</resources>

布局文件activity_course_list.xml的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<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=".CourseListActivity">

    <ListView
        android:paddingTop="20dp"
        android:layout_width="409dp"
        android:layout_height="729dp"
        android:divider="@color/design_default_color_primary_dark"
        android:dividerHeight="1dp"
        android:entries="@array/course_name"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    
</androidx.constraintlayout.widget.ConstraintLayout>

使用ListView显示字符串数组的数据比较简单,这时ListView能显示的内容很少,如果想对ListView进行外观和行为的定制,就需要把ListView作为AdapterView来使用,然后通过Adapter来控制每个列表项的外观和行为。

2.2 Adapter接口及其实现类

数据适配器是数据与视图之间的桥梁,它类似于一个转换器,将复杂的数据转换成用户可以接受的方式进行呈现。Adapter本身只是一个接口,它派生了两个子类:ListAdapter和SpinnerAdapter类。具体的派生类及继承关系如图3.14所示
在这里插入图片描述在图3.14中,BaseAdapter继承了ListAdapter和SpinnerAdapter接口,因BaseAdapter及其子类都可以为AbsListView或AbsSpinner提供列表项。

Adapter常用的实现类如下:

1.BaseAdapter

BaseAdapter,顾名思义,是基本的适配器。它实际上是一个抽象类,通常在自定义适配器时会继承BaseAdapter。BaseAdapter中的方法如表3.7所示。
在这里插入图片描述
2.ArrayAdapter

ArrayAdapter通常用于将数组或List集合的多个值包装成多个列表项。创建ArrayAdapter并使用的代码如下:

package com.example.chapter3;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class ListViewActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_list_view);
        ListView lvCourses=findViewById(R.id.lv_courses);
        String []courses=new String[]{"计算机网络","数据结构","操作系统"};
        ArrayAdapter<String> adapter =new ArrayAdapter <String> (ListViewActivity.this,android.R.layout.simple_expandable_list_item_1,courses);
        lvCourses.setAdapter(adapter);
    }
}

程序运行后显示效果如下图所示。
Adapter填充数据ArrayAdapter也是BaseAdapter的子类,,用法与SimpleAdapter类似,开发者只需要在构造方法里面传入相应参数即可。ArrayAdapter通常用于适配TextView控件。创建ArrayAdapter构造方法内的参数android.R.layout。simple_expandable_list_item_1为系统提供文件,该文件里面有一个TextView控件,该文件可以直接使用,源文件代码如下:

    <?xml version="1.0" encoding="utf-8"?>
    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@android:id/text1"
        android:layout_width="match_parent"
        android:layout_height="?android:attr/listPreferredItemHeight"
        android:textAppearance="?android:attr/textAppearanceListItem"
        android:paddingStart="?android:expandableListPreferredChildPaddingLeft"
        android:gravity="center_vertical"
        android:textAlignment="viewStart"
        />

3.SimpleAdapter

SimpleAdapter是功能强大的Adapter,将List集合的多个对象包装成多个列表项。SimpleAdapter继承自BaseAdapter,实现了BaseAdapte的四个抽象方法并进行封装。因此在使用SimpleAdapter进行数据适配时,只需要再构造方法中传入相应的参数即可。
SimpleAdapter构造方法的具体信息如下:

public SimpleAdapter(Context context,List<?extends Map <String>, ?>> data, int resource,String [] from,int[] to)

使用SimpleAdapter时有5个参数需要填写,其中后面4个非常关键:

第1个参数为上下文对象。
第2个参数:List<? extends Map<String,? >>data。它是一个List类型的集合对象,该集合中每个Map对象生成一个列表项。
第3个参数:int resource.该参数指定一个界面布局的ID。例如,指定为R.layout.simple_layout,即使用app\src\main\res\layout\simple_layout.xml文件作为列表项组件。
第4个参数:String[] from,决定提取Map<String,?>对象中哪些key对象的value来生成列表项。
第5个参数:int[]to,决定填充哪些组件。

示例7 使用SimpleAdapter来获取列表项

使用SimpleAdapter,需要在layout目录下添加一个自定义的布局文件list_item1_layout.xml文件来设置ListView中每项的样式,代码如下:

在这里插入代码片