现在即将升级上一节的应用,在之前的基础上,我们要做到
- 提供更多的测试题目
- 能够通过前进、后退按钮(使用ImageButton)实现题目的切换
创建Question类
由于要提供更多的测试题目,我们在这里会创建一个Question类,很容易地可以想到,该类中应该保存题目和答案,以及构造函数和对应的get方法和set方法。Question类的代码如下
public class Question {
private int mTextResId;
private boolean mAnswerTrue;
public Question(int textResId, boolean answerTrue) {
mTextResId = textResId;
mAnswerTrue = answerTrue;
}
public int getTextResId() {
return mTextResId;
}
public void setTextResId(int textResId) {
mTextResId = textResId;
}
public boolean getAnswerTrue() {
return mAnswerTrue;
}
public void setAnswerTrue(boolean answerTrue) {
mAnswerTrue = answerTrue;
}
}
快速生成get、set方法和构造函数
get、set和构造函数可以有Android Studio自动为我们生成,具体方法如下
1.配置Android Studio识别成员变量的m前缀
打开Android Studio,点击File→Settings菜单,选择Editor→Code Style选项,在Java选项卡下选择Code Generation选项页。如下图所示添加Field和Static field,这个作用是识别成员变量的前缀m,在为mTextResId生成get方法时,它生成的是getTextResId()而不是getMTextResId()。在这里我们暂时不会用到s前缀。
2.自动生成方法
现在我们可以在代码编辑页中点击右键-Generate-Constructor自动生成构造函数了。
按住CTRL键,选择mTextResId和mAnswerTrue成员变量,点击OK,构造函数就生成好了同样地,我们点击右键-Generate-Getter and Setter,选择两个成员变量,这样它们的get方法和set方法就已经自动生成好了,到这里为止,Question类也已经编写完了。
MVC设计模式
现在应用的整体结构如下图所示,我们使用Activity创建Question数组对象,通过TextView和Button的交互,在屏幕上显示问题,并根据用户的回答作出反馈
这就是MVC的架构模式(模型-视图-控制器),MVC设计模式表明,应用的任何对象,归根结底都属于模型对象、视图对象和控制器对象中的一种。
- 模型对象存储着应用的数据和业务逻辑,模型类通常用来映射与应用相关的一些事物,如用户、商品、图片等,还有就是这里的Question类。
- 视图对象知道如何在屏幕上绘制自己,以及如何响应用户的输入,凡是能够在屏幕上见到的对象,都属于视图对象。所有视图对象组成了应用的视图层,而视图层由XML文件中定义的各种组件构成
- 控制器对象含有应用的逻辑单元,是视图对象和模型对象的联系纽带,控制器对象响应视图对象触发的各类事件,还管理着模型对象与视图层之间的数据流动
我们已经设计好了模型对象——Question类,现在要做的就是更新视图层和控制器对象了。
更新视图层
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="24dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/true_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/true_button"/>
<Button
android:id="@+id/false_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/false_button"/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageButton
android:id="@+id/pre_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/arrow_left"/>
<ImageButton
android:id="@+id/next_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/arrow_right"/>
</LinearLayout>
</LinearLayout>
我们的XML代码如上所示,与最开始的XML代码,我们为TextView组件添加了android:id属性,这是因为在切换题目时,我们需要更新TextView显示的内容。原有的True和False按钮保持不变,再添加了两个ImageButton按钮(我们当然可以继续使用Button按钮)
ImageButton的使用
顾名思义,ImageButton就是图像按钮,它不是以文字的方式显示,而是以图片的形式。它的使用方法与Button按钮类似,我们为其添加资源ID、高度和宽度,同时要为其添加android:src属性,该属性指定了ImageButton使用的是什么图片(图片资源保存在app/res/drawable目录下,我们需要提前将图片资源添加到此目录下),我们可以预览一下我们的布局
更新控制器层
创建Question数组
首先,我们要创建一个Question数组,Question的构造函数的参数有题目的资源ID和正确答案,题目的字符串资源已经添加到字符串资源文件中,这里不再赘述。
private Question[] mQuestionBank = new Question[]{
new Question(R.string.question_tall, true),
new Question(R.string.question_fat, false),
new Question(R.string.question_single, false),
new Question(R.string.question_yellow, true),
new Question(R.string.question_happy, true),
new Question(R.string.question_cold, true)
};
更新TextView
在预览布局的时候可以发现,目前TextView组件是没有显示任何的内容的。下一步我们要做的就是更新TextView了。我们需要创建一个成员变量来绑定TextView组件。然后使用updateQuestion()函数更新题目(因为在点击Next和Pre按钮时,也需要更新TextView,所以我们使用这个函数封装相同的代码)
private TextView mTextView;
mTextView = (TextView) findViewById(R.id.text_view);
updateQuestion();
updateQuestion()函数的代码如下,其中mCurrentIndex是一个整型变量,初始值设为0,记录目前显示的题目Question数组中的索引。
private void updateQuestion(){
int question = mQuestionBank[mCurrentIndex].getTextResId();
mTextView.setText(question);
}
现在我们运行应用,已经可以正常显示第一个题目了,下面我们要实现当点击Next和Pre按钮时更新题目,我们需要为这两个ImageButton设置监听器。代码如下,两个按钮的监听器代码有些许不同,这是因为,点击Pre按钮,我们需要将mCurrentIndex往前移一位,这有可能使它为负数,导致无法在Question数组中找到对应的问题。
mPreButton = (ImageButton) findViewById(R.id.pre_button);
mNextButton = (ImageButton) findViewById(R.id.next_button);
mNextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mCurrentIndex = (mCurrentIndex + 1) % mQuestionBank.length;
updateQuestion();
}
});
mPreButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(mCurrentIndex > 0) {
mCurrentIndex = (mCurrentIndex - 1) % mQuestionBank.length;
updateQuestion();
}
else{
mCurrentIndex = mQuestionBank.length - 1;
updateQuestion();
}
}
});
检查答案
现在距离完成任务还差最后一步,我们要检查用户回答题目的正确性,因为每一个题目的答案可能有不一样,所以我们不能根据用户点击哪一个按钮来弹出Toast消息。我们实现了一个checkAnswer()函数来做这件事情,该函数的代码如下,它会比较用户的答案和问题的正确答案,如果相同,则提示Correct,如果不相同则提示Incorrect。
private boolean checkAnswer(boolean userPressTrue){
boolean answerIsTrue = mQuestionBank[mCurrentIndex].getAnswerTrue();
int messageResId = 0;
messageResId = (userPressTrue == answerIsTrue) ? R.string.correct_toast:R.string.incorrect_toast;
Toast.makeText(MainActivity.this, messageResId, Toast.LENGTH_SHORT).show();
return userPressTrue == answerIsTrue;
}
现在我们可以为True和False按钮设置监听器了~到这里我们的任务就已经完成啦。(其实这里还有一个BUG,你可以尝试旋转屏幕,看看能否发现。)
mTrueButton = (Button) findViewById(R.id.true_button);
mFalseButton = (Button) findViewById(R.id.false_button);
mTrueButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
checkAnswer(true);
}
});
mFalseButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
checkAnswer(false);
}
});
文章内容出自《Android变成权威指南》(第3版)
内容经过删改处理,仅作为学习用途
如有侵权,联系删除
2021/06/06