《Android开发秘籍(第2版)》——第2.3节多个Activity

    xiaoxiao2024-01-24  191

    本节书摘来自异步社区《Android开发秘籍(第2版)》一书中的第2章,第2.3节多个Activity,作者 【美】Ronan Schwarz , Phil Dutson , James Steele , Nelson To,更多章节内容可以访问云栖社区“异步社区”公众号查看

    2.3 多个ActivityAndroid开发秘籍(第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中也有涉及。——译者注

    最新回复(0)