1.引入
1.1 想法归类
开始构建一个应用时,会考虑这个应用要包含什么,并且肯定会有很多想法。那么需要对这些想法进行归类。
要组织这些想法,可以将其归为3种不同类型的活动:顶级活动、类别活动和详细信息/编辑活动。
顶级活动:
顶级活动( top-level activity)包括对用户来说最重要的功能,要为用户提供便捷的途径,使他们能轻松地导航到这些功能。在大多数应用中,用户看到的第一个活动就是顶级活动。
类别活动:
类别活动(Categoryactivity)会显示属于某个特定类别的数据,通常显示在一个列表中。这一类活动通常用于帮助用户导航到详细信息/编辑活动。显示Starbuzz所有饮品的清单就是一个类别活动的例子。
详细信息/编辑活动
详细信息/编辑活动(Detail/edit activity)会显示某个特定记录的详细信息,允许用户编辑这个记录,或者可以让用户输入新的记录。为用户显示某个特定饮品详细信息的活动就是一个详细信息/编辑活动的例子。
对活动分类之后,可以用它们构建一个层次结构,展示用户如何在活动之间导航。
1.2 在活动间导航
将想法归类为顶级活动、类别活动和详细信息/编辑活动之后,现在可以使用这些不同种类的活动确定如何在应用中导航。一般地,往往希望用户从顶级活动通过类别活动导航到详细信息/编辑活动。
- 顶级活动在最顶层:这些是用户最先看到的活动,所以他们位于最顶层。
- 类别活动位于顶层活动和详细信息/编辑活动之间:用户从顶层活动导航到类别活动。在复杂的应用汇总,可能会有多层类别和子类别活动。
- 详细信息/编辑活动:这些构成活动层次结构的最底层。用户将从类别活动导航到这些活动。
举例来说,假设用户想查看Starbuzz出售的某个饮品的详细信息。为此,他要启动应用,这会显示一个顶层活动开始屏幕,提供一个选项列表。接下来用户单击其中显示饮品清单的选项。要查看某个饮品的详细信息,他要在这个清单中单击选择的饮品。
2.需求实现
2.1 使用ListView导航到数据
用这种方式组织应用时,需要能够在活动之间导航。在这种情况下,一种常用的方法是使用列表视图。利用列表视图,可以显示一个数据列表来实现应用的导航。
需要知道的是 ListView中的每一项,实质都是 TextView.
因此,在获取了子项之后,可以通过 TextView类的getText()方法来获取他的文本。
例如,上一页我们说过,要有一个类别活动显示Starbuzz出售饮品的清单。这个活动可能如下所示:
这个活动使用一个列表视图显示Starbuzz出售的所有饮品。
要导航到一个特定的饮品,用户需要单击某个饮品,来显示这个饮品的详细信息。
我们并不会构建整个Starbuzz应用需要的全部类别和详细信息/编辑活动,这里只关注饮品。
实现分类:我们将构建用户启动应用时看到的顶级活动,另外会构建一个类别活动显示饮品清单,还要构建一个详细信息/编辑活动,显示某个饮品的详细信息。
顶级活动:
用户启动应用时,会显示顶层活动,也就是应用的主入口点。这个活动包括一个Starbuzz logo图像,还会显示一个导航列表,其中包含饮品(Drinks)、食物(Food)和分店(Stores)的入口。
用户单击列表中的一项时,应用会根据他选择的列表项导航到另外一个活动。例如,如果用户单击Drinks,应用就会启动关于饮品的类别活动。
饮料类别活动:
用户从顶级活动的导航列表选择Drinks时会启动这个活动,它会显示Starbuzz出售的所有饮品的一个清单。用户可以单击其中一个饮品,查看它的更多详细信息。
饮料详细信息活动:
用户点击饮品类别活动中列出的某个饮品时,会启动饮品活动。
这个活动会显示用户选择的那个饮品的详细信息,如饮品名、这种饮品的图片以及它的一个描述。
用户在应用中导航:
用户点击顶级活动中的“Drinks”时,会从顶级活动导航到饮品类别活动。单击某个饮品时会导航到饮品活动。
2.2 Starbuzz应用结构创建与具体步骤
这个应用包含3个活动。
- TopLevelActivity是应用的顶级活动,允许用户在应用中导航。
- DrinkCategoryActivity是一个类别活动,它包含所有饮品的一个列表。
- 第3个活动DrinkActivity会显示某个饮品的详细信息。
现在我们把饮品数据保存在一个Java类中。下一章中我们会把数据移到一个数据库中,不过目前我们的重点是如何构建这个应用的其余部分,暂不考虑数据库的有关知识。
1.应用启动时,它会启动活动TopLevelActivity
这个活动使用布局activity_top_level.xml。它会显示一个选项列表,其中包括Drinks、Food和Stores。
2.用户单击TopLevelActivity中的Drinks
这会启动活动DrinkCategoryActivity,显示一个饮品列表。
实现DrinkCategoryActivity时,不需要为它创建布局。这一章后面就会了解原因了。
3.饮品的详细信息保存在Drink.java类文件中
DrinkCategoryActivity从这个类得到饮品列表的值。
4.用户单击DrinkActivity中的一个饮品
这会启动活动DrinkActivity。这个活动使用布局activity_drink.xml。
5.DrinkActivity 从Drink.java类文件得到饮品的详细信息。
其中,Drink.java中的代码为:
package com.hfad.starbuzz;
public class Drink {
private String name;
private String description;
private int imageResourceId;
//static 静态数组 且为final 不可修改
public static final Drink[] drinks = {
new Drink("Latte", "A couple of espresso shots with steamed milk",
R.drawable.latte),
new Drink("Cappuccino", "Espresso, hot milk, and a steamed milk foam",
R.drawable.cappuccino),
new Drink("Filter", "Highest quality beans roasted and brewed fresh",
R.drawable.filter)
};
private Drink(String name, String description, int imageResourceId) {
this.name = name;
this.description = description;
this.imageResourceId = imageResourceId;
}
public String getDescription() {
return description;
}
public String getName() {
return name;
}
public int getImageResourceId() {
return imageResourceId;
}
public String toString() {
return this.name;
}
}
具体步骤:
构建这个应用需要完成以下步骤:
1.增加Drink类和图像资源。
这个类包含出售的饮品的详细信息,我们将在应用中使用这些饮品的图片和Starbuzz logo。
2.创建ToplevelActivity和它的布局。
这是这个应用的入口点。它要显示Starbuzz logo,另外包括一个用于导航的选项列表。TopLevelActivity要在用户单击Drink选项时启动DrinkCategoryActivity。
3.创建DrinkCategoryActivity。
DrinkCategoryActivity包含出售的所有饮品的一个列表。单击一个饮品时,它要启动DrinkCategory。
4.创建DrinkActivity和它的布局。
DrinkActivity会显示用户在Drinkcategory-Activity中单击的饮品的详细信息。
工程创建步骤见书N270.
2.3 利用监听器让ListView响应单击
可以实现一个事件监听器让列表视图中的项响应单击事件。
事件监听器允许监听应用中发生的事件,如单击视图,视图得到或失去焦点,或者用户按下设备上的一个按键。通过实现事件监听器,可以知道用户什么时候完成某个特定的动作(如单击一个列表视图中的一项),并做出响应。
onItemClickListener监听列表项的单击事件
如果希望一个列表视图中的列表项响应单击事件,需要创建一个onItemclickListener,并实现它的onItemClick()方法。OnItemclickListener会监听什么时候单击了列表项,可以在onItemclick()方法中指出活动如何响应这个单击事件。
onItemclick()方法有很多参数,可以使用这些参数找出单击了哪一项:
- 所单击的视图项的一个引用
- 它在列表视图中的位置(从0开始)
- 底层数据的行ID
我们希望在单击列表视图的第一项时启动DrinkCategoryActivity活动,这一项位于位置0(也就是第1个位置)。如果单击了位置0的列表项,要创建一个意图启动DrinkCategoryActivity。下面给出创建这个监听器的代码:
OnItemClickListener是一个内嵌在AdapterView类中的类。ListView是AdapterView的一个子类。
创建监听器后,要把它增加到ListView中。
为列表视图设置监听器
创建onclickItemListener后,要把它关联到列表视图。可以使用ListView setonItemclickListener()方法来实现。这个方法有一个参数,就是监听器自身:
为列表视图增加监听器很重要,因为有了这一步才会在用户单击列表视图中的列表项时通知监听器。如果没有完成这一步,列表视图中的项就不能响应单击事件。
运行代码时会发生什么
完整的TopLevelActivity代码为:
public class TopLevelActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_top_level);
//创建事件监听器
AdapterView.OnItemClickListener itemClickListener=new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
switch (position){
case 0:{
Intent intent=new Intent(TopLevelActivity.this,DrinkCategoryActivity.class);
startActivity(intent);
}
}
}
};
//为ListView设置 事件监听器、
ListView listView=(ListView)findViewById(R.id.list_options);
listView.setOnItemClickListener(itemClickListener);
}
}
问:要让ListView中的列表项响应单击事件,为什么要创建一个事件监听器?不能直接在布局代码中使用它的android:onClick属性吗?
答:只能在按钮(或者Button的子类视图,如Check-Box和Radio-Button)的活动布局中使用android:onclick属性。Listview类不是Button的子类,所以不能使用android:onclick属性。正因如此,必须实现你自己的事件监听器。
2.4 类别活动显示一个类别的数据--列表活动
前面我们说过,DrinkCategoryActivity是类别活动的一个例子。类别活动会显示属于某个特定类别的数据,通常显示在一个列表中。然后使用这个类别活动导航到数据的详细信息。
我们将使用DrinkCategoryActivity显示一个饮品列表。用户单击其中一个饮品时,我们会为用户显示这个饮品的详细信息。
为此,我们将创建一个活动,它包含一个列表视图,显示所有饮品的一个列表。由于这个活动只需要包含一个列表视图,而没有其他GUI组件,所以可以使用一种特殊类型的活动,称为列表活动。
那么什么是列表活动呢?
列表活动是专门处理列表的一种活动。它会自动绑定一个列表视图,所以无需你自行创建。下面给出一个列表活动:
ListActivity的类继承关系:
ListActivity是一种专门处理ListView的Activity。它有一个默认布局,包含这个ListView。
使用列表活动来显示不同类别的数据有几个主要优点:
- 你不需要创建自己的布局。
列表活动会自动定义它自己的布局,所以你不需要创建或维护XML布局。列表活动生成的布局包括一个列表视图。可以使用列表活动的getListview()方法访问这个列表视图。需要这个列表视图来指定显示什么数据。
- 不需要实现你自己的事件监听器。
ListActivity类已经实现了一个事件监听器,会监听什么时候点击了列表视图中的列表项。你不需要创建你自己的事件监听器并把它绑定到列表视图,只需要实现列表活动的onListItemclick()方法。这样可以更容易地让活动对用户点击列表视图中的列表项做出响应。后面会使用onListItemclick()方法启动另一个活动,可以从中了解如何响应点击列表项。
类别活动通常需要显示一个列表视图,可以用来导航到详细记录,所以这种情况很适合使用列表活动。
如何创建列表活动:
创建的活动不需要附带创建布局文件,然后将所创建的活动,扩展的类修改为ListActivity<AS提示已经过期>.
public class DrinkCategoryActivity extends ListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
另一个区别是,你不需要用setContentview()方法指定这个列表活动使用哪个布局。这是因为列表活动会定义自己的布局,所以无需你自行创建布局。列表活动会为你处理布局的有关问题。
与普通的活动一样,列表活动也需要在AndroidManifest.xml文件中注册,这样才能在应用中使用这些列表活动。创建活动时,Android Studio会为你完成这个注册工作。
2.5 适配器-非静态数据要使用适配器
android:entries可以使围strings.xml中的静态数组数据。
创建第一个活动TopLevelActivity时,可以在布局XML中使用android:entries属性将数据绑定到列表视图。之所以可以这么做,是因为数据保存为一个静态字符串数组资源。这个数组在strings..xml中描述,所以可以很容易地使用以下代码引用这个数组:
andorid:entries="@array/options"
这里options是字符串数组的名。
只有当数据是strings.xml中的一个静态数组时才能使用android:entries。
不过,如果不是这样一个静态数组会怎么样呢?如果数据保存在你在Java代码中通过编程创建的一个数组中,或者保存在一个数据库中,会怎么样?这种情况下就无法使用android:entries属性。
如果需要将列表视图与字符串数组资源以外的其他数据源中的数据绑定,需要采用另外一种不同的方法,要编写活动代码来绑定数据。在这里,我们需要将列表视图与Drink类中的drinks数组绑定。
非静态数据要使用适配器
如果要在列表视图中显示来自一个非静态数据源的数据,如一个Java数组或数据库,就需要使用适配器。适配器就相当于数据源与列表视图之间的一座桥:
适配器在列表视图和数据源之间搭建了一个桥梁。适配器允许类别视图显示各种不同数据源的数据。
有很多不同类型的适配器。现在我们主要关心数组适配器。
要初始化数组适配器,首先指定与列表视图绑定的数组中包含什么类型的数据。然后传入3个参
- 一个context(通常是当前活动)
- 一个布局资源(指定如何显示数组中的各个项)
- 数组本身
下面给出创建数组适配器的代码,它会显示Drink.drinks数组中的Drink数据:
其中第二项参数是内置的布局资源,它告诉数组适配器在文本视图中显示数组中的各项。
然后使用ListView setAdapter()方法将这个数组适配器与列表视图关联:
ListView listView=getListView();
listView.setAdapter(listAdapter);
在底层,数组适配器取数组中的各个项,用tostring()方法将它转换为一个string,再把各个结果分别放在一个文本视图中。然后把各个文本视图显示为列表视图中的一行。
为DrinkCategoryActivity增加数组适配器的代码:
我们要修改DrinkCategoryActivity.java代码,让列表视图使用一个数组适配器从Drink类得到饮品数据。这个代码要放在onCreate()方法中,这样活动创建时就会填充列表视图。
public class DrinkCategoryActivity extends ListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ListView listDrinks=getListView();
ArrayAdapter<Drink> listAdapter=new ArrayAdapter<>(
this,
android.R.layout.simple_list_item_1,
Drink.drinks);
listDrinks.setAdapter(listAdapter);
}
}
2.6 处理点击
在对于处理顶层活动中,TopLevelActivity处理单击时间,是创建了一个事件监听器OnItemClickListener,然后实现它的onItemClick()方法,并未列表视图指定这个监听器:
必须以这种方式建立一个时间监听器,因为列表视图不能像按钮那样以一种内置的方式相应单击时间。
在类别活动DrinkCategoryActivity中处理用户单击时间,ListActivity会默认实现一个列表项单击事件监听器。
TopLevelActivity与DrinkCategoryActivity之间有一个显著的区别。TopLevelActivity是一个普通的Activity对象,而DrinkCategoryActivity是一个ListActivity,这种特殊类型的活动专门设计用来处理列表视图。
处理用户单击事件时,这一点很重要。Activity和ListActivity间的一个重要区别是ListActivity类已经实现了一个项单击事件监听器。无需创建你自己的事件监听器,使用列表活动时,你只需要实现onListItemClick()方法。
使用ListActivity onListItemClick()方法向活动传递数据:
使用列表活动显示类别时,通常会使用onListItem-click()方法启动另一个活动,来显示用户单击的项的详细信息。为此,要创建一个意图启动第二个活动。然后增加单击项的ID作为额外信息,这样第二个活动启动时就可以使用这个信息。
在这里,我们希望启动DrinkActivity,并传入选择的饮品的ID。DrinkActiyity就可以使用这个信息来显示相应饮品的详细信息。下面给出这个方法的代码:
在这里,我们希望启动DrinkActivity,并传入选择的饮品的ID。DrinkActiyity就可以使用这个信息来显示相应饮品的详细信息。下面给出这个方法的代码:
常见的做法是传递单击列表项的ID,这正是底层数据的ID。
- 如果底层数据是一个数组,这个ID就是这一项在数组中的索引。
- 如果底层数据来自一个数据库,ID则是数据库表中这个记录的ID。
通过这种方式传递数据项的ID,这意味着第二个活动可以更容易地得到数据的详细信息,然后显示这些信息。
这样一来,就能让DrinkcategoryActivity启动DrinkActivity,并告诉它选择了哪个饮品。下一页会给出完整的活动代码。
public class DrinkCategoryActivity extends ListActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ListView listDrinks=getListView();
ArrayAdapter<Drink> listAdapter=new ArrayAdapter<>(
this,
android.R.layout.simple_list_item_1,
Drink.drinks);
listDrinks.setAdapter(listAdapter);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
Intent intent=new Intent(DrinkCategoryActivity.this,DrinkActivity.class);
intent.putExtra(DrinkActivity.EXTRA_DRINKNO,(int)id);
startActivity(intent);
}
}
2.7 详细信息/编辑活动
前面说过,DrinkActivity是一个详细信息活动的例子。详细信息活动会显示某个特定记录的详细信息,通常会从一个类别活动导航到详细信息活动。
我们要用DrinkActivity显示用户选择的饮品的详细信息。Drink类包括饮品名、描述和图像资源ID,所以我们会在布局中显示这些数据。下面将为饮品图像资源增加一个图像视图,另外为饮品名和描述增加两个文本视图。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.hfad.starbuzz.DrinkActivity" >
<ImageView android:id="@+id/photo"
android:layout_width="190dp"
android:layout_height="190dp" />
<TextView android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
从意图获取数据:
你已经看到,让类别活动启动一个详细信息活动时,要让类别活动列表视图中的列表项响应单击事件。单击一个列表项时,创建一个意图来启动详细信息活动。将用户单击的项的ID作为额外信息传递给意图。
启动详细信息活动时,这个详细信息活动可以从意图获取额外的信息,用它填充其中的视图。在这里,我们可以利用意图中启动DrinkActivity的有关信息,获取用户单击的饮品的详细信息。
创建DrinkCategoryActivity时,将用户单击的饮品的ID作为额外信息增加到意图中。为它指定一个标签DrinkActivity.EXTRA_ DRINKNO,要在DrinkActivity中把它定义为一个常量:
public static final String EXTRA_DRINKNO="drinkNo";
在第3章已经看到,可以使用getIntent()方法获取启动一个活动的意图。如果这个意图包含额外的信息,可以使用意图的get*()方法来获取这些信息。下面的代码可以从启动DrinkActivity的意图获取EXTRA _ DRINKNo值:
public class DrinkActivity extends AppCompatActivity {
public static final String EXTRA_DRINKNO="drinkNo";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_drink);
//从意图中获取指定Drink
int drinkNo=(Integer)getIntent().getExtras().get(EXTRA_DRINKNO);
Drink drink=Drink.drinks[drinkNo];//使用drinkNo得到用户选择的音频的相信信息
//添加 drink 图片 的数据
ImageView photo = (ImageView) findViewById(R.id.photo);
photo.setImageResource(drink.getImageResourceId());
photo.setContentDescription(drink.getName());
//添加 drink 名称 的数据
TextView name = (TextView) findViewById(R.id.name);
name.setText(drink.getName());
//添加 drink 描述 的数据
TextView description = (TextView) findViewById(R.id.description);
description.setText(drink.getDescription());
}
}