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

  1. 云栖社区>
  2. 博客>
  3. 正文

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

异步社区 2017-05-02 11:10:00 浏览1299
展开阅读全文

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

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中也有涉及。——译者注

网友评论

登录后评论
0/500
评论
异步社区
+ 关注