直接为之前的GeoQuiz应用添加第二个activity。
第一个activity控制一屏信息,新activity将带来第二个用户界面,方便用户偷看当前问题的答案。
一.创建第二个 activity
创建新的activity至少涉及三个文件:Java类、XML布局和应用的manifest文件。使用Android stdio的新建activity向导功能。
在activity_cheat.xml创建布局XML文件,代码如下:
<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"
android:gravity="center"
tools:context="com.bignerdranch.android.geoquiz.CheatActivity">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="24dp"
android:text="@string/warning_text"/>
<TextView android:id="@+id/answer_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="24dp"
tools:text="Answer"/>
<Button android:id="@+id/show_answer_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/show_answer_button"/>
</LinearLayout>
在manifest配置文件中声明 activity 。manifest配置文件是一个包含元数据的XML文件,用来向Android操作系统描述应用。
这里记下一个小技巧:使用Android Studio的快速打开文件功能---使用Command+Shift+O(或Ctrl+Shift+N)快捷键,呼出快速打开对话框,利用提示功 能或直接输入目标文件名,按回车键打开。
应用的所有activity都必须在manifest配置文件中声明,这样操作系统才能够找到它们。
现在在manifest文件中已经可以看到新建activity的声明:
<activity android:name=".CheatActivity"></activity>
这样之后直接在layout/activity_quiz.xml和layout-land/activity_quiz.xml布局文件中定义CHEAT新按钮就可以了。
<Button android:id="@+id/cheat_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cheat_button"/>
<Button android:id="@+id/cheat_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal "
android:text="@string/cheat_button" />
最后为CHEAT按钮添 加View.onClickListener监听器代码:
mCheatButton = (Button)findViewById(R.id.cheat_button);
mCheatButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Start CheatActivity } });
二.启动activity
public void startActivity(Intent intent)
activity调用startActivity(Intent)方法时,调用请求实际发给了操作系统。 准确地说,调用请求发送给了操作系统的ActivityManager。ActivityManager负责创建 Activity实例并调用其onCreate(Bundle)方法。
intent对象是component用来与操作系统通信的一种媒介工具。
public Intent(Context packageContext, Class<?> cls) 传入该方法的Class类型参数告诉ActivityManager应该启动哪个activity;Context参数告 诉ActivityManager在哪里可以找到它。如图:
在启动activity前,ActivityManager会确认指定的Class是否已在manifest配置文件中声明。 如果已完成声明,则启动activity,应用正常运行。反之,则抛出ActivityNotFoundException 异常,应用崩溃。这就是必须在manifest配置文件中声明应用的全部activity的原因。
显式intent与隐式intent
如果通过指定Context与Class对象,然后调用intent的构造方法来创建Intent,则创建的是显式intent。在同一应用中,我们使用显式intent来启动activity。
同一应用里的两个activity,却要借助于应用外部的ActivityManager通信,这似乎有点怪。不过,这种模式会让不同应用间的activity交互变得容易很多。
一个应用的activity如需启动另一个应用的activity,可通过创建隐式intent来处理。
三.activity间的数据传递
例如本例中
CheatActivity启动后,QuizActivity会通知它当前问题的答案。 用户知道答案后,点击后退键回到QuizActivity,CheatActivity随即被销毁。在销毁前 的瞬间,它会将用户是否作弊的数据传递给QuizActivity。
1. 从QuizActivity到CheatActivity的数据传递
为通知CheatActivity当前问题的答案,需将以下语句的返回值传递给它: mQuestionBank[mCurrentIndex].isAnswerTrue()
该值将作为extra信息,附加在传入startActivity(Intent)方法的Intent上发送出去。
extra信息可以是任意数据,它包含在Intent中,由启动方activity发送出去。可以把extra信息想象成构造函数参数。接受方activity接收到操作系统转发的intent后,访问并获取其中的 extra数据信息。
如图:
extra是一种键值结构。要将extra数据信息添加给intent,调用:
public Intent putExtra(String name, boolean value)
这其中一个参数是固定为 String类型的键,另一个参数是键值,可以是多种数据类型。该方法返回intent自身。
现在,可以返回到QuizActivity,将extra附加到intent上。不过有个更好的实现方法。 对于CheatActivity处理extra信息的实现细节,QuizActivity和应用的其他代码无需知道。因此,我们可转而在newIntent(...)方法中封装这些逻辑。
CheatActivity.java中:
public static Intent newIntent(Context packageContext, boolean answerIsTrue) {
Intent intent = new Intent(packageContext, CheatActivity.class);
intent.putExtra(EXTRA_ANSWER_IS_TRUE, answerIsTrue);
return intent;
QuizActivity.java中:
boolean answerIsTrue = mQuestionBank[mCurrentIndex].isAnswerTrue();
Intent intent = CheatActivity.newIntent(QuizActivity.this, answerIsTrue);
获取extra信息:
mAnswerIsTrue = getIntent().getBooleanExtra(EXTRA_ANSWER_IS_TRUE, false);
最后在CheatActivity.java中提供作弊机会:
mAnswerIsTrue = getIntent().getBooleanExtra(EXTRA_ANSWER_IS_TRUE, false);
mAnswerTextView = (TextView) findViewById(R.id.answer_text_view);
mShowAnswerButton= (Button) findViewById(R.id.show_answer_button);
mShowAnswerButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mAnswerIsTrue) {
mAnswerTextView.setText(R.string.true_button);
} else {
mAnswerTextView.setText(R.string.false_button);
}
}
});
下面接着来实现从QuizActivity中可以获知是否已经查看答案,也就是要实现从子activity中获取返回结果。
需要调用函数:
public void startActivityForResult(Intent intent, int requestCode)
该方法的第一个参数同前述的intent。第二个参数是请求代码。请求代码是先发送给子 activity,然后再返回给父activity的整数值,由用户定义。
1. 设置返回结果
实现子activity发送返回信息给父activity,有以下两种方法可用: public final void setResult(int resultCode)
public final void setResult(int resultCode, Intent data)
在父activity需要依据子activity的完成结果采取不同操作时,设置结果代码就非常有用。
2. 返还intent
GeoQuiz应用中,数据信息需要回传给QuizActivity。因此,我们需要创建一个Intent,附加上extra信息后,调用Activity.setResult(int, Intent)方法将信息回传给QuizActivity。
在CheatActivity代码中,为extra的键增加常量,再创建一个私有方法,用来创建intent、 附加extra并设置结果值。然后在SHOW ANSWER按钮的监听器代码中调用该方法。
private void setAnswerShownResult(boolean isAnswerShown) {
Intent data = new Intent();
data.putExtra(EXTRA_ANSWER_SHOWN, isAnswerShown);
setResult(RESULT_OK, data);
}
现如今应用内部的交互时序如图:
3. 处理返回结果
在QuizActivity.java中,新增一个成员变量保存CheatActivity回传的值。然后覆盖 onActivityResult(...)方法获取它。
最后,修改QuizActivity中的checkAnswer(boolean)方法,确认用户是否偷看答案并作出相应的反应。
activity的使用与管理。
来看看在各activity间往返的时候,操作系统层面到底发生了什么。首先,在桌面启动器中点击GeoQuiz应用时,操作系统并没有启动应用,而只是启动了应用中的一个activity。确切地说,它启动了应用的launcher activity。在GeoQuiz应用中,QuizActivity就是它的launcher activity。