一、Android开发初体验
开发一个应用,名叫GeoQuiz,它能给出一道道地理知识问题,用户点击TRUE或FALSE来回答屏幕上的问题,GeoQuiz会及时给出反馈,应用界面如下图所示。
应用由一个activity和一个布局(layout)组成。按照Android studio提示新建一个普通的项目,activity name设置为”QuizActivity”(当然名字可以随便取,不过为了具有辨识度我们还是根据activity的作用来取),layout名”activity_quiz”。修改默认的布局文件如下:
1 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
添加字符串资源(strings.xml文件)。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<resources>
<string name="app_name">GeoQuiz</string>
<string name="question_australia">Canberra is the capital of Australia.</string>
<string name="question_oceans">The Pacific Ocean is large than the Atlantic Ocean.</string>
<string name="question_mideast">The suez is Canal connects the Red Sea and the Indian Ocean.</string>
<string name="question_africa">The source of the Nile River is in Egypt.</string>
<string name="question_americas">The amazon River is the longest river in the Americas.</string>
<string name="question_asia">Lake Baikal is the world\'s oldest and deepest freshwater lake.</string>
<string name="warning_text">Are you sure you want to do this?</string>
<string name="show_answer_button">Show Answer</string>
<string name="cheat_button">Cheat!</string>
<string name="judgment_toast">Cheating is wronging!</string>
<string name="true_button">True</string>
<string name="false_button">False</string>
<string name="correct_toast">Correct!</string>
<string name="next_button">Next!</string>
<string name="incorrect_toast">Incorrect!</string>
</resources>
布局文件中,LinearLayout里嵌有一个LinearLayout,外层LinearLayout是垂直排列,里层是水平排列,在水平排列的布局中,放置了两个Button,用来选择答案的true或false。
2,从XML布局文件到视图对象
setContentView(R.layout.activity_quiz);这行代码生成指定的视图并将其放在屏幕上。布局是一种资源,资源则是非代码形式的内容,图像文件、音频、xml等都是资源。布局文件放在app/res子目录下,activity_quiz.xml文件放在res/layout/目录下,strings.xml在res/values/目录下。可以使用资源ID在代码中获取相应的资源,activity_quiz.xml布局的资源ID为R.layout.activity_quiz。
3,组件的实际应用
1),在QuizActivity中添加两个成员变量,按IDE提示导入包。
1 | private Button mTrueButton; |
2), 引用组件1
public View findViewById(int id);
以上代码用于引用已生成的组件,使用按钮的资源id获取视图对象,赋值给对应的成员变量,如下代码所示1
2mTrueButton = findViewById(R.id.true_button);
mFalseButton = findViewById(R.id.false_button);
3),设置监听器
Android应用属于典型的事件驱动类型,不像命令行或脚本程序,事件驱动应用启动后,即开始等待行为事件的发生,如用户点击某个按钮(事件也可以由操作系统或其他应用触发,但是用户触发的点击事件更直观,如点击按钮)。应用在等待某个特定事件的发生,可以说应用正在”监听”特定事件,为相应某个事件而创建的对象叫做监听器(listener)。监听器会实现特定事件的监听器接口(listener interface)。
Android Sdk为我们设置了很多的监听器接口,我们不需要自己实现,当前我们要监听用户的按钮”点击”事件,因此要实现View.OnClickListener接口。在QuizActivity中添加如下代码,并导入View类:1
2
3
4
5
6
7mTrueButton = findViewById(R.id.true_button);
mTrueButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//业务逻辑代码
}
});
这是一个匿名内部类实现了OnClickListener接口,使用匿名内部类有两大好处,第一,可以相对集中地实现监听器方法,便于观察。第二,事件监听器一般只在一个地方使用,使用匿名内部类,就不用再创建繁琐的命名类了。匿名内部类实现了OnClickListener接口,因此必须实现接口中唯一的onClick方法,onClick现在是空方法,至于如何实现这个方法,则取决于我们自己,同样为false button设置监听器。1
2
3
4
5
6
7mFalseButton = findViewById(R.id.false_button);
mFalseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//do something here
}
});
4),创建提示消息
创建一个toast提示消息,这是用来通知用户的简短弹出消息。用toast来反馈答案。首先在strings.xml中添加消息显示的字符串。
代码如下:1
2
3
4
5
6
7Toast.makeText(QuizActivity.this,
R.string.correct_toast,Toast.LENGTH_SHORT)
.show();
...
Toast.makeText(QuizActivity.this,
R.string.incorrect_
toast,Toast.LENGTH_SHORT).show();
运行程序,点击按钮会有toast提示。
二、Android与MVC设计模式
2.1 创建新类
新建Question类,新增两个成员变量和一个构造方法以及getter和setter方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class Question {
private int mTextResId;
private boolean mAnswerTrue;
public Question(int mTextResId, boolean mAnswerTrue) {
this.mTextResId = mTextResId;
this.mAnswerTrue = mAnswerTrue;
}
public int getmTextResId() {
return mTextResId;
}
public void setmTextResId(int mTextResId) {
this.mTextResId = mTextResId;
}
public boolean ismAnswerTrue() {
return mAnswerTrue;
}
public void setmAnswerTrue(boolean mAnswerTrue) {
this.mAnswerTrue = mAnswerTrue;
}
}
2.2 MVC模式
MVC现在已经用的不多,MVP和MVVM模式是主流,我们修改QuizActivity类配合Question类使用,现在看看各个类是如何协同工作的。模型(Question)、控制器(QuizActivity)、视图(布局,mTrueButton、mFalseButton、TextView、Button、Button、Button)
- 1,Android与MVC设计模式
GeoQuiz应用基于模型–视图–控制器的结构模式进行设计,MVC模式表明,应用的任何对象,归根结底都属于模型对象、视图对象以及控制器对象中的一种。 - 2,MVC各个模块分工
- a,模型对象:储存应用的数据业务逻辑。模型类通常用来映射与应用相关的一些事物,如用户、商店里的商品、服务器上的图片。
- b,视图对象:它知道怎样在屏幕上绘制自己,以及如何响应用户的输入,如触摸事件。
- c,控制器对象:含有应用的逻辑单元,是视图对象与模型对象联系纽带,控制器对象响应视图对象触发的各类事件,此外还管理着模型对象与视图层间的数据流动。
GeoQuiz应用的控制器层目前仅有QuizActivity类。MVC设计模式的好处:随着应用功能的扩展,应用往往会变得过于复杂让人难以理解,以java类组织代码有助于从整体视角设计和理解应用,这样我们可以按类而不是按变量和方法思考设计开发问题。同样,把java类以模型层、视图层和控制器层进行分类组织,也有助于我们设计和理解Android应用,这样我们可以按层而非一个个类来考虑设计开发了。
2.3 更新视图层
在QuizActivity中添加一个NEXT按钮,GeoQuiz应用唯一的布局定义在activity_quiz.xml文件中,添加一个NEXT BUTTON。1
2
3
4
5
6
7<Button
android:id="@+id/next_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/next_button"
android:drawableRight="@drawable/arrow_right"
android:drawablePadding="4dp"/>
同时对文本视图(TextView)进行调整,删除TextView的android:text属性定义,不用硬编码地理知识问题,同时为TextView新增android:id属性,TextView需要资源ID,以便于在QuizActivity代码中为它设置要显示的文字。同时更新字符串资源定义,就是strings.xml中,添加其他地理知识问题的字符串。
2.4 更新控制器层
在QuizActivity中添加TextView和Button变量,再创建一个Question对象数组以及一个该数组的索引变量,代码如下所示:1
2
3
4
5
6
7
8
9
10
11
12private Button mNextbutton;
private TextView mQuestionTextView;
private Question[] mQuestionBank = new Question[] {
new Question(R.string.question_australia,true) ,
new Question(R.string.question_oceans,true) ,
new Question(R.string.question_mideast,false) ,
new Question(R.string.question_africa,false) ,
new Question(R.string.question_americas,true) ,
new Question(R.string.question_asia,true) ,
};
priavte int mCurrentIndex = 0;
在这里,我们会通过多次调用Question类的构造方法,创建了Question对象数组(在更复杂的项目里,这类数组的创建和存储会单独处理)。1
2
3mQuestionTextView = findViewById(R.id.question_text_view);
int question = mQuestionBank[mCurrentIndex].getmTextResId();
mQuestionTextView.setText(question);
运行应用,可以看到数组存储的第一个问题显示在TextView上了。
接下来处理,NEXT按钮,首先引用NEXT按钮,然后为其设置监听器View.OnClickListener。该监听器的作用是:递增数组索引并相应地更新TextView的文本内容,代码如下所示:1
2
3
4
5
6
7
8
9mNextbutton = findViewById(R.id.next_button);
mNextbutton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mCurrentIndex = (mCurrentIndex + 1) % mQuestionBank.length;
int question = mQuestionBank[mCurrentIndex].getmTextResId();
mQuestionTextView.setText(question);
}
});
把公共代码提取出来,然后在不同的地方分别调用它。1
2
3
4private void updateQuestion() {
int question = mQuestionBank[mCurrentIndex].getmTextResId();
mQuestionTextView.setText(question);
}
我们提取代码生成update方法,运行应用,验证新增的NEXT按钮。当前应用认为所有的问题答案都是true,下面解决这个逻辑错误,同样为了避免重复代码,我们把代码封装在一个方法里。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16private void checkAnswer (boolean userPressedTrue) {
boolean answerIsTrue = mQuestionBank[mCurrentIndex].ismAnswerTrue();
int messageResId = 0;
if (mIsCheater) {
messageResId = R.string.judgment_toast;
}else{
if (userPressedTrue == answerIsTrue){
messageResId = R.string.correct_toast;
} else {
messageResId = R.string.incorrect_toast;
}
}
Toast.makeText(this,messageResId,Toast.LENGTH_SHORT).show();
}
方法接收boolean值,也就是用户按的是true还是false按钮,将用户的答案和当前Question对象中的答案作比较,最后判断答案是否正确。生成一个Toast消息反馈给用户。
在按钮的监听器里,调用checkAnswer方法,代码如下所示:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16...
public void onClick(View v) {
// Toast.makeText(QuizActivity.this,
// R.string.correct_toast,Toast.LENGTH_SHORT)
// .show();
// setGravity(5,5,5);
checkAnswer(true);
}
public void onClick(View v) {
// Toast.makeText(QuizActivity.this,
// R.string.incorrect_
// toast,Toast.LENGTH_SHORT).show();
checkAnswer(false);
}
...
2.5 添加图标资源
使应用中的NEXT按钮能够显示向右的图标,这样会更加用户友好。将图标资源(图片)复制到res目录下的drawable目录。然后在布局文件中引用:1
2android:drawableRight="@drawable/arrow_right"
android:drawablePadding="4dp"
三、activity的生命周期
现在应用运行后,旋转屏幕后,应用状态就重置,这就涉及到activity的生命周期问题了。