Android编程权威指南(第4版)
上QQ阅读APP看书,第一时间看更新

2.4 更新控制器层

在上一章,应用控制器层的MainActivity类的处理逻辑很简单:显示定义在activity_main.xml文件中的布局对象,为两个按钮设置监听器,响应用户点击事件并创建toast消息。

既然现在有更多的地理知识问题可以检索与展示,MainActivity类就需要更多的处理逻辑来让GeoQuiz应用的模型层与视图层协作。

打开MainActivity.kt文件,如代码清单2-5所示,创建一个Question对象集合以及该集合的索引变量。

代码清单2-5 增加Question对象集合(MainActivity.kt)

class MainActivity : AppCompatActivity() {

    private lateinit var trueButton: Button
    private lateinit var falseButton: Button

    private val questionBank = listOf(
            Question(R.string.question_australia, true),
            Question(R.string.question_oceans, true),
            Question(R.string.question_mideast, false),
            Question(R.string.question_africa, false),
            Question(R.string.question_americas, true),
            Question(R.string.question_asia, true))

    private var currentIndex = 0
    ...
}

这里,我们通过多次调用Question类的构造函数,创建了Question对象集合。

(在较复杂的项目里,这类集合的创建和存储会单独处理。在后续应用开发中,你会看到更好的模型数据存储方式。现在,简单起见,我们选择在控制器层代码中创建集合。)

要在屏幕上显示一系列地理知识问题,可以使用questionBank、currentIndex变量以及Question对象的存取方法。

如代码清单2-6所示,首先给TextView和新Button添加属性,然后引用它们,并设置TextView显示当前集合索引所指向的地理知识问题(稍后会设置NEXT按钮的点击事件监听器)。

代码清单2-6 使用TextView(MainActivity.kt)

class MainActivity : AppCompatActivity() {

    private lateinit var trueButton: Button
    private lateinit var falseButton: Button
    private lateinit var nextButton: Button
    private lateinit var questionTextView: TextView
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        trueButton = findViewById(R.id.true_button)
        falseButton = findViewById(R.id.false_button)
        nextButton = findViewById(R.id.next_button)
        questionTextView = findViewById(R.id.question_text_view)

        trueButton.setOnClickListener { view: View ->
            ...
        }

        falseButton.setOnClickListener { view: View ->
            ...
        }

        val questionTextResId = questionBank[currentIndex].textResId
        questionTextView.setText(questionTextResId)
    }
}

保存所有文件,确保没有错误发生,然后运行GeoQuiz应用。可以看到,集合存储的第一个问题显示在TextView上了。

现在来处理NEXT按钮,为其设置监听器View.OnClickListener。该监听器的作用是让集合索引递增并相应地更新TextView的文本内容,如代码清单2-7所示。

代码清单2-7 使用新增的按钮(MainActivity.kt)

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    falseButton.setOnClickListener { view: View ->
        ...
    }

    nextButton.setOnClickListener {
        currentIndex = (currentIndex + 1) % questionBank.size
        val questionTextResId = questionBank[currentIndex].textResId
        questionTextView.setText(questionTextResId)

    }

    val questionTextResId = questionBank[currentIndex].textResId
    questionTextView.setText(questionTextResId)
}

注意到了吗?同样的questionTextView文字赋值代码出现在了两个不同的地方。参照代码清单2-8,花点儿时间把这样的公共代码放到一个函数里,然后分别在nextButton监听器里以及onCreate(Bundle?)函数的末尾调用它。后一个调用是为了初始化设置activity视图中的文本。

代码清单2-8 使用updateQuestion()封装公共代码(MainActivity.kt)

class MainActivity : AppCompatActivity() {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        nextButton.setOnClickListener {
            currentIndex = (currentIndex + 1) % questionBank.size
            val questionTextResId = questionBank[currentIndex].textResId
            questionTextView.setText(questionTextResId)
            updateQuestion()
        }

        val questionTextResId = questionBank[currentIndex].textResId
        questionTextView.setText(questionTextResId)
        updateQuestion()
    }

    private fun updateQuestion() {
        val questionTextResId = questionBank[currentIndex].textResId
        questionTextView.setText(questionTextResId)
    }
}

运行GeoQuiz应用,验证新添加的NEXT按钮。

如果一切正常,问题应该已经完美显示出来了。当前,GeoQuiz应用认为所有问题的答案都是true,下面着手修正这个逻辑错误。同样,为避免代码重复,我们将解决方案封装在一个私有函数里。

要添加到MainActivity类的函数如下:

private fun checkAnswer(userAnswer: Boolean)

该函数接受布尔类型的变量参数,判别用户点击了TRUE还是FALSE按钮。然后,将用户的答案同当前Question对象中的答案做比较,判断正误,并生成一个toast消息反馈给用户。

在MainActivity.kt文件中,添加checkAnswer(Boolean)函数的实现代码,如代码清单2-9所示。

代码清单2-9 增加checkAnswer(Boolean)函数(MainActivity.kt)

class MainActivity : AppCompatActivity() {
    ...
    private fun updateQuestion() {
        ...
    }

    private fun checkAnswer(userAnswer: Boolean) {
        val correctAnswer = questionBank[currentIndex].answer

        val messageResId = if (userAnswer == correctAnswer) {
            R.string.correct_toast
        } else {
            R.string.incorrect_toast
        }

        Toast.makeText(this, messageResId, Toast.LENGTH_SHORT)
                .show()
    }
}

在按钮的监听器里,调用checkAnswer(Boolean)函数,如代码清单2-10所示。

代码清单2-10 调用checkAnswer(Boolean)函数(MainActivity.kt)

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    trueButton.setOnClickListener { view: View ->
        Toast.makeText(
            this,
            R.string.correct_toast,
            Toast.LENGTH_SHORT
        )
            .show()
        checkAnswer(true)
    }

    falseButton.setOnClickListener { view: View ->
        Toast.makeText(
            this,
            R.string.correct_toast,
            Toast.LENGTH_SHORT
        )
            .show()
        checkAnswer(false)
    }
    ...
}

运行GeoQuiz应用,确认toast消息基于用户点击给出了正确反馈。