2.3 多个Activity
Android开发秘籍(第2版)
就算是最简单的应用程序也会拥有不止一项功能,因此我们经常要应对多个Activity。例如,一款游戏可能含有两个Activity,其一为高分排行榜,另一为游戏画面。一个记事本可以有三个Activity:浏览笔记列表、阅读选定笔记、编辑选定的或新建的笔记。
AndroidManifest.xml文件中定义的主Activity会随应用程序启动而启动。该Activity可以开启另外的Activity,通常由触发事件引起。这第二个Activity被激活时,主Activity会暂停。当第二个Activity结束时,主Activity会被重新调回前台恢复运行。
要想激活应用中的某个特定组件,可以用显式命名该组件的Intent来实现。而应用程序的所需可以通过Intent过滤器指定,这时可采用隐式Intent。系统可随即确定最合适的某个或某组组件,不管它是属于另外的应用还是系统自带的。注意,与其他Activity不同,位于其他应用程序中的隐式Intent不需要在当前应用的AndroidManifest.xml文件中声明。
Android尽可能选用隐式Intent,它能为模块化功能提供强大的框架。当一个符合所需的隐式Intent过滤器要求的新组件开发完成,它就可以替代Android内部的Intent。举个例子,假如一个用来显示电话联系人的新应用被装入到Android设备,当用户选择联系人时,Android系统会找出符合浏览联系人这一Intent过滤器要求的所有可用的Activity,并询问用户想使用哪一个。
技巧9:使用按钮和文本视图
触发器事件有助于全面展示多Activity特性。为此我们引入一个按钮(button)按下动作,为给定的布局添加按钮并为其指派动作的步骤如下。
(1)为指定的布局XML文件添加一个按钮控件:
<Button android:id="@+id/trigger"
android:layout_width="100dip" android:layout_height="100dip"
android:text="Press this button" />
(2)声明一个指向布局文件中的按钮ID的按钮:
Button startButton = (Button) findViewById(R.id.trigger);
(3)为按钮点击事件指定一个监听器(listener):
//Set up button listener
startButton.setOnClickListener(new View.OnClickListener() {
//Insert onClick here
});
(4)重写监听器的onClick函数以执行要求的动作:
public void onClick(View view) {
// Do something here
}
为展示动作的效果,改变屏幕上的文字不失为一招。定义文本域并通过编程对其进行改动的步骤如下:
(1)用一个ID为指定的布局XML文件添加文本域,该文本域可以有初始值(此处可用strings.xml中定义的hello字符串初始化它)。
<TextView android:id="@+id/hello_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
(2)声明一个指向布局文件中TextView ID的TextView(文本视图):
private TextView tv = (TextView) findViewById(R.id.hello_text);
(3)如果需要变更文本,可使用setText函数:
tv.setText("new text string");
以上两项UI技术会在本章后续的一些技巧中用到。对于UI技术更详细的讲解请参见第5章。
技巧10:通过事件启动另外一个Activity
本技巧中MenuScreen是主Activity,如代码清单2-9所示,它会开启名为PlayGame的Activity。此处,触发器事件是作为按钮点击、用Button微件实现的。
当用户点击按钮,startGame()函数会运行,并启动PlayGame Activity。当用户点击PlayGame Activity中的按钮时,它会调用finish()函数将控制权交还给调用它的Activity。下面是启动Activity的步骤。
(1)声明一个指向要启动的Activity的Intent。
(2)在该Intent上调用startActivity方法。
(3)在AndroidManifest.xml中对这一额外的Activity加以声明。
代码清单2-9 src/com/cookbook/launch_activity/MenuScreen.java
package com.cookbook.launch_activity;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class MenuScreen extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//Set up button listener
Button startButton = (Button) findViewById(R.id.play_game);
startButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
startGame();
}
});
}
private void startGame() {
Intent launchGame = new Intent(this, PlayGame.class);
startActivity(launchGame);
}
}
在匿名内部类里提供当前上下文环境
注意,通过点击按钮启动Activity时,还有一些东西需要考虑,如代码清单2-9显示的那样,Intent需要一个上下文环境。然而,在onClick函数里使用this引用并不是个稳妥的解决办法。下面给出通过匿名内部类来提供当前上下文环境的几种不同方法。
使用Context.this代替this。
使用getApplicationContext()代替this。
显式地使用类名MenuScreen.this。
调用一个在合适的上下文级别中声明的函数。在代码清单2-8的startGame()中使用的就是这个方法。
这些方法通常是可以互换的,可依照具体情况选择最好的方法。
代码清单2-10中给出的PlayGame Activity只不过是一个按钮,带有一个会调用finish()函数把控制权交还给主Activity的onClick监听器。可以按需给该Activity添加更多的功能,各个代码分支可以导致各自不同的finish()调用。
代码清单2-10 src/com/cookbook/launch_activity/PlayGame.java
package com.cookbook.launch_activity;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
public class PlayGame extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.game);
//Set up button listener
Button startButton = (Button) findViewById(R.id.end_game);
startButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
finish();
}
});
}
}
按钮必须像代码清单2-11所示的那样添加到main布局中,其ID应为play_game,以与代码清单2-9中的设定匹配。此处,按钮的大小也以设备独立/无关像素(dip)1 **的方式声明,该方式会在第5章中进行更多讨论。
代码清单2-11 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="match_parent"
android:layout_height="match_parent"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
<Button android:id="@+id/play_game"
android:layout_width="100dip" android:layout_height="100dip"
android:text="@string/play_game"
/>
</LinearLayout>
PlayGame Activity引用它自己的按钮ID——end_game,它位于布局资源R.layout.game中,R.layout.game又对应名为game.xml的XML文件,如代码清单2-12所示。
代码清单2-12 res/layout/game.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button android:id="@+id/end_game"
android:layout_width="100dip" android:layout_height="100dip"
android:text="@string/end_game" android:layout_gravity="center"
/>
</LinearLayout>
尽管在各种情况下文本都可以显式地写在代码中,但更好的编码习惯是为每个字符串定义相应变量。本技巧里,名为play_game和end_game的两个字符串需要在字符串资源文件中分别定义,如代码清单2-13所示。
代码清单2-13 res/values/strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello">This is the Main Menu</string>
<string name="app_name">LaunchActivity</string>
<string name="play_game">Play game?</string>
<string name="end_game">Done?</string>
</resources>
最终,在AndroidManifest.xml文件里需要为PlayGame这个新类注册一个默认动作,如代码清单2-14所示。
代码清单2-14 AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0" package="com.cookbook.launch_activity">
<application android:icon="@drawable/icon"
android:label="@string/app_name">
<activity android:name=".MenuScreen"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".PlayGame"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="3" />
</manifest>
技巧11:通过使用语音转文本功能启动一个Activity
本技巧演示了如何调用一个Activity以获取其返回值,还演示了如何使用Google的RecognizerIntent中的语音转文本功能,并将转换结果输出到屏幕上。这里采用按钮点击作为触发事件,它会启动RecognizerIntent Activity,后者对来自麦克风的声音进行语音识别,并将其转换为文本。转换结束时,文本会被传递回调用RecognizerIntent的Activity。
返回时,首先会基于返回的数据调用onActivityResult()函数,然后会调用onResume()函数使Activity正常继续。调用的Activity可能会出现问题而不能正确返回,因此,在解析返回的数据之前,应当始终检查resultCode确保返回值为RESULT_OK。
注意,一般来讲启动任何会返回数据的Activity都将导致同一个onActivityResult()函数被调用。因此,要使用一个请求代号来辨别是哪个Activity在返回数据。当被启动的Activity结束时,它会将控制权交还给调用它的Activity,并使用相同的请求代码调用onActivityResult()。
调用Activity获取返回值的步骤如下:
(1)用一个Intent调用startActivityForResult()函数,定义被启动的Activity及一个起识别作用的requestCode变量。
(2)重写onActivityResult()函数,检查返回结果的状况,检查所期望的requestCode,并解析返回的数据。
下面是使用RecognizerIntent的步骤:
(1)声明一个动作为ACTION_RECOGNIZE_SPEECH的Intent。
(2)为该Intent传递附加内容,至少EXTRA_LANGUAGE_MODEL是必需的,它可以被设置成LANGUAGE_MODEL_FREE_FORM 或者LANGUAGE_MODEL_WEB_SEARCH。
(3)返回的数据包中包含可能与原始文本匹配的字符串的列表。使用data.getStringArrayListExtra检索这一数据,它将在稍后以ArrayList的形式传送给用户。
返回的文本用一个TextView显示。主Activity在代码清单2-15中给出。
所需的支持文件还有main.xml和strings.xml,其中需要定义一个按钮以及用于存放结果的TextView,这可以借助技巧10中的代码清单2-11和2-13来实现。AndroidManifest.xml文件中只需要声明主Activity,这与前面的技巧1相同。RecognizerIntent Activity是Android系统原生的Activity,不需要显式声明即可使用。
代码清单2-15 src/com/cookbook/launch_for_result/RecognizerIntent Example.java
package com.cookbook.launch_for_result;
import java.util.ArrayList;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.speech.RecognizerIntent;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class RecognizerIntentExample extends Activity {
private static final int RECOGNIZER_EXAMPLE = 1001;
private TextView tv;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tv = (TextView) findViewById(R.id.text_result);
//Set up button listener
Button startButton = (Button) findViewById(R.id.trigger);
startButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
// RecognizerIntent prompts for speech and returns text
Intent intent =
new Intent(RecognizerIntent. ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent. LANGUAGE_MODEL_FREE_FORM);
intent.putExtra(RecognizerIntent.EXTRA_PROMPT,
"Say a word or phrase\nand it will show as text");
startActivityForResult(intent, RECOGNIZER_EXAMPLE);
}
});
}
@Override
protected void onActivityResult(int requestCode,
int resultCode, Intent data) {
//Use a switch statement for more than one request code check
if (requestCode==RECOGNIZER_EXAMPLE && resultCode==RESULT_OK) {
// Returned data is a list of matches to the speech input
ArrayList<String> result =
data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
//Display on screen
tv.setText(result.toString());
}
super.onActivityResult(requestCode, resultCode, data);
}
}
技巧12:实现选择列表
应用程序中常常需要提供给用户一个选择列表,供用户点选。这一功能利用ListActivity可以轻松地实现。ListActivity是Activity的一个子类,它会根据用户选择触发事件。
下面是创建选择列表的步骤。
(1)创建一个扩展ListActivity而不是Activity的类。
public class ActivityExample extends ListActivity {
//content here
}
(2)创建一个存储各个选项名称的字符串数组:
static final String[] ACTIVITY_CHOICES = new String[] {
"Action 1",
"Action 2",
"Action 3"
};
(3)以ArrayAdapter为参数调用setListAdapter(),为其指定选择列表及一个布局:
setListAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, ACTIVITY_CHOICES));
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
getListView().setTextFilterEnabled(true);
(4)启动OnItemClickListener以确定选中了哪个选项,并做出对应的动作:
{
@Override
public void onItemClick(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
switch(arg2) {//Extend switch to as many as needed
case 0:
//code for action 1
break;
case 1:
//code for action 2
break;
case 2:
//code for action 3
break;
default: break;
}
}
});
这一技术在下一个技巧中也会用到。
技巧13:使用隐式Intent创建Activity
隐式Intent不需要指定要使用哪个组件。相反,它们通过过滤器指定所需的功能,而Android系统必须决定使用哪个组件是最佳选择。Intent过滤器可以是动作(action)、数据(data)或者分类(category)。
最常用的Intent过滤器是动作,而其中最常用的要属ACTION_VIEW。该模式需要指定一个统一资源标识符(URI),从而将数据显示给用户。它为给定的URI执行最合理的动作。比如,在下面的例子中,case 0、case 1、case 2中的隐式Intent拥有相同的语法,却产生不同的结果。
下面是使用隐式Intent启动Activity的具体步骤。
(1)声明Intent,同时指定合适的过滤器(如ACTION_VIEW、ACTION_WEB_SEARCH等)。
(2)为运行Activity所需的该Intent附加额外的信息。
(3)将该Intent传递给startActivity()方法。
代码清单2-16 src/com/cookbook/implicit_intents/ListActivityExample.java
package com.cookbook.implicit_intents;
import android.app.ListActivity;
import android.app.SearchManager;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.AdapterView.OnItemClickListener;
public class ListActivityExample extends ListActivity {
static final String[] ACTIVITY_CHOICES = new String[] {
"Open Website Example",
"Open Contacts",
"Open Phone Dialer Example",
"Search Google Example",
"Start Voice Command"
};
final String searchTerms = "superman";
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setListAdapter(new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,
ACTIVITY_CHOICES));
getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
getListView().setTextFilterEnabled(true);
getListView().setOnItemClickListener(new OnItemClickListener()
{
@Override
public void onItemClick(AdapterView<?> arg0, View arg1,
int arg2, long arg3) {
switch(arg2) {
case 0: //opens web browser and navigates to given website
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse("http://www.android.com/")));
break;
case 1: //opens contacts application to browse contacts
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse("content://contacts/people/")));
break;
case 2: //opens phone dialer and fills in the given number
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse("tel:12125551212")));
break;
case 3: //searches Google for the string
Intent intent= new Intent(Intent.ACTION_WEB_SEARCH);
intent.putExtra(SearchManager.QUERY, searchTerms);
startActivity(intent);
break;
case 4: //starts the voice command
startActivity(new
Intent(Intent.ACTION_VOICE_COMMAND));
break;
default: break;
}
}
});
}
}
技巧14:在Activity间传递基本数据类型
有时需要向某个启动的Activity传递数据,有时启动的Activity需要把其创建的数据传回给调用它的Activity。例如,需要把游戏的最终得分返回给高分排行榜界面。以下是在Activity之间传递信息的几种不同方式。
在发起调用的Activity中声明相关变量(如public int finalScore),并在启动的Activity中为其赋值(例如:CallingActivity finalScore=score)。
给bundle附加额外数据(在本技巧中有所体现)。
使用Preference属性存储数据,以备后面检索(将在第6章中介绍)。
使用SQLite数据库储存数据,以备后面检索(将在第11章中介绍)。
Bundle是从字符串值到各种可打包(parcelable)类型的映射,可以通过向Intent添加额外数据创建它。本技巧显示了将数据从主Activity传递给启动的Activity,在其中修改后再传递回来的全过程。
变量(本例中一个为integer型,另一个为String型)在StartScreen Activity中定义。在创建Intent调用PlayGame类时,通过putExtra方法把这两个变量附加给Intent。当结果从启动的Activity中返回时,可借助getExtras方法读取变量值。以上调用过程如代码清单2-17所示。
代码清单2-17 src/com/cookbook/passing_data_activities/StartScreen.java
package com.cookbook.passing_data_activities;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class StartScreen extends Activity {
private static final int PLAY_GAME = 1010;
private TextView tv;
private int meaningOfLife = 42;
private String userName = "Douglas Adams";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
tv = (TextView) findViewById(R.id.startscreen_text);
//Display initial values
tv.setText(userName + ":" + meaningOfLife);
//Set up button listener
Button startButton = (Button) findViewById(R.id.play_game);
startButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
startGame();
}
});
}
@Override
protected void onActivityResult(int requestCode,
int resultCode, Intent data) {
if (requestCode == PLAY_GAME && resultCode == RESULT_OK) {
meaningOfLife = data.getExtras().getInt("returnInt");
userName = data.getExtras().getString("returnStr");
//Show it has changed
tv.setText(userName + ":" + meaningOfLife);
}
super.onActivityResult(requestCode, resultCode, data);
}
private void startGame() {
Intent launchGame = new Intent(this, PlayGame.class);
//passing information to launched activity
launchGame.putExtra("meaningOfLife", meaningOfLife);
launchGame.putExtra("userName", userName);
startActivityForResult(launchGame, PLAY_GAME);
}
}
传入PlayGame Activity的变量可以用getIntExtra和getStringExtra读取。当该Activity结束并准备通过一个Intent返回时,可以用putExtra方法将数据传回给发起调用的Activity。上述调用如清单2-18所示。
代码清单2-18 src/com/cookbook/passing_data_activities/PlayGame.java
package com.cookbook.passing_data_activities;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class PlayGame extends Activity {
private TextView tv2;
int answer;
String author;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.game);
tv2 = (TextView) findViewById(R.id.game_text);
//reading information passed to this activity
//Get the intent that started this activity
Intent i = getIntent();
//returns -1 if not initialized by calling activity
answer = i.getIntExtra("meaningOfLife", -1);
//returns [] if not initialized by calling activity
author = i.getStringExtra("userName");
tv2.setText(author + ":" + answer);
//Change values for an example of return
answer = answer - 41;
author = author + " Jr.";
//Set up button listener
Button startButton = (Button) findViewById(R.id.end_game);
startButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
//将信息返回给发起调用的activity
Intent i = getIntent();
i.putExtra("returnInt", answer);
i.putExtra("returnStr", author);
setResult(RESULT_OK, i);
finish();
}
});
}
}
1设备独立/无关像素(device-independent pixels),简写为dip或dp,是Android为方便跨不同屏幕类型的设备的编程而推出的一种虚拟像素单位,用于定义应用的UI,以密度无关的方式表达布局尺寸或位置。在运行时,Android根据使用中的屏幕的实际密度,透明地处理任何所需dip单位的缩放。在第5章的表5.1中也有涉及。——译者注