通过构建安卓游戏学习-Java-一-

33 阅读1小时+

通过构建安卓游戏学习 Java(一)

原文:zh.annas-archive.org/md5/94381ED211CDAA9276E19DB483447D97

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

如果你完全不了解 Java、Android 或游戏编程,并且打算出版 Android 游戏以供娱乐或商业用途,但你不知道从哪里开始,那么这本书适合你。这本书也可以作为那些在其他平台上已经有经验的人的复习。

Android 是增长最快的操作系统,Android 设备可以赋予、娱乐和教育全球。Android 使用最流行的编程语言之一——Java,这是一种高性能、安全、面向对象的语言。

在每一章中,我们将在前一章学到的基础上构建,逐渐理解更高级的 Java 概念,并通过构建 Android 游戏来应用它们。

这本书涵盖了什么

第一章,“为什么选择 Java、Android 和游戏?”,告诉我们为什么我们可能会选择 Java、Android 和游戏而不是其他语言、平台和应用类型。然后我们通过安装 Java 开发工具包和 Android Studio 快速准备我们的 Android 和 Java 开发环境。

第二章,“开始使用 Android”,向我们展示了如何在真实的 Android 设备上设计、构建和运行游戏菜单 UI。这是我们数学游戏项目的第一部分。

第三章,“使用 Java——你的第一个游戏”,涵盖了 Java 基础知识及其在 Android 环境中的应用,制作一个简单的数学测验游戏。

第四章,“发现循环和方法”,包含了更多的 Java 基础知识,如决策和循环。然后我们使用它们来为我们的数学游戏添加功能。本章还涵盖了一些基本的 Android 游戏要素,如锁定和处理屏幕旋转,并介绍了设备传感器。

第五章,“游戏和 Java 基础知识”,带我们快速浏览了任何游戏都必不可少的一些重要基础知识。我们还通过动画和存储玩家得分来使 UI 更加有趣。通过一个 Simon 风格的记忆游戏进行演示,使用 Java 数组和 for 循环存储序列。

第六章,“面向对象编程——利用他人的辛勤工作”,是你迄今为止学到的一切都被漂亮地捆绑起来的一章。你应该会发现自己喃喃自语着“哦,我明白了”和“原来是这样”。我们在这一章探讨了面向对象编程(OOP)。

第七章,“复古乒乓游戏”,教会我们如何使用 Android Canvas 类逐像素绘制图形。然后我们可以将这些新技能与我们已经掌握的知识相结合,制作一个复古的乒乓风格的乒乓游戏。

第八章,“贪吃蛇游戏”,涵盖了我们最先进的游戏项目的开始——一个动画的“贪吃蛇”风格的街机游戏。我们还将能够练习在前几章中学到的重要的 Java 基础知识。

第九章,“让你的游戏成为下一个大事件”,通过教你如何发布你的游戏并添加在线排行榜和成就来为你的游戏锦上添花。这也让你了解 Java 库的概念。

附录,“自测问题和答案”,包含了测试你对主题理解的所有问题的答案。

你需要为这本书做好准备

你需要一个适度版本的 Windows(XP、Vista、7 或 8),以及 Mac、Linux PC 或笔记本电脑。32 位和 64 位系统都兼容。除此之外,所有需要的软件都是免费的,并且在书中有详细的说明。

这本书是为谁写的

如果你完全是 Java、Android 或游戏编程的新手,那么这本书就是为你准备的。由于本书不需要任何先前的知识,所以如果你对这三个领域(Java、Android 和游戏编程)都是新手,那么这本书也适合你。

如果你想为了娱乐或商业目的发布 Android 游戏,但不确定从哪里开始,那么这本书将从头开始逐步向你展示该怎么做。

如果你在其他平台(也许是 PC 或 Mac)有 Java 经验,那么这本书将是一个很好的 Java 复习,也会向你展示如何在 Android 环境中应用你现有的技能。

如果你之前在其他语言中编程,但想从头开始学习 Java,那么你可以从这本书中学习,并更快地通过实际示例。

本书假设你对自己选择的操作系统相当自信。逐步教程是在 Windows 中进行的,但如果你对 Mac 或 Linux 有相当了解,你也可以很容易地跟随它们。

约定

在本书中,你会发现一些区分不同信息类型的文本样式。以下是一些样式的例子和它们的含义解释。

文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名显示如下:"在 Android Studio 项目资源管理器中,双击layout文件夹,以显示其中的activity_main.xml文件。"

代码块设置如下:

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

当我们希望引起你对代码块的特定部分的注意时,相关行或项目会以粗体显示:

int partA = 9;
int partB = 9;
correctAnswer = partA * partB;
int wrongAnswer1 = correctAnswer - 1;
int wrongAnswer2 = correctAnswer + 1;

任何命令行输入或输出都以以下方式书写:

info﹕ a = 10
info﹕ b = Alan Turing
info﹕ c = true

新术语重要单词以粗体显示。屏幕上看到的单词,例如菜单或对话框中的单词,会以这种方式出现在文本中:"在提示导入另一个类时,点击确定。"

注意

警告或重要提示会以这种方式出现在一个框中。

提示

提示和技巧会以这种方式出现。

第一章:为什么学习 Java、Android 和游戏?

欢迎来到通过构建 Android 游戏学习 Java,我希望这只是你激动人心的设计和编写游戏之旅的开始。在本书结束时,我们将完成四款完整的游戏:一个难度动态增加的数学测验,一个类似经典 Simon 玩具的记忆游戏,一个类似乒乓球的游戏,以及一个经典的贪吃蛇游戏的克隆。

除了这些游戏,我们还将构建十多个可工作的应用程序,以练习和演示个别概念,帮助我们学习 Java、Android 和游戏。我们的游戏和应用程序将包括声音效果、图形和动画。我们将学习从使用标准 Android 用户界面(UI)设计师到通过绘制单个像素创建流畅动画的一切。

尽管我会鼓励你和我一起工作,并逐步实施本书中详细介绍的具体项目,但我完全期望一旦你掌握了不同的概念,你会想立即将它们用于自己独特的创作中。这正是我希望你会被激发去做的事情。

游戏项目本身并不是本书的目标,而是实现更崇高目标的手段。在本书结束时,你将能够设计和实现自己的 2D Android 游戏,在 Google Play 上销售或免费分享。

提示

首先需要做一些基础工作,但我保证不会花很长时间,也不会很复杂。任何人都可以学会编程。

然而,专家们有很多不同的观点,这在初学者中引起了关于学习编程的最佳方式的困惑。因此,看看为什么学习 Java、Android 和游戏是初学者的理想途径是一个好主意。这将是我们在本书中首先讨论的事情。

以下是我们将在本章学到的内容:

  • 这本书适合我吗?

  • 为什么要用游戏来学习编程?

  • 我为什么要学习 Java 和 Android?

  • 设置我们的开发环境

这本书适合我吗?

如果你已经决定要学习 Java、Android 或游戏,那么下一个问题可能是,“这本书适合我吗?”

有很多适合初学者的 Java 书籍,也有比我更有成就的作者和程序员写的书。我读过其中很多,并且钦佩这些作者。然而,当这些书开始涉及 Java 本地接口、Web 浏览器小程序或服务器端远程通信等话题时,我有时会质疑它们对我的直接相关性。

在这一点上,至少在潜意识中,我的承诺会减弱,学习过程会变得缓慢或停止。

如果你只想学习纯粹的 Java

如果你只想学习 Java 本身,这本书将是一个很好的开始。尽管 Android 的内容可能被视为对纯粹的 Java 学习的额外开销,但这远远少于任何其他 Java 书中可能引入的多余主题。这本书唯一的警告是必要的开销都在开始时。但一旦这个最小的开销被清除,我们就可以非常专注地学习 Java。

关于额外开销的问题:

  • 在本章中,我们将花大约六页来设置我们的编程环境

  • 需要第二章,开始使用 Android,来熟悉 Android 工具,创建你的第一个可工作的项目,并瞥见你的第一段真正的 Java 代码

  • 从那时起,几乎就是纯粹的 Java 和游戏开发了

你很快就会发现,这点额外的开销并不过分,而且是非常值得的。

如果你的重点是 Android

如果是 Android 本身吸引你看这本书,那么我很自豪地说,这是第一本会教你 Android 而不假设你有任何先前 Java 或编程知识的书。

这本书将带你去哪里

在本书结束时,你将能够轻松选择其中的一条道路,包括以下内容:

  • 为任何平台学习更高级别的 Java

  • 包括纯游戏框架的中级水平 Android 学习(将在第九章中更详细地介绍,使你的游戏成为下一个大事件

  • 更高级别的游戏开发

  • 更容易地处理任何现代面向对象的语言,比如 iOS、Windows 或 Web 开发

因此,如果你知道你想学习 Android 或 Java,希望我已经在某种程度上让你承诺这本书将如何帮助你。但为什么要学习游戏、Android 或 Java 呢?

为什么要制作游戏来学习编程?

当然,这很有趣!但也有其他原因。成功地运行我们编写的任何程序都是极其令人满意的,尤其是当它涉及使用我们以前不理解的一些代码时。

但是,正如你很快就会意识到的,制作我们自己的游戏会带来一种难以描述的愉悦感——这必须亲身体验才能感受到。然后,通过在手机或平板电脑上与朋友分享我们的创作,甚至在 Google Play 商店上公开分享它们,你可能会意识到一旦开始制作游戏,就停不下来了。

随着我们逐渐创建更复杂的游戏,你会意识到所有的技巧和代码片段都可以重新利用来创建其他游戏,然后你可以开始规划你自己独特的杰作。这至少是令人振奋的。

和许多其他学科一样,我们练习得越多,就会变得越好。所以游戏是学习编程 Java 的绝佳方式。然而,大多数针对 Android 游戏的初学者书籍都需要相当高水平的 Java 知识。但正如我们将看到的,完全可以将实际示例作为有趣的游戏项目,并从 Java 的基础知识开始。

这样做有一点儿取舍。我们不会总是按照“按部就班”的方式来处理工作中的游戏示例。这是为了避免在掌握前滚之前就做花式翻筋的问题。

学习成果的优先级始终是 Java 编程概念,其次是理解 Android 环境和游戏设计原则。话虽如此,我们将密切研究和实践大量的 Android 和游戏编程基础知识。

当然,从我们刚刚讨论的内容中,你可能可以推测出,如果我们没有制作游戏,那么在同样的页数中可能可以教授更多的 Java 知识。

这是真的,但我们失去了使用游戏作为学习主题所带来的所有好处。制作游戏确实可以带来愉悦,当我们的大脑敞开并渴望获取信息时,我们会学得更快。以这种方式学习的最小开销会被抵消一百倍。如果游戏对你一点兴趣都没有,那么有很多传统方法的 Java 初学者指南可以选择。只是不要期望发布你的第一个带有在线排行榜和成就的游戏时会有相同的刺激感。

为什么选择 Android 和 Java?

成功学习的一部分是学生的承诺,不仅是做工作,而且是相信他们正在以正确的方式做正确的事情。因此,许多技术课程和书籍并没有得到读者的承诺,至少在潜意识中没有。

问题在于学生们认为,他们可能在某种程度上,至少部分地,浪费时间在某些已经过时或即将过时的东西上,或者可能不太适合他们。这在很大程度上可能是真的,尤其是在编程方面。那么,为什么你要花费有限的时间学习 Java 和 Android 呢?

Android 是迄今为止发展最快、增长最快的操作系统

曾经,安卓更新几乎每两个月就会出现。即使现在,它们也大约每六个月出现一次。相比之下,Windows 版本之间需要数年的时间,即使 iOS 更新也只是每年一次,而且版本之间的变化通常相对较小。安卓显然正在以前所未有的速度发展和改进。

提示

查看安卓版本的历史,从第 1 版开始,www.cnet.com/news/history-of-android/

安卓的第一个版本于 2008 年发布,当时消费者已经对当时更加花哨的 iPhone 感到兴奋。新闻报道也在报道开发者通过在 iTunes 应用商店出售应用程序而变得富有。

但在这本书写成之前的整整一年里,仅三星一家就发货的安卓设备就比苹果销售的所有 iOS 设备加起来还要多。我不会加入关于哪种设备更好的战争。我喜欢安卓和苹果的各种方面,但纯粹从选择一个学习平台的角度来看,你可能选择了正确的时间和地点,选择了安卓。

安卓开发者前景广阔

现在你可能只是为了学习编程游戏的乐趣和满足感而拿起这本书。但如果你决定进一步发展你的学习,你会发现对安卓程序员的需求是巨大的,因此也非常有利可图。

提示

一些数据表明薪水超过 10 万美元。欲了解更多信息,请访问www.indeed.com/salary?q1=Android+Developer&l1=United+States

安卓是开源的

开源意味着尽管谷歌开发了最新设备上使用的所有安卓版本,但一旦代码发布,任何人都可以随心所欲地使用它。谷歌只在有限的时间内施加控制。

实际上,大多数安卓用户使用的是纯谷歌操作系统或三星、HTC 等大型制造商生产的修改版本,但没有任何东西可以阻止任何人拿起操作系统并将其改变、调整或转换成他们喜欢的任何东西。简而言之,安卓永远不会从编程社区中消失。

Java 会长存下去

好的,所以我们看到安卓不太可能消失,但是 Java 可能会变得多余吗?你的大量时间投资会白费吗?在安卓上,与大多数平台一样,你可以使用许多语言和工具。然而,安卓是从头开始设计的,以促进 Java 开发。所有其他语言和工具都不是无效的,但往往只是为了服务于一个相当特定的目的,而不是真正的 Java 替代品。事实上,就游戏而言,许多纯 Java 开发环境的替代品也是基于 Java 的,并且需要在 Java 上具有良好的技能水平。例如,流行的 LibGDX 游戏开发库,允许你同时为安卓、iOS、Windows、Linux、Mac 甚至 Web 制作游戏,仍然使用 Java!我们将在第九章中更多地讨论这个问题,《使你的游戏成为下一个大事件》。关键是 Java 和安卓是紧密联系在一起的,很可能会共同繁荣。

Java 不仅仅是为了安卓

事实上,Java 已经存在了很长时间,早在 1990 年代初就有了。尽管 Java 的用途在两个多十年里发生了演变和多样化,但语言本身最初实现的优势至今仍然保持不变。

Java 被设计为平台或计算机无关的。这是通过使用虚拟机VM)来实现的。这是一个用另一种语言编写的程序,它解码我们编写的 Java 程序并与其运行的计算机平台进行交互。因此,只要你想在计算机上运行你的 Java 程序,只要有一个 VM,你的 Java 程序就会运行,除了一些例外。因此,如果你学习 Java,你就是在学习一种语言,它在从智能冰箱到网络以及其他大多数地方都被使用。

然而,每个平台上的虚拟机通常会实现特定于其可能被用于的用途的功能。一个明显的例子是移动设备特定的功能,比如传感器、GPS 或许多 Android 设备上的内置摄像头。在 Android 上使用 Java,你可以拍照、检测气压,并准确地确定你在世界的哪个位置。大多数冰箱的虚拟机可能不会这样做。因此,你不能总是在设备 y 上运行为设备 x 设计的 Java 程序,但语言和语法是相同的。在 Android 上学习 Java 在很大程度上为任何情况下的 Java 做好了准备。所以请放心,Java 不会很快消失。

Java 快速且易于使用

关于哪种语言是最佳的或哪种语言是最适合学习编程的语言的辩论已经进行了几十年。Java 的批评者可能会说 Java 的速度问题。的确,Java 的内存管理以及虚拟机解释过程确实会有一些速度成本。然而,这些事情也有好处;它们显著提高了我们的生产力,而 Android 虚拟机与设备的交互方式在很大程度上抵消了轻微的速度损失。自 Android 4.4 以来,它完全通过Android Run TimeART)来实现,它将用 Java 编写的应用程序安装为完全本地的应用程序。现在 Java 程序员可以使用友好的解释语言构建游戏,并使它们运行得就像它们是用更具挑战性的本地编译语言编写的一样。

Java 和 Android 摘要

在一个快速变化的世界中,如果你担心在哪里投资你宝贵的学习时间,很难有更多的信心。在这里,我们有一种语言(Java),其基本原理几乎在近 25 年里保持不变,以及一个由硬件、软件和零售业的最大名字支持的平台(Android),尽管它受到了巨大的影响,但实际上并不属于任何人。

我不是任何技术的传道者,尽管我确实喜欢在 Android 上做一些事情。但你可以确信,如果你正在考虑开始学习编程的最佳途径,有一个非常有力的论点,那就是 Java 和 Android 是最佳选择。

如果你想学习 Java 以及它的众多用途,那么这是一个非常好的开始。如果你想为 Android 开发或进入任何类型的 Android 开发,那么 Java 是绝对基础的开始方式,而制作游戏已经讨论过的巨大好处。

到书的结尾,你将能够为几乎任何支持 Java 的平台编写 Java 代码。你将能够在 Android 环境之外使用你在本书中学到的几乎所有东西。

如果你计划通过制作 Android 游戏或任何 Android 应用来追求职业或业务,那么这本书可能是初学者开始的唯一选择。

如果你完全是新手,想要掌握 Java 的最简单途径——这是地球上增长最快的平台——那么通过构建 Android 游戏学习 Java可能会非常适合你。

因此,希望你确信,这本书学习 Java 的路径是如此简单、有趣和全面,学习 Java 就像是。让我们开始设置,这样我们就可以开始制作游戏。

设置我们的开发环境

我们需要做的第一件事是准备我们的 PC 使用 Java 开发 Android。幸运的是,这对我们来说相当简单。

提示

如果您正在 Mac 或 Linux 上学习,本书中的所有内容仍然适用。接下来的两个教程有 Windows 特定的说明和截图。但是,稍微调整步骤以适应 Mac 或 Linux 应该不会太困难。

我们需要做的只是:

  1. 安装一个名为“Java 开发工具包”(JDK)的软件包,它允许我们使用 Java 进行开发。

  2. 安装 Android Studio,这是一个旨在使 Android 开发快速简单的程序。Android Studio 使用 JDK 和一些其他特定于 Android 的工具,这些工具在安装 Android Studio 时会自动安装。

安装 JDK

我们需要做的第一件事是获取 JDK 的最新版本。要完成本指南,执行以下步骤:

  1. 您需要在 Java 网站上,所以访问www.oracle.com/technetwork/java/javase/downloads/index.html

  2. 找到下面截图中显示的三个按钮,并点击标有“JDK”的按钮(高亮显示)。它们位于网页的右侧。点击“JDK”选项下的“下载”按钮:安装 JDK

  3. 您将被带到一个页面,上面有多个选项可以下载 JDK。在“产品/文件描述”列中,您需要点击与您的操作系统匹配的选项。Windows、Mac、Linux 和其他一些不太常见的选项都列在其中。

  4. 这里一个常见的问题是,“我有 32 位还是 64 位的 Windows?”。要找出来,右键单击“我的电脑”(在 Windows 8 上是“此电脑”)图标,点击“属性”选项,在“系统类型”条目下查看“系统”标题下,如下截图所示:安装 JDK

  5. 点击略微隐藏的“接受”“许可协议”复选框:安装 JDK

  6. 现在点击下载选项,选择您的操作系统和系统类型,等待下载完成。

  7. 在您的“下载”文件夹中,双击刚刚下载的文件。在撰写本文时,64 位 Windows PC 的最新版本是jdk-8u5-windows-x64。如果您使用 Mac/Linux 或 32 位操作系统,您的文件名将相应地有所不同。

  8. 在几个安装对话框中的第一个,点击“下一步”按钮,您将看到下一个对话框:安装 JDK

  9. 通过点击“下一步”接受前面截图中显示的默认设置。在下一个对话框中,您可以通过点击“下一步”接受默认的安装位置。

  10. 接下来是 Java 安装程序的最后一个对话框。点击“关闭”。

JDK 现在已安装。接下来我们将确保 Android Studio 能够使用 JDK。

  1. 右键单击“我的电脑”(在 Windows 8 上是“此电脑”)图标,导航到“属性”|“高级系统设置”|“环境变量”|“新建”(在“系统变量”下,而不是在“用户变量”下)。现在您可以看到“新建系统变量”对话框,如下截图所示:安装 JDK

  2. 在“变量名”中键入JAVA_HOME,在“变量值”字段中输入C:\Program Files\Java\jdk1.8.0_05。如果您在其他地方安装了 JDK,那么您在“变量值”字段中输入的文件路径将需要指向您放置它的地方。您的确切文件路径可能会有不同的结尾,以匹配您下载时的 Java 最新版本。

  3. 点击“确定”保存新设置。现在再次点击“确定”清除“高级系统设置”对话框。

现在我们在我们的 PC 上安装了 JDK。我们离开始学习 Java 编程还差一半,但我们需要一种友好的方式与 JDK 进行交互,并帮助我们用 Java 制作 Android 游戏。

Android Studio

我们了解到 Android Studio 是一个简化 Android 开发的工具,它使用 JDK 允许我们编写和构建 Java 程序。除了 Android Studio,还有其他工具可以使用。它们各有利弊。例如,另一个非常流行的选择是 Eclipse。就像编程中的许多事情一样,可以提出强有力的论据,说明为什么应该使用 Eclipse 而不是 Android Studio。我两者都使用,但我希望你会喜欢 Android Studio 的以下元素:

  • 这是一个非常整洁的界面,尽管仍在开发中,但非常精致和干净。

  • 与 Eclipse 相比,Android Studio 更容易上手,因为一些 Android 工具已经包含在软件包中,而不需要单独安装。

  • Android Studio 由 Google 开发,基于另一个名为 IntelliJ IDEA 的产品。有可能它将成为不久的将来开发 Android 的标准方式。

提示

如果你想使用 Eclipse,那很好;本书中的所有代码都可以工作。但是,一些键盘快捷键和用户界面按钮显然会有所不同。如果你还没有安装 Eclipse 并且没有使用 Eclipse 的经验,那我更加强烈地建议你使用 Android Studio。

安装 Android Studio

所以,不要拖延,让我们安装 Android Studio,然后我们可以开始我们的第一个游戏项目。为此,让我们访问developer.android.com/sdk/installing/studio.html

  1. 点击标有下载 Android Studio的按钮开始下载 Android Studio。这将带你到另一个网页,上面有一个看起来非常相似的按钮。

  2. 通过勾选复选框接受许可证,点击标有为 Windows 下载 Android Studio的按钮开始下载,并等待下载完成。按钮上的确切文本可能会根据当前最新版本而有所不同。

  3. 在你刚刚下载 Android Studio 的文件夹中,右键单击android-studio-bundle-135.12465-windows.exe文件,然后点击以管理员身份运行。你的文件名结尾会根据 Android Studio 的版本和你的操作系统而有所不同。

  4. 当询问是否允许未知发布者的以下程序更改您的计算机时,点击。在下一个屏幕上,点击下一步

  5. 在下图所示的屏幕上,你可以选择你的 PC 上的用户谁可以使用 Android Studio。选择适合你的选项,然后点击下一步安装 Android Studio

  6. 在下一个对话框中,保持默认设置,然后点击下一步

  7. 然后在选择开始菜单文件夹对话框中,保持默认设置,然后点击安装

  8. 安装完成对话框中,点击完成以第一次运行 Android Studio。

  9. 下一个对话框是给已经使用过 Android Studio 的用户,所以假设你是第一次使用者,选择我没有以前的 Android Studio 版本,也不想导入我的设置复选框,然后点击确定安装 Android Studio

这是我们需要的最后一个软件。我们刚刚完成的简单的九步流程实际上已经设置了一整套 Android 工具,我们将在下一章开始使用。

摘要

我们讨论了为什么游戏、Java 和 Android 不仅极其令人兴奋,而且可以说是学习编程的最佳方式。这是因为游戏可以是一个极具动力的主题,而 Java 和 Android 在流行度和长期性方面具有巨大优势,并且对我们所有人都是免费开放的。

我们还设置了 Java 开发工具包并安装了 Android Studio,为接下来的章节做好准备,我们将实际创建一个工作游戏的一部分,并首次查看一些 Java 代码。

第二章:开始使用 Android

在本章中,我们将通过所有 Android 主题的过山车之旅,这些主题是您需要学习的,以便开始学习 Java。不过,这不仅仅是理论。我们将设计一个游戏菜单的用户界面UI),并且我们还将看到并编辑我们的第一行 Java 代码。

此外,我们将看到如何在 PC/Mac 上的 Android 模拟器或者如果有的话在真实的 Android 设备上运行我们的应用程序。

本章中我们将涵盖的一些内容只是冰山一角。也就是说,我们讨论的一些主题下面有更多的内容,这些内容不适合于学习 Java 书籍的第二章。有时,我们可能需要相信一些信息。

这将使我们能够在本章结束时实际设计和运行我们自己的 Android 应用程序。然后我们可以在下一章的开始学习真正的 Java。

如果本章看起来有点困难,那么不要担心;继续前进,因为每个后续章节都会逐渐揭开一些不太清晰的主题。

在本章和接下来的两章中,我们将构建一个数学游戏。我们将从简单开始,到第四章结束时,发现循环和方法,我们将扩展到使用重要的 Java 技能的游戏功能。

在本章中,我们将:

  • 开始我们的第一个游戏项目

  • 探索 Android Studio

  • 使用 Android Studio 可视化设计器制作我们的游戏 UI

  • 了解为 Android 构建代码的结构

  • 首次查看一些 Java 代码

  • 在模拟器和真实设备上构建和安装我们的游戏

我们的第一个游戏项目

现在我们将直接开始使用 Android Studio。通过双击桌面的开始菜单上的 Android Studio 图标,或者在安装它的文件夹中双击 Android Studio 图标来运行 Android Studio。

注意

如果您在对话框中收到任何提到权限提升的错误,请尝试以管理员权限运行 Android Studio。要做到这一点,通过单击 Windows 开始按钮并搜索Android Studio来找到 Android Studio 图标。现在右键单击该图标,然后单击以管理员身份运行。每次运行 Android Studio 时都要这样做。

准备 Android Studio

因此,安装了 Android Studio 和 Java 后,我们只需要添加我们将用于制作第一个游戏的最新版本的 Android API。以下是安装 API 的步骤:

  1. 从 Android Studio UI 顶部的菜单栏,导航到工具 | Android | SDK 管理器。在Android SDK 管理器窗口中向下滚动,并选择**Android 4.4.2 (API 19)**的复选框。

注意

请注意,由于 Android 发展如此迅速,当您阅读本章时,可能会有比 19 更高的 API,如 20、21 等。如果您遇到这种情况,请选择更新的(编号更高的)API。

准备 Android Studio

  1. 点击安装软件包

  2. 在下一个屏幕上,点击接受许可证复选框,然后点击安装按钮。Android Studio 将下载并安装适当的软件包。

刚才所做的是设置 Android Studio,以便提供最新的预写代码,称为 API,我们将在整本书中与之交互。

构建项目

  1. 点击新项目...,如下面的屏幕截图所示:构建项目

  2. 创建新项目配置窗口将出现。在应用程序名称字段中填写Math Game Chapter 2,在公司域中填写packtpub.com(或者您可以在此处使用您自己公司的网站名称),如下面的屏幕截图所示:构建项目

  3. 现在点击下一步按钮。在下一个屏幕上,检查手机和平板电脑复选框是否被选中。现在我们必须选择要为其构建应用程序的最早版本的 Android。随意在下拉选择器中尝试几个选项。您会发现我们选择的版本越早,我们的应用程序支持的设备百分比就越大。然而,这里的权衡是,我们选择的版本越早,我们的应用程序中可用的尖端 Android 功能就越少。一个很好的平衡是选择API 8:Android 2.2(Froyo)。现在就像下一个截图中所示那样去做吧:构建项目

  4. 点击下一步。现在如下截图所示选择空白活动,然后再次点击下一步构建项目

  5. 在下一个屏幕上,只需将Activity Name更改为MainActivity,然后点击完成

提示

默认情况下,Android Studio 在每次启动时都会显示一个“每日提示”对话框。在您还在学习 Java 时,一些提示可能没有意义,但其中许多确实非常有用,并揭示了很多快捷方式和其他节省时间的方法。当它们出现时,花几秒钟时间阅读它们是非常值得的。正如已经讨论过的,Android Studio 是从 IntelliJ IDEA 构建的,您可以在www.jetbrains.com/idea/webhelp/keyboard-shortcuts-you-cannot-miss.html找到完整的键盘快捷键列表。

  1. 通过点击关闭清除每日提示

如果您是完全新手,那么代码、选项和文件可能看起来有点令人生畏。不要担心;在学习 Java 时,我们不需要关注它们中的大部分。当与更细节的东西互动的时候,我们将一步一步地进行。

也许很难相信,但我们刚刚创建了我们的第一个可工作的应用程序。我们可以在 Android 设备上构建和运行它,很快我们就会。

在我们继续进行游戏之前,让我们深入了解一下 Android Studio。

探索 Android Studio

Android Studio 是一个非常深入的工具,但只需要逐步学习其中的一部分就可以开始。对我们可能有用的是给 UI 的一些部分命名,这样在阅读本书时可以更容易地参考它们。

看一下这个编号的图表,以及对 Android Studio 一些关键部分的快速解释。如果可以的话,尽量记住这些部分,以便将来更容易地讨论它们。

探索 Android Studio

这是一个方便的表格,您可以快速参考并记住我们正在提到的 Android Studio 的哪个部分。接下来是对每个区域的更详细的解释。

编号名称
1项目资源管理器
2编辑器
3菜单栏
4工具栏
5导航栏
6重要的工具窗口
  • 项目资源管理器1):这在截图中显示为1,有点像 Windows 资源管理器。它显示了为我们的项目生成的所有文件和文件夹。随着本书的继续,我们将从这里做很多事情。实际上,如果您深入研究 Android Studio 创建的文件和文件夹,项目资源管理器并不是一个精确的映射。它稍微简化并突出显示,以便更轻松地管理和探索我们的项目。

  • 编辑器2):顾名思义,我们将在编辑器中编辑我们的 Java 代码文件。但是,正如我们很快将看到的,编辑器窗口会根据我们正在编辑的文件类型而发生变化。我们还将在这里查看和编辑 UI 设计。

  • 菜单栏3):像大多数程序一样,菜单栏为我们提供了访问 Android Studio 全部功能的途径。

  • 工具栏4):这包含了许多非常有用的一键选项,可以执行诸如部署和调试游戏等操作。将鼠标悬停在图标上,以获得弹出提示,并更深入地了解每个工具栏图标。

  • 导航栏5):就像文件路径一样,它显示了当前在编辑器中的文件在项目中的位置。

  • 重要的工具窗口6):这是一些选项卡,可以通过单击弹出并再次单击关闭。如果愿意,现在可以尝试一些选项卡,看看它们是如何工作的。

让我们更多地谈谈 Android Studio UI 的各个部分,以及编辑窗口如何转变为可视化 UI 设计师。之后,当我们足够熟悉时,我们将看看为我们的数学游戏构建一个简单的菜单屏幕。

使用 Android Studio 可视化设计师

Android Studio 编辑器窗口是一个非常动态的区域。它以最有用的方式呈现不同的文件类型。稍早一点,当我们创建项目时,它还为我们制作了一个基本的 UI。在 Android 中,UI 可以使用 Java 代码构建,或者,正如我们将看到的那样,在不需要一行 Java 代码的情况下使用可视化设计师。然而,正如我们在构建游戏菜单的 UI 之后将要调查的那样,要使 UI 做任何有用的事情,我们需要与之交互。这种交互总是通过 Java 代码完成的。可视化设计师还为我们生成 UI 代码。我们也会快速看一下那个。

随着书籍的进展,我们将主要避开 Android UI 开发,因为这是更多非游戏应用的基本功能。相反,我们将花更多时间直接绘制像素和图像来制作我们的游戏。尽管如此,常规的 Android UI 也有其用途,而 Android Studio 可视化设计师是最快的入门方式。

现在让我们来看看:

  1. 在 Android Studio 项目资源管理器中,双击layout文件夹,以显示其中的activity_main.xml文件。这应该很容易看到,除非您已经折叠了目录。如果看不到layout文件夹,请使用项目资源管理器导航到它。它可以在 Android Studio 项目资源管理器中找到,路径为Math Game Chapter2/src/main/res/layout,如下面的截图所示:使用 Android Studio 可视化设计师

  2. 现在双击activity_main.xml以在编辑器窗口中打开它。加载一小段时间后,您将看到与下一个截图非常相似的东西。下一个截图显示了以前只包含我们代码的整个内容。正如您所看到的,以前只是一个文本窗口现在有了多个部分。让我们更仔细地看一下这个截图:

在前面标有(1)的截图中,称为Palette,您可以从可用的 Android UI 元素中进行选择,然后简单地点击并将它们拖放到您的 UI 设计中。区域(2)是您正在构建的 UI 的可视视图,您将从 Palette 中点击并拖动元素。在可视 UI 视图的右侧,您将看到Component Tree区域(3)。组件树允许您检查复杂 UI 的结构,并更轻松地选择特定元素。在此树下方是Properties面板(4)。在这里,您可以调整当前选定的 UI 元素的属性。这些可以是简单的东西,如颜色和大小,也可以是更高级的属性。

注意

请注意标有(5)的标签。这些标签允许您在 Android Studio 为此类型的布局文件提供的两个主要视图之间切换。正如您所看到的,这些视图是DesignText。设计视图是默认视图,并且显示在前面的截图中。文本视图还显示您正在建设的 UI,但它显示为我们自动生成的代码,而不是Palette元素和组件树。

我们不需要担心这段代码,因为它都是为我们处理的。不过,偶尔查看一下这个选项卡可能会有好处,这样我们就可以开始理解设计工具为我们生成的内容。但是,这并不是学习 Java 所必需的。这段代码称为可扩展标记语言XML)。

  1. 快速查看Text选项卡,完成后点击Design选项卡,我们将继续。

现在我们已经看到了可视设计师的概述,甚至还瞥见了它为我们生成的自动生成代码。我们可以更仔细地查看一些我们将在项目中使用的实际 UI 元素。

Android UI 类型

现在我们将快速浏览一些非常有用的 Android UI 元素,一些关键属性,以及如何将它们组合在一起制作 UI。这些将为我们介绍一些可能性以及如何使用它们。然后我们将快速使用我们所知道的知识来制作我们的菜单。

TextView

在可视 UI 区域,点击Hello world!。我们刚刚选择的是一个称为 TextView 的小部件。TextView 可以是像这样的小文本,也可以是大标题类型的文本,在我们的游戏菜单中可能会有用。

让我们尝试将另一个 TextView 拖放到我们的可视 UI 上:

  1. 在我们的调色板中Widgets标题下方,您可以看到多种类型的 TextView。它们在调色板中呈现为普通 TextView大文本中等文本小文本。将大文本小部件拖放到我们的可视设计中。不要立即放开。当您将其拖动到手机图像周围时,请注意 Android Studio 以图形方式显示不同的定位选项。在下一个屏幕截图中,您可以看到当被拖动的小部件位于中心时设计师的外观:TextView

  2. 在您想要小部件放置的位置松开鼠标左键。如果您在上一个屏幕截图中显示的位置放开,那么文本将如预期般出现在中心。

  3. 现在我们可以玩一下属性。在Properties窗口中,点击textSize右侧。您可能需要滚动查找。将值输入为100sp并按Enter键。注意文本变得更大。我们可以通过增加和减少在此处输入的值来调整文本的大小。单位sp代表缩放像素,只是一种尝试在不同屏幕密度下将文本缩放到适当的等效实际大小的测量系统。TextView

  4. 如果你喜欢的话,可以玩一些更多的属性,完成后,点击我们在可视化设计中创建的 TextView 以突出显示它。然后点击删除键来摆脱它。现在删除我们开始时存在的 TextView,上面写着Hello world!

布局元素

现在您有一个看似空白的屏幕。但是,如果您在设计预览中的任何位置单击,您将看到Properties窗口中仍然有一些选项。这个元素称为 RelativeLayout。它是作为基础提供的几种布局元素类型之一,用于控制和对齐布局小部件,如按钮、文本等。如果您查看Palette窗口的顶部,您将看到主要的布局选项。我们将在稍后实际构建游戏菜单时使用此布局元素。

ImageView 小部件

ImageViews 毫不奇怪地用于显示图像。在标准的 Android UI 中,这是一种快速将我们设计师的艺术品添加到我们的游戏中的方法:

  1. 以与刚才定位 TextView 相同的方式将ImageView元素拖放到设计中。ImageView元素可以在Widgets标题下方找到。现在将其放置在中心,或者通过拖动它在设计中玩一下选项。我们将在一会儿删除它;在真正删除之前,我们只是进行了一些探索。

  2. 属性窗口中,以与之前选择textSize属性相同的方式选择src属性。

  3. 注意,在选择后,您可以点击**...以获得更多选项。点击...并滚动到选项列表的底部。这些都是我们可以在这个 ImageView 中显示的所有图像文件。只是为了好玩,滚动到列表的底部,选择ic_launcher**,然后点击确定。我们可以使任何我们喜欢的图像可用,这是构建有吸引力的游戏菜单屏幕的一种简单而强大的方法。

  4. layout:width属性更改为150dp,将layout:height属性更改为150dp。单位dp是一种在具有非常不同像素数量的屏幕设备上保持相对恒定的元素和小部件大小的方法。

  5. 以与之前删除其他视图相同的方式删除 ImageView。

ButtonView

ButtonView 的使用可能已经被它的名称泄露了。尝试在我们的布局上单击并拖动一些按钮。请注意,有几种类型的 ButtonView,例如小按钮按钮,以及,如果您在小部件列表中继续向下查看,图像按钮。我们将使用常规的 ButtonView,简称为按钮

现在我们将对每个这些 Android UI 元素进行操作,以制作我们的游戏菜单。

注意

您可以从本书配套网站的代码下载部分下载整个示例。

使用示例代码

本书中的所有代码都是以项目形式组织的。如果一个项目跨越多个章节,那么每个章节都会提供一个已完成状态的项目。这有助于您看到进展,而不仅仅是最终结果。要在 Android Studio 中打开项目,只需按照以下说明操作:

  1. 下载本书的代码。

  2. Android Studio中,从菜单栏导航到文件 | 关闭项目

  3. 现在创建一个新的空白项目,就像之前一样。浏览到您下载本书代码的位置。

  4. 导航到Chapter2/MathGameChapter2文件夹。在这里,您将找到本章中创建的所有文件的代码。

  5. 使用诸如免费的 Notepad++之类的纯文本编辑器打开代码文件。

  6. 复制并粘贴到您的 Android Studio 项目中,或者只是按照您看到的代码进行比较。

提示

尽管本书提供了所需的每一行代码,但您仍然需要通过 Android Studio 为自己创建每个项目。然后,您可以简单地将代码的全部内容复制并粘贴到具有匹配名称的文件中,或者只是将您可能遇到困难的代码部分复制并粘贴。请记住,如果您创建了一个具有不同包名称的项目,那么您必须从提供的代码文件中省略包名称的代码行。当我们在本章后面更多地谈论包时,这一点将更加清晰。

让我们亲自看看如何做到这一切。

制作我们的游戏菜单

现在我们只是让我们的游戏菜单功能正常。稍后在第五章中,游戏和 Java 基础,我们将看到如何通过添加一些酷炫的动画来使菜单更具视觉吸引力和乐趣。

这是本教程中我们的目标:

制作我们的游戏菜单

在开始编码之前,您应该先在纸上设计您的布局。但是,Android Studio 的设计师非常友好,特别是对于简单的布局,有很强的论点,可以在布局设计师中实际完善您的设计。执行以下步骤创建游戏菜单:

  1. 通过依次单击它们然后依次点击删除键,从设计师中删除所有小部件。注意不要删除RelativeLayout布局元素,因为我们将把它用作所有其他元素的基础。

  2. 从面板中拖动一个Large Text元素到设计区域的顶部中心,并赋予它以下属性。请记住,您可以通过单击要更改的属性右侧来在Properties面板中更改属性。将text属性更改为My Math Gamesize更改为30sp

  3. 从面板中拖动一个ImageView元素到设计的中心,稍微低于之前的 TextView。将layout:width属性更改为150dp,将layout:height属性更改为150dp

  4. 现在点击并拖动三个按钮,分别为PlayHigh ScoresQuit。将它们垂直居中,放在之前的 ImageView 下方,一个接一个地放置,就像我们之前展示的设计一样。

  5. 点击顶部的按钮,配置text属性,并输入值Play

  6. 点击中间的按钮,配置text属性,并输入值High Scores

  7. 点击最低的按钮,配置text属性,并输入值Quit

  8. 由于按钮现在包含的文本量相对于彼此不同,它们的大小也会略有不同。您可以通过点击并拖动较小按钮的边缘来使它们与较大的按钮匹配,以匹配预期的布局。这与您在 Windows 中调整应用程序窗口大小的方式基本相同。

  9. 使用Ctrl + S保存项目,或者导航到File | Save All保存项目。

提示

如果您要在比设计师中显示的 Nexus 4 屏幕大得多或小得多的屏幕上测试游戏,那么您可能希望调整本教程中使用的spdp单位的值。

在多个设备上讨论 Android UI 的全部内容超出了本书的范围,也不需要为本书中的任何游戏做任何讨论。如果你想立即开始为不同的屏幕设计,请查看developer.android.com/training/multiscreen/index.html

您可以通过从下拉菜单中选择设备来查看其他设备上的菜单外观,如下面的屏幕截图所示:

制作我们的游戏菜单

在我们的菜单在实际设备上启动之前,让我们先看一下 Android 应用程序的结构以及我们在编写 Java 代码时如何使用该结构。

为 Android 构建我们的代码

如果您曾经使用过 Android 设备,您可能已经注意到它的工作方式与许多其他操作系统有很大不同。例如,您正在使用一个应用程序-比如您正在查看 Facebook 上的人们在做什么。然后您收到一封电子邮件通知,您点击电子邮件图标阅读它。在阅读电子邮件的过程中,您可能会收到 Twitter 通知,因为您正在等待您关注的某人的重要消息,所以您中断了阅读电子邮件并触摸了 Twitter 应用。

阅读推特后,你想玩愤怒的小鸟,但在第一次大胆的投掷中途,你突然想起了 Facebook 的帖子。所以你退出了愤怒的小鸟,点击了 Facebook 图标。

然后你恢复了 Facebook,可能是在你离开的同一个点。你本可以继续阅读邮件,决定回复推特,或者开始一个全新的应用程序。所有这些来回都需要操作系统进行相当多的管理,显然独立于各个应用程序本身。

在我们刚刚讨论的情境中,Windows PC 和 Android 之间的区别在于,虽然用户决定使用哪个应用程序,但 Android 操作系统决定何时关闭(销毁)应用程序。我们在编写游戏时需要考虑这一点。

生命周期阶段-我们需要知道的内容

Android 系统有不同的阶段,任何给定的应用程序都可以处于这些阶段中。根据阶段,Android 系统决定应用程序如何被用户查看,或者是否被用户查看。Android 有这些阶段,以便它可以决定哪个应用程序正在使用,并分配正确数量的资源,如内存和处理能力。但也允许我们作为游戏开发人员与这些阶段进行交互。如果有人退出我们的游戏接听电话呢?他们会失去他们的进度吗?

Android 有一个相当复杂的系统,简化一下以便解释,确保 Android 设备上的每个应用程序都处于以下阶段之一:

  • 正在创建

  • 开始

  • 恢复

  • 运行

  • 暂停

  • 停止

  • 被销毁

希望阶段列表看起来相当合乎逻辑。例如,用户按下 Facebook 应用图标,应用程序创建。然后它启动。到目前为止,所有都相当简单,但接下来的是恢复!如果我们能暂时接受应用程序在启动后恢复,那么一切都会变得清晰。

恢复后,应用程序正在运行。这是当 Facebook 应用程序控制屏幕,可能也控制着更多的系统内存和处理能力。那么我们之前从 Facebook 应用切换到电子邮件应用的例子呢?

当我们点击去读我们的电子邮件时,Facebook 应用程序可能已经进入了暂停阶段,电子邮件应用程序将进入正在创建阶段,然后是恢复,然后是运行。如果我们决定重新访问 Facebook,就像之前的情景一样,Facebook 应用程序可能会直接进入恢复阶段,然后再次运行,很可能会准确地停留在我们离开时的帖子上。

请注意,随时,Android 可以决定停止销毁一个应用程序,在这种情况下,当我们再次运行应用程序时,它将需要重新创建。因此,如果 Facebook 应用程序长时间不活动,或者愤怒的小鸟需要太多系统资源,以至于 Android 将销毁 Facebook 应用程序,那么我们之前阅读的确切帖子的体验可能会有所不同。

现在,如果所有这些阶段的东西开始变得令人困惑,那么你会高兴地知道,提到的唯一原因如下:

  • 你知道它存在

  • 我们偶尔需要与它交互

  • 我们将一步一步地进行

生命周期阶段 - 我们需要做什么

当我们制作游戏时,我们如何可能与这种复杂性进行交互?好消息是,当我们创建第一个项目时自动生成的 Android 代码大部分为我们处理了交互。

作为游戏开发人员,我们所要做的就是确保 Android 知道在每个阶段发生时该如何处理我们的应用程序。更好的消息是,除非我们覆盖默认处理,否则所有这些阶段都将被默认处理。

这意味着我们可以继续学习 Java 和制作游戏,直到我们遇到少数需要在游戏中做一些事情的情况,特别是在其中一个阶段。

将我们的游戏分成活动

我们编写的 Java 代码将被分成称为活动的部分或部分。我们可以将活动视为游戏的不同屏幕。例如,在游戏中,我们经常会为主屏幕创建一个活动,为游戏屏幕创建一个活动,以及为高分屏幕创建一个活动。

每个活动都将有自己的生命周期,并将进一步分成部分,这些部分将对应于我们刚刚讨论的 Android 阶段之一。在 Java 中,这些部分被称为方法。方法是 Java 编程中的一个重要概念。

然而,在这个阶段,我们只需要知道方法用于将我们编写的 Java 代码分隔开,并且一些方法是由 Android 系统提供的,以便我们可以轻松处理否则复杂的 Android 生命周期。

接下来的列表是 Android 为了我们的方便提供的方法的快速解释,以管理生命周期的各个阶段。为了澄清我们对生命周期阶段的讨论,方法被列在我们一直在讨论的相应阶段旁边。然而,正如您将看到的,方法名称本身已经相当清楚地说明了它们在哪里适用。

在列表中,还有关于何时使用给定方法以及在特定阶段进行交互的简要解释或建议。随着我们在书中的进展,我们将遇到大多数这些方法。我们将在本章后面看到onCreate方法。以下是列表:

  • onCreate:当活动正在创建时,将执行此方法。在这里,我们准备好一切游戏所需的东西,包括图形、声音,也许还有高分。

  • onStart:当应用程序处于启动阶段时执行此方法。

  • onResume:此方法在onStart之后运行,但也可以在我们的活动在先前暂停后恢复时进入,这可能是最合乎逻辑的。当应用程序被中断时,我们可能会重新加载先前保存的游戏情况,例如电话呼叫或用户运行其他应用程序。

  • onPause:当我们的应用程序暂停时发生。在这里,我们可能希望保存当前的游戏。您可能已经掌握了这些方法。

  • onStop:这与停止阶段有关。这是我们可能会撤消在onCreate中所做的一切的地方。如果我们到达这里,我们的活动很可能很快就会被销毁。

  • onDestroy:这是我们的活动最终被销毁时——我们拆除游戏的最后机会。如果我们到达这里,我们肯定会再次经历生命周期的各个阶段。

所有方法的描述及其相关阶段应该是直接的。也许,唯一真正的问题是关于运行阶段。正如我们将看到的,当我们在其他方法/阶段中编写代码时,onCreateonStartonResume方法将准备游戏,这将形成运行阶段。onPauseonStoponDestroy方法将随后发生。现在我们实际上可以看一下其中一个方法以及其他一些方法。

我们对 Java 的第一次了解

那么早些时候在创建新项目时 Android Studio 生成的所有代码呢?这些代码将使我们的游戏菜单生动起来。让我们仔细看一下。编辑窗口中的第一行代码是这样的:

package com.packtpub.mathgamechapter2;

这行代码定义了我们在创建项目时命名的包。随着书籍的进展,我们将编写跨越多个文件的更复杂的代码。我们创建的所有代码文件都需要清楚地定义它们所属的包,就像前一行代码一样。代码实际上并没有在我们的游戏中任何事情。还要注意,这行代码以分号(;)结束。这是 Java 语法的一部分,它表示代码行的结束。删除分号,您将会得到一个错误,因为 Android Studio 试图理解两行代码。如果您愿意,可以尝试一下。

提示

请记住,如果您要从下载包中复制和粘贴代码,这是可能会有所不同的一行代码,具体取决于您如何设置项目。如果代码文件中的包名称与您创建的包名称不同,请始终使用创建项目时的包名称。

要查看接下来的四行代码,您可能需要单击小**+图标以显示它们。Android Studio 试图通过简化我们对代码的视图来提供帮助。请注意,编辑窗口的侧边还有几个小-**图标。您可以展开和折叠它们以适应自己的需求,而不会影响程序的功能。如下截图所示:

我们第一次接触 Java

扩展代码后,您将看到这四行:

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem; 

请注意,所有前面的行都以单词import开头。这是一个指示,包括其他包在我们的游戏中,而不仅仅是我们自己的包。这非常重要,因为它使我们能够使用其他程序员的辛勤工作,本例中是安卓开发团队的辛勤工作。正是这些导入使我们能够使用我们之前讨论过的方法,并允许我们与安卓生命周期阶段进行交互。再次注意,所有行都以分号(;)结尾。

下一行介绍了 Java 的一个基本构建块,称为class。类是我们将在整本书中不断扩展知识和理解的内容。现在,先看一下这行代码,然后我们将详细讨论它:

public class MainActivity extends ActionBarActivity {

逐字逐句,以下是正在发生的事情。前一行是说:创建一个名为MainActivity的新public class,并基于(extendsActionBarActivity

你可能还记得,在创建这个项目时我们选择的名称是MainActivityActionBarActivity是由安卓开发团队编写的代码(称为类),它使我们能够将我们的 Java 代码放入安卓中。

如果您有敏锐的眼光,您可能会注意到这行末尾没有分号。但是,有一个左花括号({)。这是因为MainActivity包含了其余的代码。实际上,一切都是我们的MainActivity类的一部分,它是基于ActionBarActivity类/代码构建的。如果您滚动到编辑窗口底部,您将看到一个右花括号(}),这表示我们称为MainActivity的类的结束。

  • 我们现在不需要知道类如何工作

  • 我们将使用类来访问其中包含的一些方法的代码,而不需要做任何其他操作,我们已经默认地利用了我们之前讨论过的安卓生命周期方法

  • 我们现在可以自由选择是否、何时以及在这些类中定义的方法中覆盖或保留默认值

所以,ActionBarActivity类包含了使我们能够与安卓生命周期交互的方法。实际上,有许多不同的类使我们能够做到这一点,一会儿,我们将从使用ActionBarActivity更改为一个更合适的类,该类也执行刚才提到的所有操作。

提示

此时重要的不是正确理解 Java 类;只需了解您可以导入一个包,一个包可以包含一个或多个类,然后您可以使用这些类的功能或基于自己的 Java 程序。

在接下来的几章中,我们将经常遇到类。把它们看作是做事情的编程黑匣子。在第六章中,OOP – Using Other People's Hard Work,我们将打开这个黑匣子,真正掌握它们,甚至开始制作我们自己的类。

继续进行代码,让我们看看我们的类中实际包含的代码是做什么的。

在我们刚刚讨论过的关键行之后,直接是代码块:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

希望现在这些代码中的一些内容开始变得有意义,并与我们已经讨论过的内容联系起来。尽管确切的语法仍然会感觉有点陌生,但只要我们意识到发生了什么,我们就可以继续学习 Java。

在前面的代码中,我们注意到的第一件事是@override关键字。还记得我们说过所有与 Android 生命周期交互的方法都是默认实现的,我们可以自行选择是否以及何时覆盖它们吗?这就是我们在这里用onCreate方法所做的。

@override关键字表示接下来的方法被覆盖。protected void onCreate(Bundle savedInstanceState) {行包含我们正在覆盖的方法。你可能能猜到,动作从问题行的{开始,三行后以}结束。

在方法名onCreate之前和方法名后的(Bundle savedInstanceState)看起来有点奇怪,但这些在这个时候并不重要,因为它们已经为我们处理了。这与数据在我们程序的各个部分之间传递有关。我们只需要知道这里发生的事情将在 Android 生命周期的创建阶段发生。其余内容将在第四章发现循环和方法中变得清晰。让我们继续到下一行:

super.onCreate(savedInstanceState);

在这里,super关键字引用了原始的onCreate方法中的代码,即使我们看不到它,它仍然存在。代码的意思是:尽管我正在覆盖你,但我希望你首先像往常一样设置好一切。然后,在onCreate完成了我们看不到也不需要看到的大量工作之后,方法继续进行,我们实际上可以用这行代码做一些事情:

setContentView(R.layout.activity_main);

在这里,我们告诉 Android 设置主内容视图(我们用户的屏幕),这是我们之前创建的酷炫游戏菜单。具体来说,我们声明它是layout文件夹中的R或资源,文件名为activity_main

清理我们的代码

接下来的两个代码块是由 Android Studio 创建的,假设我们想要覆盖另外两个方法。但我们不需要,因为这些方法更常用于非游戏应用程序:

  1. 删除以下代码中显示的整个内容。注意不要删除我们MainActivity类的结束大括号:
@Override
    public boolean onCreateOptionsMenu(Menu menu) {

        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
  1. 现在我们可以删除一些@import语句。这是因为我们刚刚删除了不再需要的类的覆盖方法(之前导入的)。请注意编辑器窗口中以下行是灰色的。请注意,如果你保留它们,程序仍然可以正常工作。现在删除它们,以使你的代码尽可能清晰:
import android.view.Menu;
import android.view.MenuItem;
  1. 在我们的代码完成之前进行一些最终修改:此时,你可能会认为我们已经删除和更改了我们的代码很多,以至于我们可能干脆从空白页面开始重新输入。这几乎是正确的。但是,让 Android Studio 为我们创建一个新项目,然后进行这些修改的过程更加彻底,也避免了很多步骤。以下是最后的代码更改。将import android.support.v7.app.ActionBarActivity;行更改为import android.support.app.Activity;

  2. 现在你会看到我们的代码下面有几条红线标出错误。这是因为我们试图使用一个尚未导入的类。只需将public class MainActivity extends ActionBarActivity {行更改为public class MainActivity extends Activity {

我们对最后两个更改所做的是使用Activity类的稍微更合适的版本。为此,我们还必须更改我们导入的内容。

完成后,你的编辑器窗口应该看起来像这样:

package com.packtpub.mathgamechapter2.mathgamechapter2;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;

public class MainActivity extends ActionBarActivity {

    @Override
    protected void onCreate(Bundle savedInstancePhase) {
        super.onCreate(savedInstancePhase);
        setContentView(R.layout.activity_main);
    }

}

提示

下载示例代码

你可以从www.packtpub.com的账户中下载你购买的所有 Packt Publishing 图书的示例代码文件。如果你在其他地方购买了这本书,你可以访问www.packtpub.com/support并注册,将文件直接发送到你的邮箱。

现在我们知道发生了什么,我们的代码干净而简洁,我们实际上可以看一下我们游戏的开头部分在运行中的样子!

提示

如果我们刚才讨论的任何内容看起来复杂,不用担心。安卓强制我们在 Activity 生命周期内工作,所以之前的步骤是不可避免的。即使你没有完全理解关于类和方法等的所有解释,你仍然完全有能力从这里学习 Java。随着书的进展,所有的类和方法都会变得更加简单明了。

构建和安装我们的游戏

很快,我们将实际看到我们的菜单在运行。但在这之前,我们需要找出如何使用安卓模拟器以及如何构建我们的游戏。然后我们将把这些放在一起,把我们的游戏放入模拟器或真实设备中,以便像我们的玩家一样看到它。

模拟器和设备

现在我们已经准备好运行游戏的第一部分。我们需要测试它,检查是否有任何错误、崩溃或其他意外情况。还要确保它在您想要定位的设备类型/尺寸上看起来好并且运行正确。

注意

我们不会详细讨论处理不同设备类型的细节。我们所有的游戏都是全屏的,我们将稍后锁定方向并动态计算屏幕分辨率等方面。所以我们可以只为一个设备类型编写,并专注于学习 Java。

现在知道,你可以为任何屏幕尺寸分类或像素密度创建不同的布局文件。你所需要做的就是将布局文件放在适当的文件夹中,使用完全相同的文件名。安卓设备将知道最适合它使用的布局。有关详细讨论,请参阅 Google 开发者网站developer.android.com/guide/practices/screens_support.html

请注意,您不需要理解上述链接中的任何信息就可以学习 Java 并发布您的第一个游戏。

有几种方法可以做到这一点,我们将看两种。首先,我们将使用 Android Studio 和 Android 开发工具来制作一个设备模拟器,这样我们就可以在同一台 PC/Mac 上使用、测试和调试我们的游戏在各种设备模拟器上。所以我们不需要拥有一个设备。这将允许我们从我们的游戏中获得崩溃报告。

然后我们将直接将游戏安装到真实设备上,以便我们可以看到当他们下载我们的应用时,设备的所有者将看到什么。

还有更多的选择。例如,您可以通过 USB 连接真实设备,并在 Android Studio 中直接调试设备上的错误和语法反馈。这个过程可能因不同的设备而有所不同,由于我们只关注基本的调试,我们不会在本书中涵盖这个内容。

创建一个模拟器

让我们启动我们的模拟器:

  1. 在 Android Studio 快速启动栏的右侧,找到 AVD 管理器图标:创建模拟器

  2. 点击图标启动 Android 虚拟设备管理器。然后点击左下角的创建虚拟设备...按钮,打开虚拟设备配置窗口。

  3. 现在点击Nexus 4选项,然后点击下一步

  4. 现在我们需要选择我们将在其上构建和测试我们的游戏的安卓版本。最新版本(写作时)是Lollipop - 21 - x86。这是唯一一个我们不需要完成下载就可以继续的选项。所以选择它(或者在您阅读本文时的默认选项),然后点击下一步继续。

  5. 在下一个屏幕上,我们可以保留所有默认设置。所以点击完成

我们现在有一个可以直接运行的安卓模拟器。

运行模拟器

现在我们将启动(打开)我们的虚拟设备,然后通过以下步骤实际运行我们之前制作的游戏:

  1. 点击Name列下的Nexus 4 API 21。现在点击描述我们的模拟器右侧的三角形播放图标。

注意

安卓模拟器需要很长时间才能启动。即使在高规格的 PC 上也是如此。预计至少要等待几分钟,甚至 10 分钟。

  1. 一旦启动,通过点击和拖动模拟设备屏幕的任何位置来解锁设备。这类似于在真实的 Nexus 4 上滑动解锁。当我们的 Nexus 4 虚拟设备运行并解锁时,它看起来是这样的:Running the emulator

您可以几乎以与真实安卓设备相同的方式玩这个模拟器。但是,您无法从 Google Play 下载应用。您可能会注意到,与真实设备相比,甚至与旧设备相比,模拟器速度有点慢。不久,我们将看看如何在真实设备上运行我们的应用程序。

在模拟器上运行我们的游戏

一旦模拟器运行起来,通常最好让它保持运行,这样每次我们想要使用它时,就不必等待它启动。让我们使用模拟器:

  1. 如果模拟器尚未运行,请启动它,并确保设备已按先前描述的方式解锁。

  2. 点击工具栏中的运行图标(如下所示)来运行您的应用程序。您也可以通过从菜单栏导航到Run | Math Game Chapter 2来实现相同的功能:Running our game on the emulator

  3. 在 Android Studio 构建我们的应用程序时,弹出对话框将询问您要在哪个设备上运行该应用程序。选择描述中带有Nexus 4 API 21的设备。这是我们之前创建的已经运行的设备。现在按OK

  4. 注意此时在 Android Studio 的底部部分出现了有用的安卓窗口。如果您遇到任何问题,只需检查代码中的拼写错误。如果事情真的不顺利,只需返回到使用示例代码部分,与提供的代码进行比较或复制粘贴。

在另一段暂停后,我们的游戏菜单屏幕将出现在模拟器上。当然,它现在还没有做任何事情,但它正在运行,按钮可以被按下。

完成后,您可以按返回或主屏幕图标退出应用程序,就像在真实的安卓设备上一样。

现在我们已经看到了一种我们可以通过在安卓模拟器中运行来测试我们的应用程序的方法。让我们找出如何将我们的代码制作成一个可以在真实设备上分发和使用的应用程序。

构建我们的游戏

在真实的安卓设备上运行我们的游戏,我们需要创建一个以.apk结尾的文件,也就是一个以.apk为扩展名的文件。.apk文件是安卓系统用来运行和安装我们的应用程序的文件和文件夹的压缩存档。以下是使用安卓工作室制作我们游戏的.apk的步骤:

  1. 从菜单栏导航到Build | Generate Signed APK

  2. 一个略显冗长的窗口将弹出并显示:对于基于 Gradle 的项目,签名配置应在 Gradle 构建脚本中指定。您可以点击OK安全地关闭此窗口。

  3. 接下来是Generate Signed APK Wizard对话框。在这里,我们正在创建一个标识持有者被授权分发 APK 的密钥。在此过程结束时,您将获得一个.keys文件,您可以在每次构建.apk文件时使用。因此,这一步在将来可以省略。点击Create new按钮。

  4. Key Store Path字段中,键入或转到您的硬盘上希望存储密钥的位置。然后,您将被提示选择密钥库的文件名。这是任意的。键入MyKeystore并点击OK

  5. Password字段中输入密码,然后在Confirm字段中重新输入。这是用于保护您的密钥的存储的密码。

  6. 接下来,在别名字段中,输入一个易记的别名。您可以将其视为密钥的用户名。再次在密码字段中输入密码,然后在确认字段中重新输入。这是您的密钥密码。

  7. 有效年限下拉菜单保持默认的25

  8. 然后您可以填写您的姓名和组织详情(如果有),然后点击确定

  9. 现在我们的密钥和密钥库已经完成,我们可以在生成已签名 APK 向导对话框上点击确定

  10. 然后我们被提示选择运行 Proguard。在这个时候,加密和优化我们的.apk是不必要的。所以只需点击完成生成我们应用程序的.apk文件。

  11. 生成的.apk文件将放在您选择放置项目文件的同一目录中。例如,MathGameChapter2/app

我们现在已经构建了一个.apk文件,可以在我们首次创建项目时指定的任何 Android 设备上运行。

将设置安装到设备

所以我们有了.apk文件,也知道了在哪里找到它。以下是我们将在 Android 设备上运行它的方法。

我们可以使用多种方法将.apk文件传输到设备中。我发现其中一种最简单的方法是使用云存储服务,比如 Dropbox。然后您只需点击并拖动.apk文件到您的 Dropbox 文件夹,就完成了。或者,您的 Android 设备可能附带了 PC 同步软件,允许您将文件拖放到设备中。在您将.apk文件放入 Android 设备后,继续进行教程。

大多数 Android 手机默认设置为只能从 Google Play 商店以外的地方安装应用程序。所以我们需要改变这一点。您将要导航到的确切菜单在您的设备上可能略有不同,但以下选项在大多数设备上几乎是相同的,无论新旧:

  1. 找到并点击设置应用。大多数 Android 手机也有设置菜单选项。任何一个都可以。现在选择安全,并滚动到未知来源选项。点击未知来源复选框,允许从未知来源安装应用程序。

  2. 使用 Dropbox 应用或您设备的文件浏览器在您的 Android 设备上找到文件,具体取决于您选择的方法将 APK 放入设备。点击MathGameChapter2.apk文件。

  3. 现在您可以像安装其他应用程序一样安装该应用程序。在提示时,点击安装,然后点击打开。游戏现在将在您的设备上运行。

将您的设备保持竖直方向,因为这是 UI 设计的方式。恭喜您在自己的设备上运行自己的 Android 应用程序。在数学游戏的以后版本中,我们将锁定方向,使其更加用户友好。

未来的项目

在整本书中,我们将测试和运行我们的游戏项目。你可以自行选择我们讨论过的方法中的哪一种。如果出现崩溃或无法解释的错误,那么您需要使用模拟器。如果一切正常,那么最快、最令人愉悦的方式可能就是在您拥有的设备上运行它。

自测问题

Q1)如果所有这些关于生命周期、类和方法的讨论让您感到困惑,您应该怎么办?

Q2)什么是 Java 类?

Q3)方法和类之间有什么区别?

Q4)查看 Android 开发者网站,以及其更详细的生命周期阶段解释,网址为developer.android.com/reference/android/app/Activity.html。您能看到我们没有讨论过的阶段及其相关方法吗?在应用程序中它会在什么时候触发?从创建到销毁,活动的确切路径是什么?

总结

到目前为止,我们讨论了完全理解代码的工作原理并不重要。这是因为它将仅充当我们在本书其余部分中编写的代码的容器。然而,当我们在第四章中详细讨论诸如方法和第六章中的类时,我们将开始理解我们游戏中的所有代码。

我们详细讨论了有些复杂的 Android 生命周期。我们了解到,我们现阶段需要理解的是,我们必须在与生命周期不同阶段相关的正确方法中编写我们的代码。然后,学习 Java 就不会有任何困难。与类和方法一样,一切都将在实践中得到解释并变得更加清晰。

我们还学习了 Android Studio UI 的关键领域。我们使用 Android Studio 设计师构建了我们数学游戏的开始菜单。此外,我们创建了必要的 Java 代码,使游戏出现在玩家的设备上。这主要是通过修改为我们自动生成的代码来实现的。

这可能是本书中最困难的一章,因为我们需要介绍一些东西,比如 Java 类、Java 方法和 Android 生命周期。我们这样做是因为我们需要了解在学习 Java 时周围发生的事情。

然而,从现在开始,我们可以循序渐进地以非常合乎逻辑的方式进行。如果你已经达到了这一点,那么你将毫无问题地完成本书中最艰难的项目。

如果这一章让你的大脑有点疼,那么请放心,你已经走到了这一步,这非常好地表明你将来会成为 Java 高手。从基础开始,让我们现在学习一些 Java。

第三章:说 Java-你的第一个游戏

在这一章中,我们将开始编写我们自己的 Java 代码,同时开始理解 Java 语法。我们将学习如何存储、检索和操作存储在内存中的不同类型的值。我们还将研究如何根据这些数据的值做出决策和分支我们代码的流程。

按照这个顺序,我们将:

  • 学习一些 Java 语法,并看看编译器是如何将其转换为运行应用程序的

  • 存储数据并使用变量

  • 学习如何用 Java 表达自己

  • 通过提问继续数学游戏

  • 学习 Java 中的决策

  • 通过获取和检查答案继续数学游戏

获得前述的 Java 技能将使我们能够构建我们数学游戏的下两个阶段。这个游戏将能够向玩家提出一个乘法问题,检查答案,并根据给出的答案给出反馈,如下图所示:

说 Java-你的第一个游戏

Java 语法

在本书中,我们将使用简单的英语讨论一些相当技术性的事情。你永远不会被要求阅读一个之前没有以非技术方式解释过的 Java 或 Android 概念的技术解释。

偶尔,我可能会要求或暗示您接受一个简化的解释,以便在更合适的时候提供更完整的解释,比如 Java 类作为一个黑匣子;但是,你永远不需要匆忙去谷歌以理解一个大词或充满术语的句子。

话虽如此,Java 和 Android 社区充满了讲技术术语的人,要加入并从这些社区中学习,你需要理解他们使用的术语。因此,本书的方法是使用完全简单的语言学习概念或欣赏想法,同时将行话作为学习的一部分。

然后,许多行话将开始显示其有用性,通常作为澄清的一种方式,或者避免解释/讨论变得比必要的更长。

“Java 语法”这个术语本身可能被认为是技术性的或术语性的。那么它是什么?Java 语法是我们将 Java 语言元素组合在一起以便在 Java/Dalvik 虚拟机中运行的代码。语法也应尽可能清晰地呈现给人类读者,尤其是在未来重新访问我们的程序时。Java 语法是我们使用的词和将这些词组成类似句子的结构的组合。

这些 Java 元素或词汇数量众多,但分成小块学习几乎肯定比任何人类语言更容易。原因在于 Java 语言及其语法是专门设计为尽可能简单明了。我们还有 Android Studio 在我们这边,它通常会告诉我们如果我们犯了错误,有时甚至会提前思考并提示我们。

我相信如果你能阅读,你就能学会 Java;因为学习 Java 非常容易。那么,是什么让一个完成了初级 Java 课程的人与一个专业程序员分开呢?同样的东西也分开了语言学生和大师诗人。语言的掌握来自于实践和进一步的学习。

在最后一章中,我将向你展示正确的方向,如果你想自己掌握 Java。

编译器

编译器是将我们可读的 Java 代码转换为可以在虚拟机中运行的另一段代码的东西。这被称为编译。Dalvik 虚拟机将在我们的玩家点击我们应用程序图标时运行这段编译后的代码。除了编译 Java 代码,编译器还会检查错误。虽然我们的发布应用程序可能仍然存在错误,但许多错误是在编译我们的代码时发现的。

用注释清晰地编写代码

随着你在编写 Java 程序方面变得更加高级,你用来创建程序的解决方案将变得更长、更复杂。此外,正如我们将在后面的章节中看到的,Java 被设计为通过将代码分成单独的块来管理复杂性,往往跨越多个文件。

注释是 Java 程序的一部分,在程序本身中没有任何功能。编译器会忽略它们。它们用于帮助程序员记录、解释和澄清他们的代码,以便在以后更容易理解自己或其他需要使用或修改代码的程序员。

因此,一个好的代码片段将大量地添加这样的行:

//this is a comment explaining what is going on

前面的注释以两个斜杠字符//开头。注释在行末结束。这被称为单行注释。因此,该行上的任何内容只供人类阅读,而下一行上的任何内容(除非是另一个注释)都需要是语法正确的 Java 代码。

//I can write anything I like here
but this line will cause an error

我们可以使用多个单行注释:

//Below is an important note
//I am an important note
//We can have as many single line comments like this as we like

单行注释也很有用,如果我们想暂时禁用一行代码。我们可以在代码前面加上//,这样它就不会包含在程序中。回想一下这段代码,它告诉 Android 加载我们的菜单 UI:

//setContentView(R.layout.activity_main);

在前面的情况下,当运行时菜单不会被加载,应用程序将有一个空白屏幕,因为整行代码被编译器忽略。Java 中还有另一种类型的注释——多行注释。这对于较长的注释以及在代码文件顶部添加版权信息等内容非常有用。与单行注释一样,它可以用于暂时禁用代码,通常是多行代码。

在前导/*和结束*/之间的所有内容都会被编译器忽略。以下是一些例子:

/*
This program was written by a Java expert
You can tell I am good at this because my
code has so many helpful comments in it.
*/

多行注释中的行数没有限制。使用哪种类型的注释将取决于情况。在本书中,我将始终明确解释每一行代码,但你经常会在代码本身中发现大量的注释,这些注释会进一步解释、洞察或澄清。因此,阅读所有代码总是一个好主意:

/*
The winning lottery numbers for next Saturday are
9,7,12,34,29,22
But you still want to learn Java? Right?
*/

提示

所有优秀的 Java 程序员都会在他们的代码中大量使用注释。

存储数据并使用变量

我们可以将变量看作是带有标签的存储箱。它们也像是程序员对 Android 设备内存或者我们正在编程的任何设备的窗口。变量可以在内存中存储数据(存储箱),在需要时通过适当的标签进行调用或更改。

计算机内存具有高度复杂的寻址系统,幸运的是,在 Java 中我们不需要与之交互。Java 变量允许我们为程序中需要处理的所有数据编写方便的名称;JVM 将处理与操作系统交互的所有技术细节,而操作系统可能通过几层传递交互与硬件。

因此,我们可以将我们的 Android 设备内存想象成一个巨大的仓库。当我们为变量分配名称时,它们被存储在仓库中,等待我们需要它们时。当我们使用变量的名称时,设备会准确知道我们在引用什么。然后我们可以告诉它做一些事情,比如“拿出 A 箱并将其添加到 C 箱,删除 B 箱”,等等。

在游戏中,我们可能会有一个名为score的变量。正是这个score变量,我们用来管理与用户分数相关的任何事情,比如增加、减少或者只是向玩家展示分数。

可能出现以下一些情况:

  • 玩家回答正确一个问题,所以将 10 加到他们现有的score

  • 玩家查看他们的统计屏幕,所以在屏幕上打印score

  • 玩家获得了有史以来最高的分数,所以将hiScore设置为他们当前的score

这些都是相当任意的变量名称示例,只要你不使用 Java 限制的字符关键字,你实际上可以随意命名你的变量。然而,在实践中,最好采用一种命名约定,这样你的变量名称将是一致的。在本书中,我们将使用一个变量名称以小写字母开头的宽松约定。当变量的名称中有多个单词时,第二个单词将以大写字母开头。这被称为“驼峰命名法”。

以下是一些驼峰命名的示例:

  • score

  • hiScore

  • playersPersonalBest

在我们看一些带有变量的真正的 Java 代码之前,我们首先需要看一下我们可以创建和使用的变量的类型。

变量类型

可以想象,即使是一个简单的游戏可能也会有相当多的变量。在前一节中,我们介绍了hiScore变量作为一个例子。如果游戏有一个记住前 10 名玩家姓名的高分榜,那么我们可能需要为每个玩家创建变量。

那么当游戏需要知道可玩角色是死了还是活着,或者还有几条命/重试机会时呢?我们可能需要一些代码来测试生命,然后在可玩角色死亡时结束游戏并播放一个漂亮的血花动画。

计算机程序中的另一个常见要求,包括游戏,是正确或错误的计算:真或假。

为了涵盖您可能想要跟踪的这些和许多其他类型的信息,Java 有类型。有许多类型的变量,正如我们将在第六章中看到的,OOP – Using Other People's Hard Work,我们也可以发明自己的类型或使用其他人的类型。但现在,我们将看看内置的 Java 类型。公平地说,它们几乎涵盖了我们可能会遇到的每种情况。一些示例是解释这种类型的东西的最佳方式。

我们已经讨论了假设但非常可能的score变量。变量score很可能是一个数字,所以我们必须通过给分数一个适当的类型来向 Java 编译器传达这一点(分数是一个数字)。同样假设但同样可能的playerName当然将保存组成玩家姓名的字符。再往前跳几段,保存常规数字的类型称为int,保存类似姓名的数据的类型称为String。如果我们试图将一个玩家的名字,比如“Ada Lovelace”,存储在score中,而score是用于数字的,我们肯定会遇到麻烦。

编译器说不行!实际上,错误会说:

变量类型

正如我们所看到的,Java 被设计成不可能让这样的错误出现在运行的程序中。你是否也注意到在之前的截图中,我忘记了在行末加上分号?有了这个编译器来识别我们的错误,可能会出现什么问题呢?

以下是 Java 中的主要类型。稍后,我们将看到如何开始使用它们:

  • int:这种类型用于存储整数。它使用 32 位内存,因此可以存储略大于 20 亿的值,包括负值。

  • long:顾名思义,当需要更大的数字时可以使用这种数据类型。long数据类型使用 64 位内存,我们可以在这种类型中存储 2 的 63 次方。如果你想看看它是什么样子,试试这个:9,223,372,036,854,775,807。也许令人惊讶的是,long变量也有用处,但如果较小的变量可以胜任,我们应该使用它,这样我们的程序就会使用更少的内存。

注意

你可能会想知道何时会使用这么大的数字。明显的例子可能是进行复杂计算的数学或科学应用,但另一个用途可能是用于计时。当你计算某事花费的时间时,Java Date类使用自 1970 年 1 月 1 日以来的毫秒数。long数据类型可能有用,用于从开始时间减去结束时间以确定经过的时间。我们将在第五章游戏和 Java 基础中使用long

  • float:这是用于浮点数的,也就是说,小数点后有精度的数字。由于数字的小数部分占用的内存空间与整数部分一样,因此与非浮点数相比,浮点数的可能范围会减少。因此,除非我们的变量肯定会使用额外的精度,否则浮点数不会是我们的数据类型选择。

  • double:当float中的精度不够时,我们有double

  • short:当即使是int数据类型都过度时,超薄的 short 适合于最小的存储盒,但我们只能存储大约 64,000 个值,从-32,768 到 32,767。

  • byte:这是比 short 类型更小的存储盒。内存中有足够的空间,但一个字节只能存储-128 到 127 的值。

  • boolean:我们将在整本书中大量使用布尔值。布尔变量只能是真或假,没有其他选项。也许布尔值可以回答这样的问题:

  • 玩家还活着吗?

  • 是否达到了新的最高分?

  • 布尔变量的两个例子足够吗?

  • char:这存储一个单个的字母数字字符。它本身不会改变任何东西,但如果我们把它们放在一起,它可能会有用。

提示

我将这种数据类型的讨论保持在一个在本书的上下文中有用的实际水平上。如果你对数据类型的值是如何存储的以及为什么限制是什么感兴趣,请访问 Oracle Java 教程网站docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html。请注意,你不需要比我们已经讨论过的更多信息来继续阅读本书。

正如我们刚刚学到的,我们可能想要存储的每种数据类型都需要特定的内存量。因此,在我们开始使用变量之前,我们必须让 Java 编译器知道变量的类型。

前面的变量被称为原始类型。它们使用预定义的内存量,因此,使用我们的存储类比,适合于预定义大小的存储盒。

正如“原始”标签所示,它们不像引用类型那样复杂。

引用类型

你可能已经注意到,我们没有涵盖我们之前用来介绍变量概念的String变量类型。

字符串是一种特殊的变量类型,称为引用类型。它们简单地指向内存中变量存储开始的位置,但引用类型本身并不定义特定的内存量。这样做的原因非常简单:我们并不总是知道在程序实际运行之前需要存储多少数据。

我们可以将字符串和其他引用类型视为不断扩展和收缩的存储盒。那么这些String引用类型中的一个不会最终碰到另一个变量吗?如果你把设备的内存想象成一个满是标记存储盒的货架的巨大仓库,那么你可以把达尔维克虚拟机想象成一个超级高效的叉车司机,把不同类型的存储盒放在最合适的地方。

如果有必要的话,虚拟机会在一秒钟内迅速移动物品,以避免碰撞。在适当的时候,它甚至会焚毁不需要的存储盒。这与不断卸载各种类型的新存储盒并将它们放在最佳位置同时发生,对于该类型的变量。Dalvik 倾向于将引用变量保存在仓库的一个部分,这与原始变量的部分不同,我们将在第六章中了解更多细节,OOP – 使用其他人的辛勤工作

因此,字符串可以用来存储任何键盘字符,就像char数据类型,但长度几乎可以是任意的。从玩家的名字到整本书都可以存储在一个字符串中。我们将经常使用字符串,包括在本章中。

还有一些我们将要探索的引用类型。数组是一种存储大量相同类型变量的方法,可以快速高效地访问。我们将在第五章中研究数组,游戏和 Java 基础

将数组视为仓库中的一条通道,其中按照精确的顺序排列了某种类型的所有变量。数组是引用类型,因此 Dalvik 将它们保存在与字符串相同的仓库部分。

另一种引用类型是神秘的对象或类,我们将在第六章中进行讨论,OOP – 使用其他人的辛勤工作

因此,我们知道我们可能想要存储的每种数据类型都需要一定的内存。因此,在开始使用变量之前,我们必须让 Java 编译器知道变量的类型。

声明

这就够了。让我们看看我们实际上如何使用我们的变量和类型。请记住,每种原始类型都需要特定数量的真实设备内存。这是编译器需要知道变量类型的原因之一。因此,在尝试对其进行任何操作之前,我们必须首先声明变量及其类型。

要声明一个名为scoreint类型的变量,我们会这样输入:

int score;

就是这样!只需声明类型,比如int,然后留下一个空格,输入您要用于此变量的名称。还要注意分号,像往常一样在行尾,以告诉编译器我们已经完成了这一行,接下来的内容(如果有的话)不是声明的一部分。

对于几乎所有其他变量类型,声明都是以相同的方式进行的。以下是一些示例。变量名是任意的。这就像在仓库中预留一个带标签的存储盒:

long millisecondsElapsed;
float gravity;
double accurateGravity;
boolean isAlive;
char playerInitial;
String playerName;

初始化

在这里,对于每种类型,我们将一个值初始化到变量中。想象一下将一个值放入存储盒中,就像下面的代码所示:

score = 0;
millisecondsElapsed = 1406879842000;//1st Aug 2014 08:57:22
gravity = 1.256;
double accurateGravity =1.256098;
isAlive = true;
playerInitial = 'C';
playerName = "Charles Babbage";
and initialize the same variables as we did previously, but in one step each:
int score = 0;
long millisecondsElapsed = 1406879842000;//1st Aug 2014 08:57:22
float gravity = 1.256;
double accurateGravity =1.256098;
boolean isAlive = true;
char playerInitial = 'C';
String playerName = "Charles Babbage";

注意

我们是否分开声明和初始化可能取决于具体情况。重要的是我们必须两者都做:

int a;
//The line below attempts to output a to the console
Log.i("info", "int a = " + a);

上述代码将导致以下结果:

Compiler Error: Variable a might not have been initialized

这个规则有一个重要的例外。在某些情况下,变量可以有默认值。我们将在第六章中看到这一点,但是良好的做法是同时声明和初始化变量。

使用运算符更改变量

当然,在几乎任何程序中,我们都需要对这些值进行操作。以下是可能是最常见的 Java 运算符列表,它们允许我们操作变量。您不需要记住它们,因为我们第一次使用它们时会逐行查看代码:

  • 赋值运算符(=):这使得操作符左边的变量与右边的值相同。例如,hiScore = score;score = 100;

  • 加法运算符(+):这会将运算符两侧的值相加。通常与赋值运算符一起使用,例如score = aliensShot + wavesCleared;score = score + 100;。请注意,同时在运算符的两侧使用相同的变量是完全可以接受的。

  • 减法运算符(-):这会将运算符右侧的值从左侧的值中减去。通常与赋值运算符一起使用,例如lives = lives - 1;balance = income - outgoings;

  • 除法运算符(/):这会将左侧的数字除以右侧的数字。同样,通常与赋值运算符一起使用,如fairShare = numSweets / numChildren;recycledValueOfBlock = originalValue / .9;

  • 乘法运算符(*):这会将变量和数字相乘,例如answer = 10 * 10;biggerAnswer = 10 * 10 * 10;

  • 递增运算符(++):这是一个非常简洁的方法,可以将变量的值加 1。myVariable = myVariable + 1;语句与myVariable++;相同。

  • 递减运算符(--):你猜对了:这是一个非常简洁的方法,可以从某个值中减去 1。myVariable = myVariable -1;语句与myVariable--;相同。

注意

这些运算符的正式名称与此处用于解释的名称略有不同。例如,除法运算符实际上是乘法运算符之一。但是,前面的名称对于学习 Java 来说更有用,如果您在与 Java 社区的某人交谈时使用术语“除法运算符”,他们会完全明白您的意思。

实际上,Java 中有比这更多的运算符。当我们在本章后面学习 Java 中的决策时,我们将看到更多的运算符。

提示

如果您对运算符感到好奇,可以在 Java 网站上找到完整的运算符列表docs.oracle.com/javase/tutorial/java/nutsandbolts/operators.html。本书中完成项目所需的所有运算符都将在本书中得到充分解释。链接是为我们中的好奇者提供的。

用 Java 表达自己

让我们尝试使用一些声明、赋值和运算符。当我们将这些元素捆绑到一些有意义的语法中时,我们称之为表达式。因此,让我们编写一个快速应用程序来尝试一些内容。

在这里,我们将制作一个小的辅助项目,以便我们可以玩转到目前为止学到的所有内容。我们需要创建一个新项目,就像我们在上一章中所做的那样,但这次我们不需要 UI。

相反,我们将简单地编写一些 Java 代码,并通过将变量的值输出到名为logcat的 Android 控制台来检查其效果。我们将通过构建简单的项目并检查代码和控制台输出来看到这是如何工作的:

提示

以下是如何创建新项目的快速提醒。

  1. 通过导航到文件 | 关闭项目关闭当前打开的任何项目。

  2. 点击开始一个新的 Android Studio 项目

  3. 创建新项目配置窗口将出现。在应用程序名称字段和公司域中填写packtpub.com,或者您可以在此处使用您自己公司的网站名称。

  4. 现在点击下一步按钮。在下一个屏幕上,确保手机和平板电脑复选框中有一个勾选。现在我们必须选择我们想要为其构建应用程序的最早版本的 Android。随便尝试一下下拉选择器中的几个选项。您会发现我们选择的版本越早,我们的应用程序支持的设备百分比就越大。但是,这里的权衡是,我们选择的版本越早,我们的应用程序中可用的最新 Android 功能就越少。一个很好的平衡是选择API 8:Android 2.2(Froyo)

  5. 单击下一步。现在选择空白活动,然后再次单击下一步

  6. 在下一个屏幕上,只需将Activity Name更改为MainActivity,然后单击Finish

  7. 就像我们在第二章中所做的那样,开始使用 Android,为了保持我们的代码清晰简单,您可以删除两个不需要的方法(onCreateOptionsMenuonOptionsItemSelected)及其相关的@override@import语句。但是,这对于示例的工作并不是必要的。

提示

有关创建新项目的详细说明和图像,请参见第二章中的开始使用 Android

与本书中的所有示例和项目一样,您可以从下载包中复制或查看代码。您将在Chapter3/ExpressionsInJava/MainActivity.java文件中找到本教程的代码。只需按照以前描述的方式创建项目,并将下载包中的MainActivity.java文件中的代码粘贴到在 Android Studio 中创建项目时生成的MainActivity.java文件中。只需确保包名称与创建项目时选择的包名称相同。但是,我强烈建议跟着教程一起学习,这样我们就可以学会如何自己做一切。

注意

由于此应用程序使用 logcat 控制台显示其输出,因此您应该仅在模拟器上运行此应用程序,而不是在真实的 Android 设备上运行。该应用程序不会损害真实设备,但您将无法看到任何发生的事情。

  1. 创建一个名为Expressions In Java的新空白项目。

  2. 现在,在onCreate方法中,在我们使用setContentView方法的那一行之后,添加以下代码来声明和初始化一些变量:

//first we declare and initialize a few variables
int a = 10;
String b = "Alan Turing";
boolean c = true;
  1. 现在添加以下代码。这段代码简单地输出了我们的变量的值,以便我们可以在一分钟内仔细检查它们:
//Let's look at how Android 'sees' these variables
//by outputting them, one at a time to the console
Log.i("info", "a = " + a);
Log.i("info", "b = " + b);
Log.i("info", "c = " + c);
  1. 现在让我们使用加法运算符和另一个新的运算符来改变我们的变量。在查看输出和代码解释之前,看看你能否计算出变量abc的输出值:
//Now let's make some changes
a++;
a = a + 10;
b = b + " was smarter than the average bear Booboo";
b = b + a;
c = (1 + 1 == 3);//1 + 1 is definitely 2! So false.
  1. 让我们再次以与步骤 3 相同的方式输出值,但这次,输出应该是不同的:
//Now to output them all again
Log.i("info", "a = " + a);
Log.i("info", "b = " + b);
Log.i("info", "c = " + c);
  1. 以通常的方式在模拟器上运行程序。您可以通过单击Android选项卡来查看输出,该选项卡位于项目资源管理器下方的“有用选项卡”区域。

以下是输出,其中去掉了一些不必要的格式:

info﹕ a = 10
info﹕ b = Alan Turing
info﹕ c = true
info﹕ a = 21
info﹕ b = Alan Turing was smarter than the average bear Booboo21
info﹕ c = false

现在让我们讨论一下发生了什么。在第 2 步中,我们声明并初始化了三个变量:

  • a:这是一个 int,其值为 10

  • b:这是一个字符串,其中包含一位杰出的计算机科学家的名字。

  • c:这是一个布尔值,其值为 false

所以当我们在步骤 3 中输出数值时,我们得到以下结果应该不足为奇:

info﹕ a = 10
info﹕ b = Alan Turing
info﹕ c = true

在第 4 步中,所有有趣的事情发生了。我们使用增量运算符将 1 添加到我们的 int a的值,就像这样:a++;。请记住,a++a = a + 1是相同的。

然后我们将 10 添加到a。请注意,我们是在已经添加 1 之后将 10 添加到a。所以我们得到了这样的输出,进行了 10 + 1 + 10 的操作:

info﹕ a = 21

现在让我们检查一下我们的字符串b。我们似乎在我们杰出的科学家身上使用了加法运算符。正在发生的事情可能正是你猜到的。我们将两个字符串"Alan Turing""was smarter than the average bear Booboo."相加在一起时,它被称为连接+符号同时也是连接运算符。

最后,对于我们的字符串,我们似乎在其中添加了int a。这是允许的,a的值被连接到b的末尾。

info﹕ b = Alan Turing was smarter than the average bear Booboo21

注意

这种方法不适用于反向操作;你不能将一个字符串添加到一个int。这是有道理的,因为没有逻辑上的答案。

a = a + b

在 Java 中表达自己

最后,让我们看一下改变我们的布尔值c从 true 变为 false 的代码:c = (1+1=3);。在这里,我们将括号中的表达式的值赋给了c。这本来很简单,但为什么要用双等号(==)呢?我们有点超前了。双等号是另一个叫做比较运算符的操作符。

所以我们真的在问,“1+1”是否等于 3?显然答案是否定的。你可能会问,“为什么要使用==而不是=?”简单地说,这是为了让编译器清楚地知道我们是要赋值还是要比较。

提示

无意中使用=而不是==是一个非常常见的错误。

赋值运算符(=)将右边的值赋给左边的值,而比较运算符(==)比较两边的值。

当我们这样做时,编译器会用错误警告我们,但乍一看你可能会觉得编译器错了。我们将在本章和整本书中更多地了解这个比较运算符和其他运算符。

现在让我们利用我们所知道的一切,再加一点,来制作我们的数学游戏项目。

第四章:发现循环和方法

在本章中,我们将学习如何通过查看 Java 中不同类型的循环来以受控且精确的方式重复执行我们代码的部分。这些包括while循环、do-while循环和for循环。我们将学习在何种情况下使用不同类型的循环。

然后我们将简要介绍随机数的主题。我们还将看到如何使用 Java 的Random类。这显然对增强我们的数学游戏非常有帮助。

接下来,我们将看看方法。它们允许我们将代码分隔成更易管理的块。然后,我们将看到如何在方法之间共享数据,并将编程任务分解以简化问题。

然后,我们将在我们的数学游戏项目中使用我们所学到的关于循环、随机数和方法的所有知识。例如,我们将使游戏在每次尝试答案后更改问题。

我们还将添加问题难度级别和在适当难度级别内的随机问题。我们将展示并更新我们的分数。答对问题的难度级别越高,分数增加得越快。最终,即使我们中最优秀的数学家也应该被游戏打败。然而,我们中的大多数人希望能够比下面的截图所显示的更进一步。

如果玩家答错了问题,难度将回到最简单的级别,分数将变为零。这是我们完成后游戏的样子:

发现循环和方法

在本章中,我们将:

  • 了解多种类型的循环中的循环

  • 学习如何在 Java 中生成随机数

  • 学习有关 Java 方法的所有内容,包括如何编写和调用它们

  • 显著增强我们的数学游戏

使用循环进行循环

询问循环与编程有什么关系是完全合理的,但它们确实如其名称所示。它们是一种多次执行相同代码的方式,或者循环执行相同代码的一部分,但每次可能会有不同的结果。

这可能意味着做同样的事情,直到循环的代码提示循环结束。它可以在由循环代码本身指定的预定次数后提示循环。它也可以在满足预定情况或条件时提示循环结束。或者可能是这些提示循环结束的方式的组合。除了ifelseswitch,循环也是 Java控制流语句的一部分。

我们将研究 Java 提供的所有主要类型的循环,然后在研究了方法之后,我们将使用其中一些来实现对我们数学游戏的增强。让我们继续进行我们的第一种循环。

While 循环

while循环具有最简单的语法。回想一下第三章中的if语句,说 Java - 你的第一个游戏。我们可以在if语句的条件表达式中放置几乎任何组合的运算符和变量。如果表达式评估为true,则执行if块中的代码。同样,在while循环中,我们放置一个可以评估为truefalse的表达式,如下所示:

int x = 10;

while(x > 0){
  x--;
  //x decreases by one each pass through the loop
}

这里发生的是,在while循环之外,声明了一个整数x,并将其初始化为10。然后while循环开始。它的条件是x > 0,因此它将继续循环执行其主体中的代码,直到条件评估为false。因此,代码将执行 10 次。

在第一次循环中,x等于10,然后是9,然后是8,依此类推。但一旦x等于0,显然它不再大于0。因此,程序将退出while循环,并继续执行循环后的第一行代码。

就像if语句一样,while循环有可能甚至不执行一次。看一下这个永远不执行的while循环的例子:

int x = 10;

while(x > 10){
  //more code here.
  //but it will never run unless x is greater than 10.
}

此外,条件表达式的复杂程度或循环体中可以编写的代码量没有限制:

int playerLives = 3;
int alienShips = 10;

while(playerLives >0 && alienShips >0){
  //Entire game code here.
  //...
  //...
  //etc.
}
//continue here when either playerLives or alienShips = 0

前面的while循环将继续执行,直到playerLivealienShips变为等于或小于零。一旦其中一个条件发生,表达式就会评估为false,程序就会从while循环后的第一行代码继续执行。

值得注意的是,一旦进入循环体,即使表达式在中间某个地方评估为false,循环体也会始终完成,因为在代码尝试开始另一次通过之前不会再次检查条件:

int x = 1;

while(x > 0){
  x--;
  //x is now 0 so the condition is false
  //But this line still runs
  //and this one
  //and me!

}

前面的循环体将确切地执行一次。我们还可以设置一个永远运行的while循环(不出所料地称为无限循环),就像这样:

int x = 0;

while(true){
  x++; //I am going to get mighty big!
}

跳出循环

我们可以使用一个无限循环,就像前面的例子中的循环一样,这样我们就可以决定何时从循环体内退出循环。当我们准备离开循环体时,我们会使用break关键字,如下面的代码所示:

int x = 0;

while(true){
  x++; //I am going to get mighty big!
  break; //No you're not haha.
  //code doesn't reach here
}

你可能已经猜到,我们可以在while循环和我们即将看到的其他循环中结合任何决策工具,比如ifelseswitch

int x = 0;
int tooBig = 10;

while(true){
  x++; //I am going to get mighty big!
  if(x == tooBig){
    break;
  } //No you're not haha.

  //code reaches here only until x = 10
}

我们可以简单地继续演示while循环的多种用途,但是在某个时候,我们想要回到做一些真正的编程。因此,这是最后一个概念,结合了while循环。

continue 关键字

continue关键字的作用方式与break类似,但有一点不同。continue关键字将跳出循环体,但之后也会检查条件表达式,所以循环可能会再次运行。下面的例子将展示continue的用法:

int x = 0;
int tooBig = 10;
int tooBigToPrint = 5;

while(true){
  x++; //I am going to get mighty big!
  if(x == tooBig){
    break;
  } //No your not haha.

  //code reaches here only until x = 10

  if(x >= tooBigToPrint){
    //No more printing but keep looping
    continue;
  }
  //code reaches here only until x = 5

  //Print out x 

}

Do-while 循环

do-while循环与while循环非常相似,唯一的区别是它在循环体之后评估表达式。这意味着do-while循环将始终至少执行一次,如下面的代码所示:

int x= 0;
do{
  x++;
}while(x < 10);
//x now = 10 

注意

breakcontinue关键字也可以在do-while循环中使用。

For 循环

for循环的语法比whiledo-while循环稍微复杂一些,因为它需要三个部分来初始化。首先看一下下面的for循环。然后我们将它拆分开来:

for(int i = 0; i < 10; i++){
  //Something that needs to happen 10 times goes here
}

for循环的这种看似晦涩的形式在这样表述时更加清晰:

for(declaration and initialization; condition; change after each pass through loop)

为了进一步澄清,我们在for循环中有以下内容:

  • 声明和初始化:我们创建一个新的int变量i,并将其初始化为 0。

  • 条件:就像其他循环一样,这指的是必须评估为真以使循环继续的条件。

  • 每次通过循环后更改:在前面的例子中,i++表示每次通过循环时i增加 1。我们也可以使用i--来减少/递减i,如下面的代码所示:

for(int i = 10; i > 0; i--){
  //countdown
}
//blast off i = 0

注意

请注意,breakcontinue也可以在for循环中使用。

for循环本质上控制了初始化、条件评估和控制变量。在我们看完随机数和方法之后,我们将使用for循环来增强我们的数学游戏。

Java 中的随机数

在我们深入研究方法之前,我们首先来看一下如何创建随机数,因为这是我们将生成随机问题的方法。

Random类为我们完成了所有的工作。首先我们需要创建一个Random类型的对象:

Random randInt = new Random();

然后我们使用新对象的nextInt方法来生成一个在某个范围内的随机数:

int ourRandomNumber = randInt.nextInt(10);

我们输入的数字范围从零开始。因此,前面的代码将生成一个介于 0 和 9 之间的随机数。如果我们想要一个介于 1 和 10 之间的随机数,我们只需要这样做:

ourRandomNumber++;

提示

在这些早期章节中,我们经常需要接受一些魔法在 Random 等对象中发生。在第六章中,OOP-使用他人的辛勤工作,我们将打开黑匣子,甚至制作我们自己的黑匣子。我们将能够编写我们自己的类和这些类中的方法。

一个很好的开始是查看常规的基本方法,接下来我们将这样做。

数学游戏-提问

现在我们已经掌握了所有这些知识,我们可以利用它来改进我们的数学游戏。首先,我们将创建一个新的 Android 活动,作为实际的游戏屏幕,而不是开始菜单屏幕。然后,我们将使用 UI 设计师来布置一个简单的游戏屏幕,以便我们可以使用我们的 Java 技能来使用变量、类型、声明、初始化、运算符和表达式来使我们的数学游戏为玩家生成一个问题。然后,我们可以使用一个按钮将开始菜单和游戏屏幕连接在一起。

如果您想节省输入时间,只需查看完成的项目,您可以使用从 Packt Publishing 网站下载的代码。如果您在使任何代码工作时遇到任何问题,您可以查看、比较或复制并粘贴下载包中提供的已完成代码。

完成的代码在以下文件中,这些文件对应于本教程中将使用的文件名:

  • Chapter3/MathGameChapter3a/java/MainActivity.java

  • Chapter3/MathGameChapter3a/java/GameActivity.java

  • Chapter3/MathGameChapter3a/layout/activity_main.xml

  • Chapter3/MathGameChapter3a/layout/activity_game.xml

像往常一样,我建议跟着本教程来看看我们如何为自己创建所有的代码。

创建新的游戏活动

我们首先需要为游戏活动代码创建一个新的 Java 文件和一个相关的布局文件来保存游戏活动 UI。

  1. 运行 Android Studio 并选择我们在第二章中构建的Math Game Chapter 2项目,开始使用 Android。它可能已经默认打开了。现在我们将创建一个新的 Android 活动,该活动将包含实际的游戏屏幕,当玩家点击主菜单屏幕上的Play按钮时将运行。

  2. 要创建一个新的活动,我们现在需要另一个布局文件和另一个 Java 文件。幸运的是,Android Studio 会帮助我们做到这一点。要开始创建新活动所需的所有文件,请在项目资源管理器中右键单击src文件夹,然后转到New | Activity。现在点击Blank Activity,然后点击Next

  3. 现在,我们需要在上面的对话框中输入一些关于我们的新活动的信息。将Activity Name字段更改为GameActivity。注意Layout Name字段会自动更改为activity_gameTitle字段也会自动更改为GameActivity

  4. 点击Finish。Android Studio 已经为我们创建了两个文件,并且还在清单文件中注册了我们的新活动,所以我们不需要关心它。

  5. 如果您查看编辑窗口顶部的选项卡,您会看到GameActivity.java已经准备好供我们编辑,如下面的屏幕截图所示:Creating the new game activity

  6. 通过点击之前显示的GameActivity.java选项卡,确保GameActivity.java在编辑窗口中处于活动状态。

  7. 回到第二章开始使用 Android,我们谈到了 Android 默认覆盖了一些方法,其中大多数是不必要的。在这里,我们可以看到不必要的代码。如果我们删除它,那么它将使我们的工作环境更简单、更清洁。你可能还记得从第二章开始使用 Android,删除和修改代码部分的过程虽然不复杂,但是相当长。为了避免这种情况,我们将简单地使用MainActivity.java中的代码作为GameActivity.java的模板。然后我们可以进行一些小的修改。

  8. 点击编辑器窗口中的MainActivity.java选项卡。使用键盘上的Ctrl + A突出显示编辑器窗口中的所有代码。

  9. 现在使用键盘上的Ctrl + C复制编辑器窗口中的所有代码。

  10. 现在点击GameActivity.java选项卡。

  11. 使用键盘上的Ctrl + A突出显示编辑器窗口中的所有代码。

  12. 现在粘贴复制的代码,并使用键盘上的Ctrl + V覆盖当前高亮显示的代码。

  13. 注意,我们的代码中有一个错误,如下截图所示,用红色下划线表示。这是因为我们将引用MainActivity的代码粘贴到名为GameActivity的文件中。

只需将文本MainActivity更改为GameActivity,错误就会消失。在我告诉你之前,花点时间看看还需要做出什么其他小的改变。

  1. 记住,setContentView加载我们的 UI 设计。我们需要做的是将setContentView更改为加载新设计(我们将在下面构建的)而不是主屏幕设计。将setContentView(R.layout.activity_main);更改为setContentView(R.layout.activity_game);

  2. 保存你的工作,我们准备继续。

注意项目资源管理器,Android Studio 将我们创建的两个新文件放在其中。在下一个截图中,我已经突出显示了两个文件夹。将来,我将简单地称它们为我们的java代码文件夹或layout文件夹。

创建新游戏活动

注意

你可能会想为什么我们不直接复制和粘贴MainActivity.java文件,而是要经过创建新活动的过程?原因是 Android Studio 在幕后做了一些事情。首先,它为我们制作了布局模板。它还通过一个稍后会看到的名为AndroidManifest.xml的文件注册了新的活动供使用。这对于新的活动能够首先工作是必要的。综上所述,我们的做法可能是最快的。

此阶段的代码与主菜单屏幕的代码完全相同。我们声明包名并导入一些 Android 提供的有用类:

package com.packtpub.mathgamechapter3a.mathgamechapter3a;

import android.app.Activity;
import android.os.Bundle;

我们创建一个新的活动,这次叫做GameActivity

public class GameActivity extends Activity {

然后我们覆盖onCreate方法,并使用setContentView方法将我们的 UI 设计设置为玩家屏幕的内容。然而,目前,这个 UI 是空的:

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

现在我们可以考虑实际游戏屏幕的布局。

布置游戏屏幕 UI

我们知道,我们的数学游戏将提出问题,并为玩家提供一些多个选择来选择答案。我们可以添加许多额外的功能,例如难度级别、高分和更多。但现在,让我们只问一个简单的、预定义的问题,并提供三个预定义的可能答案选择。

保持 UI 设计尽可能简单暗示着布局。我们的目标 UI 将看起来有点像这样:

布置游戏屏幕 UI

布局希望是不言自明的,但让我们确保我们真的清楚;当我们在 Android Studio 中构建这个布局时,显示2 x 2的部分是问题,将由三个文本视图组成(两个数字和**=号也是一个单独的视图)。最后,答案的三个选项由按钮**布局元素组成。我们在上一章中使用了所有这些 UI 元素,但这一次,因为我们将使用我们的 Java 代码来控制它们,我们需要对它们做一些额外的事情。因此,让我们一步一步地进行:

  1. 在编辑器窗口中打开将保存我们游戏 UI 的文件。通过双击activity_game.xml来执行此操作。这个文件位于项目资源管理器中的 UI layout文件夹中。

  2. 删除Hello World TextView,因为它不是必需的。

  3. 在调色板中找到大文本元素。它可以在小部件部分下找到。将三个元素拖放到 UI 设计区域并排列在设计的顶部附近,如下一张截图所示。它不必完全相同;只需确保它们在一行上,不重叠,如下一张截图所示:布局游戏屏幕 UI

  4. 请注意,在组件树窗口中,每个三个 TextView 都已被 Android Studio 自动分配了一个名称。它们是textViewtextView2textView3布局游戏屏幕 UI

  5. Android Studio 将这些元素名称称为id。这是一个重要的概念,我们将会用到。因此,为了确认这一点,通过单击组件树中的任何一个 textView 的名称(id)或直接在 UI 设计器中单击它,查看属性窗口并找到id属性。您可能需要滚动一下来做到这一点:布局游戏屏幕 UI

请注意,id属性的值是textView。正是这个id,我们将用它来与我们的 Java 代码交互。因此,我们希望将所有 TextView 的 ID 更改为一些有用且易于记忆的内容。

  1. 如果您回顾我们的设计,您会发现具有textView id 的 UI 元素将保存我们数学问题的第一部分的数字。因此,将 id 更改为textPartA。注意text中的小写tPart中的大写P和大写A。您可以使用任何大小写组合,实际上可以将 ID 命名为任何您喜欢的名称。但是,就像 Java 变量的命名约定一样,在这里遵循约定将使得当我们的程序变得更加复杂时,减少错误的可能性。

  2. 现在选择textView2并将id更改为textOperator

  3. 选择当前具有 id textView3的元素并将其更改为textPartB。这个 TextView 将保存我们问题的后半部分。

  4. 现在从调色板中添加另一个大文本。将其放在我们刚刚编辑的三个 TextView 的行后面。

这个大文本将简单地保存我们的等于号,而且没有计划去改变它。因此,我们不需要在我们的 Java 代码中与它交互。我们甚至不需要关心更改 ID 或知道它是什么。如果情况改变,我们随时可以在以后的时间回来编辑它的 ID。

  1. 然而,这个新的 TextView 目前显示大文本,我们希望它显示一个等于号。因此,在属性窗口中,找到text属性,并输入值**=。我们之前在第二章中更改了text属性,开始使用 Android,你可能也想更改textPartAtextPartBtextOperator的文本属性。这并非绝对必要,因为我们很快就会看到如何通过我们的 Java 代码来更改它;然而,如果我们将text**属性更改为更合适的内容,那么我们的 UI 设计师看起来将更像在真实设备上运行游戏时的样子。

  2. 所以将textPartA的文本属性更改为2textPartB更改为2textOperator更改为x。现在你的 UI 设计和组件树应该是这样的:布局游戏屏幕 UI

  3. 为了包含我们的多项选择答案,将三个按钮依次拖到**=**符号下面。将它们整齐地排列,就像我们的目标设计一样。

  4. 现在,就像我们为 TextViews 所做的那样,找到每个按钮的id属性,并从左到右,将id属性更改为buttonChoice1buttonChoice2buttonChoice3

  5. 为什么不为每个按钮的text属性输入一些任意的数字,以便设计更准确地反映我们的游戏将是什么样子,就像我们为其他 TextView 所做的那样?同样,这并非绝对必要,因为我们的 Java 代码将控制按钮的外观。

  6. 我们现在实际上已经准备好继续了。但你可能会同意 UI 元素看起来有点迷失。如果按钮和文本更大会更好看。我们只需要调整每个 TextView 和每个 Button 的 textSize 属性。然后,我们只需要找到每个元素的 textSize 属性,并输入带有 sp 语法的数字。如果你希望你的设计看起来就像我们之前的目标设计一样,那么为每个 TextView 的 textSize 属性输入70sp,为每个按钮的 textSize 属性输入40sp。当你在真实设备上运行游戏时,你可能需要回来调整一下大小。但在我们实际尝试游戏之前,我们还有更多的工作要做。

  7. 保存项目,然后我们可以继续。

与以前一样,我们已经构建了我们的用户界面。然而,这一次,我们为 UI 的所有重要部分提供了一个独特的、有用的、易于识别的 ID。正如我们将看到的,我们现在能够通过我们的 Java 代码与我们的 UI 进行通信。

在 Java 中编写问题

凭借我们目前对 Java 的了解,我们还不能完成我们的数学游戏,但我们可以有一个重要的开始。我们将看看如何向玩家提问并提供一些多项选择答案(一个正确答案和两个错误答案)。

在这个阶段,我们已经了解了足够的 Java 知识,可以声明和初始化一些变量来存储我们问题的各个部分。例如,如果我们想要问乘法表问题2 x 2,我们可以有以下变量初始化来存储问题的每个部分的值:

int partA = 2;
int partB = 2;

上面的代码声明并初始化了两个int类型的变量,每个变量的值都为 2。我们使用int,因为我们不会处理任何小数。请记住,变量名是任意的,只是因为它们看起来合适才选择的。显然,任何值得下载的数学游戏都需要提出比2 x 2更多样化和高级的问题,但这是一个开始。

现在我们知道我们的数学游戏将提供多个选择作为答案。因此,我们需要一个变量来存储正确答案,以及两个变量来存储两个错误答案。看一下这些组合声明和初始化:

int correctAnswer = partA * partB;
int wrongAnswer1 = correctAnswer - 1;
int wrongAnswer2 = correctAnswer + 1; 

请注意,错误答案的变量初始化取决于正确答案的值,并且在初始化correctAnswer变量之后初始化错误答案的变量。

现在我们需要将我们变量中保存的这些值放入我们 UI 上的适当元素中。问题变量(partApartB)需要显示在我们的 UI 元素textPartAtextPartB中,答案变量(correctAnswerwrongAnswer1wrongAnswer2)需要显示在具有以下 ID 的 UI 元素中:buttonChoice1buttonChoice2buttonChoice3。我们将在下一个逐步教程中看到如何做到这一点。我们还将实现我们刚才讨论过的变量声明和初始化代码:

  1. 首先,在编辑窗口中打开GameActivity.java。请记住,您可以通过双击java文件夹中的GameActivity或单击编辑窗口上方的选项卡来执行此操作,如果GameActivity.java已经打开。

  2. 我们所有的代码都将放在onCreate方法中。它将放在setContentView(R.layout.activity_game);行之后,但在onCreate方法的结束大括号}之前。也许,为了清晰起见,最好留一行空白,并添加一个好的解释性注释,如下面的代码所示。我们可以看到整个onCreate方法在最新的修改之后的样子。粗体部分是你需要添加的部分。如果愿意,可以像我一样添加有用的注释:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //The next line loads our UI design to the screen
        setContentView(R.layout.activity_game);

 //Here we initialize all our variables
 int partA = 9;
 int partB = 9;
 int correctAnswer = partA * partB;
 int wrongAnswer1 = correctAnswer - 1;
 int wrongAnswer2 = correctAnswer + 1;

    }//onCreate ends here
  1. 现在我们需要将变量中包含的值添加到我们 UI 的TextViewButton中。但首先,我们需要访问我们创建的 UI 元素。我们通过创建适当类的变量并通过适当 UI 元素的 ID 属性进行链接来实现这一点。我们已经知道我们 UI 元素的类:TextViewButton。以下是为每个必要的 UI 元素创建我们特殊类变量的代码。仔细看看这段代码,但如果现在不理解所有内容也不要担心。一切正常后,我们将详细解析代码。在上一步输入的代码之后立即输入这段代码。如果愿意,可以留一行空白以便清晰。在继续之前,请注意,在键入此代码时,有两个地方会提示您导入另一个类。在这两种情况下都可以继续操作:
/*Here we get a working object based on either the button
or TextView class and base as well as link our new objects directly to the appropriate UI elements that we created previously*/

TextView textObjectPartA = (TextView)findViewById(R.id.textPartA);

TextView textObjectPartB = (TextView)findViewById(R.id.textPartB);

Button buttonObjectChoice1 = (Button)findViewById(R.id.buttonChoice1);

Button buttonObjectChoice2 = (Button)findViewById(R.id.buttonChoice2);

Button buttonObjectChoice3 = (Button)findViewById(R.id.buttonChoice3);

注意

在上述代码中,如果您阅读多行注释,您会看到我使用了术语对象。当我们基于类创建变量类型时,我们称之为对象。一旦我们有了类的对象,我们就可以做任何该类设计的事情。这是非常强大的,并且在第六章中得到了充分探讨,OOP – Using Other People's Hard Work

  1. 现在我们有五个新对象与我们需要操作的 UI 元素相关联。我们要用它们做什么?我们需要在 UI 元素的文本中显示变量的值。我们可以使用我们刚刚创建的对象,结合类提供的方法,并将我们的变量作为文本的值。像往常一样,我们将在本教程结束时进一步解析这段代码。以下是要直接输入到上一步代码之后的代码。在我们一起查看之前,试着弄清楚发生了什么:
//Now we use the setText method of the class on our objects
//to show our variable values on the UI elements.
//Just like when we output to the console in the exercise -
//Expressions in Java, only now we use setText method
//to put the values in our variables onto the actual UI.
textObjectPartA.setText("" + partA);
textObjectPartB.setText("" + partB);

//which button receives which answer, at this stage is arbitrary.

buttonObjectChoice1.setText("" + correctAnswer);
buttonObjectChoice2.setText("" + wrongAnswer1);
buttonObjectChoice3.setText("" + wrongAnswer2);
  1. 保存您的工作。

如果您玩转partApartB的赋值,您可以随意设置它们,游戏会相应地调整答案。显然,我们不应该每次想要一个新问题时都需要重新编写我们的游戏,我们很快就会解决这个问题。现在我们需要做的就是将我们刚刚制作的游戏部分与开始屏幕菜单连接起来。我们将在下一个教程中完成这一点。

现在让我们更详细地探讨我们代码中更棘手和更新的部分。

在第 2 步中,我们声明并初始化了到目前为止所需的变量:

//Here we initialize all our variables
int partA = 2;
int partB = 2;
int correctAnswer = partA * partB;
int wrongAnswer1 = correctAnswer - 1;
int wrongAnswer2 = correctAnswer + 1;

然后在第 3 步,我们通过 Java 代码获得了对我们 UI 设计的引用。对于 TextView,是这样完成的:

TextView textObjectPartA = (TextView)findViewById(R.id.textPartA);

对于每个按钮,获得对我们 UI 设计的引用是这样完成的:

Button buttonObjectChoice1 = Button)findViewById(R.id.buttonChoice1);

在第 4 步中,我们做了一些新的事情。我们使用setText方法在 UI 元素(TextViewButton)上向玩家显示我们的变量的值。让我们完全分解一行,看看它是如何工作的。这是显示correctAnswer变量显示在buttonObjectChoice1上的代码。

buttonObjectChoice1.setText("" + correctAnswer);

通过输入buttonObjectChoice1并添加一个句点,如下面的代码行所示,我们可以访问 Android 提供的该对象类类型的所有预编程方法:

buttonObjectChoice1.

提示

按钮和 Android API 的力量

实际上,我们可以在 Button 类型的对象上执行许多方法。如果您感到勇敢,尝试一下,看看 Android 有多少功能。

输入以下代码:

buttonObjectChoice1.

确保在末尾输入句点。Android Studio 将弹出一个可能要在此对象上使用的方法列表。浏览列表,了解选项的数量和多样性:

在 Java 中编写问题

如果一个简单的按钮可以做到这一切,想象一下一旦我们掌握了 Android 中包含的所有类,我们的游戏的可能性。一个供他人使用的类集合被称为应用程序员接口API)。欢迎来到 Android API!

在这种情况下,我们只想设置按钮的文本。因此,我们使用setText并将存储在我们的correctAnswer变量中的值连接到空字符串的末尾,就像这样:

setText("" + correctAnswer);

我们为每个需要显示我们的变量的 UI 元素执行此操作。

提示

使用自动完成玩耍

如果您尝试了前面的提示,按钮和 Android API 的力量,并探索了 Button 类型对象可用的方法,您可能已经对自动完成有所了解。请注意,当您输入时,Android Studio 会不断地为您提供建议。如果您留意这一点,您可以节省很多时间。只需选择建议的正确代码完成语句并按Enter。您甚至可以通过选择帮助 | 生产力指南来查看您节省了多少时间。在这里,您将看到有关代码完成和更多内容的统计数据。以下是我的一些条目:

在 Java 中编写问题

正如您所看到的,如果您早期习惯使用快捷方式,您可以从长远来看节省很多时间。

从主菜单链接我们的游戏

目前,如果我们运行应用程序,玩家实际上没有办法到达我们的新游戏活动。我们希望当玩家点击主MainActivity UI 上的Play按钮时,游戏活动会运行。这是我们需要做的:

  1. 打开文件activity_main.xml,可以通过在项目资源管理器中双击它或者在编辑窗口中点击它的选项卡来打开。

  2. 现在,就像我们构建游戏 UI 时所做的那样,为Play按钮分配一个 ID。作为提醒,在 UI 设计或组件树中点击Play按钮。在属性窗口中找到id属性。将buttonPlay值分配给它。现在我们可以通过在我们的 Java 代码中引用它来使这个按钮做一些事情。

  3. 打开文件MainActivity.java,可以通过在项目资源管理器中双击它或者在编辑窗口中点击它的选项卡来打开。

  4. 在我们的onCreate方法中,在我们setContentView的行后面,添加以下突出显示的代码行:

setContentView(R.layout.activity_main);
Button buttonPlay = (Button)findViewById(R.id.buttonPlay);

  1. 一旦我们让它工作起来,我们将详细解剖这段代码。基本上,我们正在通过创建对Button对象的引用变量来连接到Play按钮。注意到两个词都是红色高亮显示的,表示有错误。就像之前一样,我们需要导入 Button 类来使这段代码工作。使用Alt + Enter键盘组合。现在点击弹出的选项列表中的Import class。这将自动在我们的MainActivity.java文件顶部添加所需的导入指令。

  2. 现在是新的内容。我们将使按钮能够监听用户点击它。在我们输入的最后一行代码之后立即输入以下内容:

buttonPlay.setOnClickListener(this);
  1. 注意到this关键字是红色高亮显示的,表示有一个错误。这引入了另一个 Java 特性,将在第六章中更加深入地探讨,OOP – Using Other People's Hard Work。暂且不谈这个,我们现在需要对我们的代码进行修改,以允许使用一个特殊的代码元素,即接口,它允许我们添加功能,比如监听按钮点击。编辑以下行。在提示导入另一个类时,点击OK
public class MainActivity extends Activity {

to

public class MainActivity extends Activity implements View.OnClickListener{

现在我们已经用红色下划线标出了整行。这表示了一个错误,但这是我们目前应该在的位置。我们提到通过添加implements View.OnClickListener,我们已经实现了一个接口。我们可以把它想象成一个我们可以使用的类,但有额外的规则。OnClickListener接口的规则规定我们必须实现/使用它的一个方法。注意到到目前为止,我们已经根据需要选择性地重写/使用方法。如果我们希望使用这个接口提供的功能,即监听按钮按下,那么我们必须添加/实现onClick方法。

  1. 这就是我们的做法。注意到开放的大括号{和闭合的大括号}。这表示方法的开始和结束。注意到这个方法是空的,它什么也不做,但一个空的方法足以符合OnClickListener接口的规则,红线表示我们的代码有错误已经消失。我们一直在使用的这些方法的语法,将在下一章中承诺时解释。确保你在onCreate方法的闭合大括号(})之外但在MainActivity类的闭合大括号之内输入以下代码:
@Override
    public void onClick(View view) {

    }
  1. 注意到onClick方法的{}之间有一行空白。我们现在可以在这里添加代码,使按钮实际上做一些事情。在onClick{}之间输入以下高亮显示的代码:
@Override
    public void onClick(View view) {
        Intent i;
 i = new Intent(this, GameActivity.class);
 startActivity(i);
    }
  1. 好的,这段代码一次理解起来有点费劲。看看你能否猜到发生了什么。提示在名为startActivity的方法和熟悉的术语GameActivity中。注意到我们正在给i赋值。我们将快速让我们的应用程序工作,然后全面诊断代码。当我们探索类在第六章中的工作方式时,我们的理解将是完整的,OOP – Using Other People's Hard Work

  2. 注意到我们有一个错误:所有Intent一词的实例都是红色的。我们可以通过导入所需的类来解决这个问题,使Intent工作。和之前一样,按下Alt + Enter

  3. 在模拟器或设备上运行游戏。

我们的应用现在将工作。这是在菜单屏幕上按下Play后新游戏屏幕的样子:

从主菜单链接到我们的游戏

我们代码的几乎每个部分都有一些变化,我们也添加了很多内容。让我们逐行查看MainActivity.java的内容。为了上下文,这是完整的代码:

package com.packtpub.mathgamechapter3a.mathgamechapter3a;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends Activity implements View.OnClickListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final Button buttonPlay = (Button)findViewById(R.id.buttonPlay);
        buttonPlay.setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        Intent i;
        i = new Intent(this, GameActivity.class);
        startActivity(i);
    }

}

我们之前已经看到了大部分这段代码,但在继续之前,让我们一块一块地复习一下,以确保它绝对清晰。代码的工作方式如下:

package com.packtpub.mathgamechapter3a.mathgamechapter3a;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

你可能还记得,这个代码块定义了我们的包叫什么,并且使得所有 Android API 的东西对 Button、TextView 和 Activity 都是可用的。

从我们的MainActivity.java文件中,我们有这样的内容:

public class MainActivity extends Activity implements View.OnClickListener{

我们的MainActivity声明与我们的新代码一起实现了View.OnClickListener,这使我们能够检测按钮点击。

我们代码中的下一步是这样的:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main); 

自第二章以来,这段代码就没有改变过,开始使用 Android。它位于我们的onCreate方法的开头,我们首先要求onCreate的隐藏代码使用super.onCreate(savedInstanceState);来执行其操作。然后我们使用setContentView(R.layout.activity_main);将我们的 UI 设置到屏幕上。

接下来,我们获取了一个 ID 为buttonPlay的按钮的引用:

Button buttonPlay = (Button)findViewById(R.id.buttonPlay);
buttonPlay.setOnClickListener(this);

最后,我们的onClick方法使用Intent类将玩家发送到我们的GameActivity类和相关的 UI,当用户点击Play按钮时:

    @Override
    public void onClick(View view) {
        Intent i;
        i = new Intent(this, GameActivity.class);
        startActivity(i);
    }

如果你运行这个应用,你会注意到我们现在可以点击Play按钮,我们的数学游戏会问我们一个问题。当然,我们现在还不能回答。虽然我们已经非常简要地看过如何处理按钮按下,但我们需要学习更多的 Java 知识才能对其做出智能反应。我们还将揭示如何编写代码来处理来自多个按钮的按下。这对于从我们的多选中心的game_activity UI 接收输入是必要的。

在 Java 中做决定

我们现在可以召唤足够的 Java 技能来提问,但一个真正的数学游戏显然需要做的远不止这些。我们需要捕获玩家的答案,我们几乎已经做到了——我们可以检测按钮按下。然后,我们需要能够决定他们的答案是对还是错。然后,根据这个决定,我们必须选择适当的行动方针。

暂时把数学游戏放在一边,看看 Java 如何帮助我们学习一些更基本的 Java 语言的基础知识和语法。

更多的运算符

让我们再看看一些运算符:我们已经可以使用运算符进行加法(+)、减法(-)、乘法(*)、除法(/)、赋值(=)、递增(++)、比较(==)和递减(--)。让我们介绍一些更加超级有用的运算符,然后我们将直接理解如何在 Java 中使用它们。

提示

不要担心记住这里给出的每个运算符。浏览它们和它们的解释,然后快速转到下一节。在那里,我们将使用一些运算符,当我们看到它们允许我们做些什么的几个例子时,它们将变得更加清晰。它们在这里以列表的形式呈现,只是为了从一开始就清楚地展示运算符的种类和范围。当不与随后的实现讨论混在一起时,这个列表也更方便作为参考。

  • ==:这是一个我们之前非常简要看到的比较运算符。它用于测试相等,结果要么是真要么是假。例如,表达式(10 == 9);是假的。

  • !:逻辑非运算符。表达式! (2+2==5).)是真的,因为 2+2 不等于 5。

  • !=:这是另一个比较运算符,用于测试某物是否不相等。例如,表达式(10 != 9);)是真的,也就是说,10 不等于 9。

  • >:这是另一个比较运算符,用于测试某物是否大于另一物。表达式(10 > 9);)是真的。还有一些其他的比较运算符。

  • <:你猜对了。这个测试左边的值是否小于右边的值。表达式(10 < 9);是假的。

  • >=:这个运算符测试一个值是否大于或等于另一个值,如果其中一个条件成立,结果就是真。例如,表达式(10 >= 9);是真的。表达式(10 >= 10);也是真的。

  • <=:与前一个运算符类似,这个运算符测试两个条件,这次是小于或等于。表达式(10 <= 9);为假。表达式(10 <= 10);为真。

  • &&:这个运算符称为逻辑与。它测试表达式的两个或多个独立部分,所有部分必须为真才能使结果为真。逻辑与通常与其他运算符一起使用,以构建更复杂的测试。表达式((10 > 9) && (10 < 11));是真的,因为两部分都为真。表达式((10 > 9) && (10 < 9));是假的,因为表达式的一部分为真,另一部分为假。

  • ||:这个运算符称为逻辑或。它与逻辑与类似,只是表达式中的两个或多个部分中只有一个为真,表达式才为真。让我们看看我们之前使用的最后一个例子,但是用||代替&&。表达式((10 > 9) || (10 < 9));现在为真,因为表达式的一部分为真。

所有这些运算符如果没有正确使用它们来影响真实变量和代码的决策,就几乎没有用。让我们看看如何在 Java 中做出决策。

决策 1 - 如果他们过桥,就射击他们

正如我们所看到的,运算符单独使用几乎没有任何意义,但是可能有用的是看到我们可以使用的广泛和多样的范围的一部分。现在,当我们开始使用最常见的运算符==时,我们可以开始看到运算符提供给我们的强大而精细的控制。

让我们使用 Java 的if关键字和一些条件运算符以及一个有趣的故事和一些代码来使之前的例子不那么抽象。

船长垂危,知道剩下的部下经验不足,他决定在自己死后写一个 Java 程序来传达他的最后命令。部队必须在等待增援的同时守住桥的一侧。

船长想要确保他的部队理解的第一个命令是:如果他们过桥,就射击他们。

那么我们如何在 Java 中模拟这种情况呢?我们需要一个布尔变量isComingOverBridge。下一段代码假设isComingOverBridge变量已经被声明和初始化。

我们可以这样使用它:

if(isComingOverBridge){
  //Shoot them
}

如果isComingOverBridge布尔值为真,则大括号内的代码将运行。如果不是,则程序在if块之后继续运行而不运行它。

决策 2 - 否则,执行这个操作

船长还想告诉他的部队,如果敌人没有过桥,他们应该待在原地。

现在我们介绍另一个 Java 关键字,else。当我们想要明确执行某些操作,而if块的条件不成立时,我们可以使用else

例如,如果敌人没有过桥,我们使用else命令部队留在原地:

if(isComingOverBridge){
  //Shoot them
}else{
  //Hold position
}

然后船长意识到问题并不像他最初想的那么简单。如果敌人过桥并且有更多的部队怎么办?他的小队将被压垮。因此,他想出了这段代码(这次我们也将使用一些变量):

boolean isComingOverTheBridge;
int enemyTroops;
int friendlyTroops;
//Code that initializes the above variables one way or another

//Now the if
if(isComingOverTheBridge && friendlyTroops > enemyTroops){
  //shoot them
}else if(isComingOverTheBridge && friendlyTroops < enemyTroops) {
  //blow the bridge
}else{
  //Hold position
}

最后,船长最后的担心是,如果敌人拿着白旗过桥投降,然后被立即屠杀,那么他的部队将成为战争罪犯。所需的 Java 代码是显而易见的。使用wavingWhiteFlag布尔变量,他写下了这个测试:

if (wavingWhiteFlag){
  //Take prisoners
}

但是在哪里放置这段代码并不太清楚。最后,船长选择了以下嵌套解决方案,并将wavingWhiteFlag的测试更改为逻辑非,就像这样:

if (!wavingWhiteFlag){//not surrendering so check everything else
  if(isComingOverTheBridge && friendlyTroops > enemyTroops){
    //shoot them
  }else if(isComingOverTheBridge && friendlyTroops < enemyTroops) {
    //blow the bridge
  }
}else{//this is the else for our first if
  //Take prisoners
{
//Holding position

这表明我们可以嵌套ifelse语句以创建更深层次的决策。

我们可以继续做出更多更复杂的决定,但我们已经看到的已经足够作为介绍。如果有任何不清楚的地方,请花时间重新阅读。谁知道,也许在本章末尾的自测中甚至会有一个棘手的逻辑问题。还要指出的是,很多时候,有两种或更多种方法可以得出解决方案。正确的方法通常是以最清晰和最简单的方式解决问题的方法。

切换以做出决策

我们已经看到了将 Java 运算符与ifelse语句相结合的广阔而几乎无限的可能性。但有时,在 Java 中做出决定可能有其他更好的方法。

当我们必须根据一个清晰的可能性列表做出决定,而不涉及复杂的组合时,switch通常是最好的选择。

我们开始一个switch决定就像这样:

switch(argument){

}

在前面的例子中,参数可以是表达式或变量。然后在大括号内,我们可以根据参数使用 case 和 break 元素做出决定:

case x:
  //code to for x
  break;

case y:
  //code for y
  break;

您可以看到在前面的例子中,每个 case 都陈述了可能的结果,每个 break 都表示该 case 的结束以及不再评估进一步 case 语句的点。遇到的第一个 break 将我们带出 switch 块,继续执行下一行代码。

我们还可以使用没有值的default来运行一些代码,如果没有一个 case 语句计算为 true,就像这样:

default://Look no value
  //Do something here if no other case statements are true
break;

假设我们正在编写一个老式的文本冒险游戏-玩家输入命令,比如“向东走”,“向西走”,“拿剑”等。在这种情况下,switch 可以像这个例子代码一样处理这种情况,我们可以使用default来处理玩家输入的未经特别处理的命令的情况:

//get input from user in a String variable called command
switch(command){

  case "Go East":":
  //code to go east
  break;

  case "Go West":
  //code to go west
  break;
  case "Take sword":
  //code to take the sword
  break;

  //more possible cases

  default:
  //Sorry I don't understand your command
  break;

}

在接下来的部分中,我们将使用switch,这样我们的onClick方法就可以处理数学游戏中不同的多选按钮。

提示

Java 甚至有比我们在这里介绍的更多的运算符。我们已经看过了本书中需要的所有运算符,也可能是一般情况下使用最多的运算符。如果您想了解有关运算符的完整信息,请查看官方 Java 文档docs.oracle.com/javase/tutorial/java/nutsandbolts/operators.html

数学游戏-获取和检查答案

在这里,我们将检测正确或错误的答案,并向玩家提供弹出消息。我们的 Java 现在已经相当不错了,所以让我们深入研究并添加这些功能。我会在我们进行的过程中解释事情,然后像往常一样,在最后彻底解剖代码。

已经完成的代码在下载包中,对应于我们将在 Android Studio 中创建/自动生成的文件名:

  • Chapter3/MathGameChapter3b/java/MainActivity.java

  • Chapter3/MathGameChapter3b/java/GameActivity.java

  • Chapter3/MathGameChapter3b/layout/activity_main.xml

  • Chapter3/MathGameChapter3b/layout/activity_game.xml

像往常一样,我建议按步骤跟着本教程,看看我们如何为自己创建所有的代码。

  1. 打开编辑器窗口中可见的GameActivity.java文件。

  2. 现在我们需要将点击检测功能添加到我们的GameActivity中,就像我们为MainActivity做的那样。但是,这次我们会比上次更进一步。所以让我们一步一步地做,就像它是全新的一样。再一次,我们将让按钮有能力监听用户点击它们。在我们输入onCreate方法中的最后一行代码之后,但在}之前立即输入这个。当然,这次我们需要添加一些代码来监听三个按钮:

buttonObjectChoice1.setOnClickListener(this);
buttonObjectChoice2.setOnClickListener(this);
buttonObjectChoice3.setOnClickListener(this);
  1. 注意this关键字被标记为红色,表示错误。同样,我们需要对代码进行修改,以便允许使用接口,这是一种特殊的代码元素,允许我们添加诸如监听按钮点击之类的功能。编辑以下行。在提示要导入另一个类时,点击OK。考虑以下代码行:
public class GameActivity extends Activity {

将其更改为以下行:

public class GameActivity extends Activity implements View.OnClickListener{
  1. 现在我们在整个前一行下划线标记为红色。这表示错误,但这是我们目前应该在的位置。我们提到通过添加implements View.OnClickListener,我们已经实现了一个接口。我们可以将其视为一个我们可以使用的类,但带有额外的规则。OnClickListener接口的规则之一是我们必须实现其方法之一,您可能还记得。现在我们将添加onClick方法。

  2. 输入以下代码。注意开头的大括号{和结尾的大括号}。这些表示方法的开始和结束。注意方法是空的;它什么也不做,但是一个空方法足以符合OnClickListener接口的规则,指示错误的红线已经消失。确保您在onCreate方法的结束大括号(})之外输入以下代码,但在我们的MainActivity类的结束大括号内部:

@Override
    public void onClick(View view) {

    }
  1. 注意我们的onClick方法的{}括号之间有一行空行。现在我们可以在这里放一些代码,使按钮实际上做一些事情。在onClick{}之间输入以下内容。这是与我们在MainActivity中的代码不同的地方。我们需要区分可能被按下的三个按钮。我们将使用我们之前讨论过的switch语句来做到这一点。看一下case条件;它们应该看起来很熟悉。以下是使用switch语句的代码:
switch (view.getId()) {

            case R.id.buttonChoice1:
            //button 1 stuff goes here
                break;

            case R.id.buttonChoice2:
            //button 2 stuff goes here
                break;

            case R.id.buttonChoice3:
           //button 3 stuff goes here
                break;

        }
  1. 每个case元素处理不同的按钮。对于每个按钮情况,我们需要获取刚刚按下的按钮中存储的值,并查看它是否与我们的correctAnswer变量匹配。如果匹配,我们必须告诉玩家他们答对了,如果不匹配,我们必须告诉他们答错了。然而,我们仍然有一个问题需要解决。onClick方法是单独的,与onCreate方法和 Button 对象分开。事实上,所有变量都是在onCreate方法中声明的。如果现在尝试输入第 9 步的代码,将会出现很多错误。我们需要使onClick中需要的所有变量在onClick中可用。为此,我们将它们的声明从onCreate方法上方移动到GameActivity的开头{下方。这意味着这些变量成为GameActivity类的变量,并且可以在GameActivity中的任何地方看到。像这样声明以下变量:
int correctAnswer;
Button buttonObjectChoice1;
Button buttonObjectChoice2;
Button buttonObjectChoice3;
  1. 现在更改onCreate中这些变量的初始化如下。需要更改的实际代码部分已经突出显示。其余部分仅用于上下文显示:
//Here we initialize all our variables
int partA = 9;
int partB = 9;
correctAnswer = partA * partB;
int wrongAnswer1 = correctAnswer - 1;
int wrongAnswer2 = correctAnswer + 1;

TextView textObjectPartA = (TextView)findViewById(R.id.textPartA);

TextView textObjectPartB = (TextView)findViewById(R.id.textPartB);

buttonObjectChoice1 = (Button)findViewById(R.id.buttonChoice1);

buttonObjectChoice2 = (Button)findViewById(R.id.buttonChoice2);

buttonObjectChoice3 = (Button)findViewById(R.id.buttonChoice3);

  1. 这是我们的onClick方法的顶部以及我们的onClick方法的第一个case语句:
@Override
    public void onClick(View view) {
        //declare a new int to be used in all the cases
        int answerGiven=0;
        switch (view.getId()) {

            case R.id.buttonChoice1:
            //initialize a new int with the value contained in buttonObjectChoice1
            //Remember we put it there ourselves previously
                answerGiven = Integer.parseInt("" + buttonObjectChoice1.getText());

                //is it the right answer?
                if(answerGiven==correctAnswer) {//yay it's the right answer
                    Toast.makeText(getApplicationContext(), "Well done!", Toast.LENGTH_LONG).show();
                }else{//uh oh!
                    Toast.makeText(getApplicationContext(),"Sorry that's wrong", Toast.LENGTH_LONG).show();
                }
                break;
  1. 以下是执行与上一步中的代码相同步骤的其余case语句,除了处理最后两个按钮。在上一步输入的代码之后输入以下代码:
            case R.id.buttonChoice2:
                //same as previous case but using the next button
                answerGiven = Integer.parseInt("" + buttonObjectChoice2.getText());
                if(answerGiven==correctAnswer) {
                    Toast.makeText(getApplicationContext(), "Well done!", Toast.LENGTH_LONG).show();
                }else{
                    Toast.makeText(getApplicationContext(),"Sorry that's wrong", Toast.LENGTH_LONG).show();
                }
                break;

            case R.id.buttonChoice3:
                //same as previous case but using the next button
                answerGiven = Integer.parseInt("" + buttonObjectChoice3.getText());
                if(answerGiven==correctAnswer) {
                    Toast.makeText(getApplicationContext(), "Well done!", Toast.LENGTH_LONG).show();
                }else{
                    Toast.makeText(getApplicationContext(),"Sorry that's wrong", Toast.LENGTH_LONG).show();
                }
                break;

        }
  1. 运行程序,然后我们将仔细查看代码,特别是那个看起来奇怪的Toast东西。当我们点击最左边的按钮时会发生什么:数学游戏-获取和检查答案

这就是我们做的:在步骤 1 到 6 中,我们设置了处理多选按钮的方法,包括使用onClick方法和switch块来处理根据按下的按钮做出的决定的能力。

在第 7 和第 8 步中,我们不得不修改我们的代码,以使我们的变量在onClick方法中可用。我们通过将它们作为GameActivity类的成员变量来实现这一点。

提示

当我们将变量作为类的成员时,我们称之为字段。我们将在第六章中讨论变量何时应该是字段,何时不应该是字段。

在第 9 和第 10 步中,我们实现了在onClick中执行实际工作的代码。让我们逐行查看当button1被按下时运行的代码。

case R.id.buttonChoice1:

首先,case语句在按下 id 为buttonChoice1的按钮时为真。然后执行的下一行代码是这样的:

answerGiven = Integer.parseInt(""+ buttonObjectChoice1.getText());

前一行使用了两个方法来获取按钮上的值。首先,getText以字符串形式获取数字,然后Integer.parseInt将其转换为整数。该值存储在我们的answerGiven变量中。接下来执行以下代码:

if(answerGiven==correctAnswer) {//yay it's the right answer
  Toast.makeText(getApplicationContext(), "Well done!", Toast.LENGTH_LONG).show();
}else{//uh oh!
    Toast.makeText(getApplicationContext(),"Sorry that's wrong", Toast.LENGTH_LONG).show();
                }

if语句测试answerGiven变量是否与correctAnswer相同,使用==运算符。如果是,Toast对象的makeText方法用于显示祝贺消息。如果两个变量的值不同,显示的消息会更消极一些。

注意

Toast代码行可能是我们迄今为止见过的最邪恶的东西。它看起来异常复杂,需要比我们目前所掌握的 Java 知识更多才能理解。我们现在只需要知道我们可以直接使用代码并只改变消息,这是一个向玩家宣布消息的好工具。到第六章结束时,Toast的代码将会清晰明了。如果你现在真的想要解释,你可以这样想:当我们创建按钮对象时,我们可以使用所有按钮方法。但是对于 Toast,我们直接使用类来访问其makeText方法,而不需要先创建对象。当类及其方法被设计允许这样做时,我们可以进行这个过程。

最后,我们通过以下方式跳出整个switch语句:

break;

现在我们已经根据本章学到的知识改进了项目,为什么不测试一下你迄今为止所学到的一切呢?

自测问题

Q1) 这段代码做什么?

// setContentView(R.layout.activity_main);

Q2) 以下哪行会导致错误?

String a = "Hello";
String b = " Vinton Cerf";
int c = 55;
a = a + b
c = c + c + 10;
a = a + c;
c = c + a;

Q3) 我们谈了很多关于运算符以及不同运算符如何一起构建复杂表达式。表达式乍一看有时会让代码看起来复杂。然而,仔细观察时,它们并不像看起来那么难。通常,只是将表达式分成较小的部分来弄清楚发生了什么。这是一个比本书中你将看到的任何其他东西都更复杂的表达式。作为挑战,你能计算出:x将是什么吗?

int x = 10;
int y = 9;
boolean isTrueOrFalse = false;
isTrueOrFalse = (((x <=y)||(x == 10))&&((!isTrueOrFalse) || (isTrueOrFalse)));

总结

本章我们涵盖了很多内容。我们从不了解 Java 语法到学习注释、变量、运算符和决策过程。

与任何语言一样,掌握 Java 可以通过简单的练习、学习和扩展词汇量来实现。此时,诱惑可能是要等到掌握当前 Java 语法后再继续,但最好的方法是同时学习新的语法,同时重新学习我们已经开始学习的内容。

在下一章中,我们将通过添加多种难度的随机问题以及为多选按钮使用更合适和随机的错误答案,最终完成我们的数学游戏。

为了使我们能够做到这一点,我们将首先学习更多的 Java。

方法

那么 Java 方法到底是什么?方法是一组变量、表达式和控制流语句。我们已经在使用很多方法;我们只是还没有深入研究过。

学习 Java 方法将是本章最后一个主题,在此之前我们将实际应用我们所学到的知识来增强我们的数学游戏。

方法的结构

我们编写的方法的第一部分称为签名。以下是一个虚构的签名示例:

public boolean shootLazers(int number, string type)

添加一对大括号并包含一些方法执行的代码,我们就有了一个完整的方法,或者定义。以下是一个虚构但语法正确的方法:

private void setCoordinates(int x, int y){
  //code to set coordinates goes here
}

然后我们可以在我们代码的另一个部分中使用我们的新方法,就像这样:

//I like it here

setCoordinates(4,6);//now I am going off to setCoordinates method

//Phew, I'm back again - code continues here

在我们调用setCoordinates的地方,我们程序的执行将分支到该方法中包含的代码,直到它达到结束或被告知返回为止。然后代码将从方法调用后的第一行继续运行。

以下是另一个方法的示例,包括使方法返回到调用它的代码的代码:

int addAToB(int a, int b){
  int answer = a + b;
  return answer;
}

使用前面的方法的调用可能如下所示:

int myAnswer = addAToB(2,4); 

显然,我们不需要编写方法来将两个int变量相加,但前面的示例帮助我们更多地了解了方法的工作原理。首先,我们传递值24。在方法的签名中,值2被赋给int a,值4被赋给int b

在方法体内,ab变量被相加并用于初始化一个新变量,即int答案。return answer行就是这样做的。它将存储在answer中的值返回给调用代码,导致myAnswer被初始化为6的值。

请注意,前面示例中的每个方法签名都有所不同。其原因是 Java 方法签名非常灵活,允许我们精确构建我们需要的方法。

方法签名如何定义方法必须如何调用以及方法必须返回值(如果必须返回值)的方式,值得进一步讨论。让我们给签名的每个部分命名,以便我们可以将其分成块并分别学习。

提示

以下是一个带有标签并准备讨论的方法签名。您还可以查看以下表格,进一步确定签名的哪个部分是哪个。这将使我们对方法的讨论变得简单明了。

修饰符 | 返回类型 | 方法名称参数

以下是我们迄今为止使用的一些示例,以便您可以清楚地确定正在讨论的签名的部分:

签名的一部分示例
修饰符publicprivate
返回类型intbooleanfloat等,或任何 Java 类型,表达式或对象
方法名称shootLazerssetCoordinatesaddAToB
参数int numberstring type),(int xint y),(int aint b)等

修饰符

在我们之前的示例中,我们只使用了修饰符两次,部分原因是方法不必使用修饰符。修饰符是指定哪些代码可以使用您的方法的一种方式。一些修饰符的类型是publicprivate。实际上,常规变量也可以有修饰符,例如:

//Most code can see me
public int a;

//Code in other classes can't see me
private string secret = "Shhh, I am private";

修饰符(用于方法和变量)是一个重要的 Java 主题,但最好在我们讨论其他重要的 Java 主题时处理,这些主题我们迄今为止已经绕了几次——对象和类。

注意

正如之前承诺的那样,这些神秘的对象将在第六章中揭示,OOP – 使用他人的辛勤工作。然而,正如我们从我们的示例方法和迄今为止我们编写的所有示例都可以正常工作的事实中看到的,修饰符并不是必要的,以便促进我们迄今为止的学习。

返回类型

接下来是return类型。像修饰符一样,return类型也是可选的,尽管它对我们来说更有用。所以让我们仔细看一下。我们已经知道我们的方法可以完成任何事情。但是如果我们需要从它们所做的事情中得到结果呢?到目前为止,我们看到的最简单的返回类型示例是这样的:

int addAToB(int a, int b){
  int answer = a + b;
  return answer;
}

在这段代码中,签名中的return类型被突出显示。所以return类型是intaddAToB方法将一个值返回给调用它的代码,这个值将适合一个int变量。

return类型可以是我们到目前为止见过的任何 Java 类型。然而,方法不一定要返回一个值。在这种情况下,签名必须使用void关键字作为return类型。当使用void关键字时,方法体不得尝试返回一个值,否则会导致编译器错误。但是,它可以使用没有值的return关键字。以下是一些有效的返回类型组合和return关键字的用法:

void doSomething(){
  //our code

  //I'm done going back to calling code here
  //no return is necessary
}

returnvoid的另一种组合如下:

void doSomethingElse(){
  //our code

  //I can do this as long as I don't try and add a value
  return;
}

以下代码是returnvoid的另一种组合:

void doYetAnotherThing(){
  //some code
  if(someCondition){
    //if someCondition is true returning to calling code 
    //before the end of the method body
    return;
  }
  //More code that might or might not get executed

  return;
  //As I'm at the bottom of the method body 
  //and the return type is void, I'm 
  //really not necessary but I suppose I make it 
  //clear that the method is over.
}

String joinTogether(String firstName, String lastName){
  return firstName + lastName;
}

我们可以依次调用前面的每个方法,就像这样:

//OK time to call some methods
doSomething();
doSomethingElse();
doYetAnotherThing();
String fullName = joinTogether("Jeff ","Minter")
//fullName now = Jeff Minter
//continue with code from here

注意

前面的代码将依次执行每个方法中的所有代码语句。如果方法签名有参数,调用方法的代码会略有不同。

方法的名称

当我们设计自己的方法时,方法名是任意的,但有一个约定是使用能清楚解释方法将要做什么的动词。另一个约定是名称中第一个单词的第一个字母小写,后续单词的第一个字母大写。这被称为驼峰命名法,因为名称的形状中有一个驼峰:

XGHHY78802c(){
  //code here
}

这个名称是完全合法的,并且可以工作。然而,让我们看一个更清晰的示例,使用约定:

doSomeVerySpecificTask(){
  //code here
}

getMySpaceShipHealth(){
  //code here
}

startNewGame(){
  //code here
}

这些方法名更清晰。

现在让我们来看一下参数。

参数

我们知道方法可以将结果返回给调用代码。如果我们需要将一些数据值从调用代码与方法共享呢?参数允许我们与方法共享值。在查看返回类型时,我们已经看到了一个带有参数的示例。我们将仔细看一下相同的示例:

int addAToB(int a, int b){
  int answer = a + b;
  return answer;
}

这段代码中的参数被突出显示。请注意,在方法体的第一行中,我们使用a + b,好像它们已经声明并初始化了。那是因为它们确实是。方法签名的参数就是它们的声明,调用方法的代码初始化它们:

int returnedAnswer = addAToB(10,5);

此外,正如我们在之前的示例中部分看到的那样,我们不必在参数中使用int。我们可以使用任何 Java 类型,包括我们自己设计的类型。我们也可以混合和匹配类型。我们还可以使用尽可能多的参数来解决我们的问题。混合 Java 类型的示例可能会有所帮助:

void addToAddressBook(char firstInitial, String lastName, String city, int age){
  //all the parameters are now living breathing,
  //declared and initialized variables

  //code to add details to address book goes here
}

现在是时候认真对待我们的方法体了。

在方法体中完成任务

到目前为止,我们一直在避免的部分是方法体,有这样的注释:

//code here
    //some code

但实际上,我们已经完全知道在这里该做什么。到目前为止,我们学到的任何 Java 语法都可以在方法体中使用。事实上,如果我们回顾一下,到目前为止我们写的所有代码都是在一个方法中,尽管是别人的方法。例如,我们在onCreateonClick方法中编写了代码。

我们接下来最好做的事情是在方法体中写一些真正有用的方法。

使用方法

我们不必在我们的数学游戏项目中瞎搞。我们将快速为接下来的两个探索方法创建一个新的空白项目。

我们也不需要花时间制作 UI。我们将使用 Android 控制台查看结果,并讨论我们的方法示例的影响。由于我们正在使用 Android 控制台查看我们使用方法的工作结果,我们需要在 Android 模拟器上运行所有这些示例,而不是在真实设备上。

注意

可以设置真实设备输出到控制台,但我们在本书中没有涵盖这一点。如果您想了解更多关于使用实际设备进行调试的信息,请查看developer.android.com/tools/device.html上的文章。

通常情况下,您可以以通常的方式打开已输入的代码文件。关于方法的下两个示例可以在Chapter4文件夹和AWorkingMethodExploringMethodOverloading子文件夹中的 Packt Publishing 代码下载中找到。

提示

以下是如何创建一个新的空白项目的快速提醒。

  1. 通过导航到File | Close Project关闭当前打开的任何项目。

  2. 点击New Project...

  3. 将出现Create New Project配置窗口。在Application name字段和Company Domain中填写packtpub.com,或者您可以在此处使用您自己公司的网站名称。

  4. 现在点击Next按钮。在下一个屏幕上,确保Phone and tablet复选框中有一个勾。现在我们必须选择我们想要为其构建应用程序的最早版本的 Android。随意在下拉选择器中尝试几个选项。您会发现,我们选择的版本越早,我们的应用程序支持的设备百分比就越大。然而,这里的权衡是,我们选择的版本越早,我们的应用程序中可以拥有的最新 Android 功能就越少。一个很好的平衡是选择API 8: Android 2.2 (Froyo)。按照下一个截图中所示进行操作。

  5. 点击Next。现在选择Blank Activity,然后再次点击Next

  6. 在下一个屏幕上,只需将Activity Name更改为MainActivity,然后点击Finish

  7. 与我们在第二章中所做的一样,为了保持我们的代码清晰简单,您可以删除两个不需要的方法(onCreateOptionsMenuonOptionsItemSelected)及其相关的@override@import语句,但这对示例的工作并不是必需的。

有关创建新项目的详细说明和图像,请参见第二章,开始使用 Android

一个工作方法

首先,让我们创建一个简单的工作方法,包括返回类型和完全功能的主体。

这个方法将接受三个数字作为参数,并根据这三个数字中是否有一个是在方法中随机生成的,返回一个truefalse值给调用代码:

  1. 创建一个名为A Working Method的新空白项目。

  2. 在这个方法中,我们将使用我们之前看到的Random类及其randInt方法作为演示的一部分。将此方法的代码复制到onCreate的结束括号之后,但在MainActivity的结束括号之前。当提示导入任何类时,只需点击OK

  boolean guessANumber(int try1, int try2, int try3){
  //all the Log.i lines print to the Android console
  Log.i("info", "Hi there, I am in the method body");
  //prove our parameters have arrived in the method
  //By printing them in the console
  Log.i("info", "try1 = " + try1);
  Log.i("info", "try2 = " + try2);
  Log.i("info", "try3 = " + try3);
  1. 现在我们声明一个名为found的布尔变量,并将其初始化为false。如果我们猜对了随机数,我们将把found更改为true。接下来,我们声明我们的随机数,并将一些有用的值打印到控制台:
  //we use the found variable to store our true or false
  //setting it to false to begin with
  boolean found = false;

  //Create an object of the Random class so we can use it
  Random randInt = new Random();
  //Generate a random number between 0 and 5
  int randNum = randInt.nextInt(6);
  //show our random number in the console
  Log.i("info", "Our random number = " + randNum);
  1. 我们方法中的最后一部分代码测试是否有任何匹配我们传入参数的内容,打印一些输出,然后使用found变量将truefalse返回给onCreate方法中的调用代码:
  //Check if any of our guesses are the same as randNum
  if(try1 == randNum || try2 == randNum || try3 == randNum){
    found = true;
    Log.i("info", "aha!");
   }else{
     Log.i("info", "hmmm");
   }

  return found;
 }
  1. 现在在onCreate方法的结束括号之前编写以下代码,以调用代码并将一些值打印到 Android 控制台:
//all the Log.i lines print to the Android console
Log.i("info", "I am in the onCreate method");

//Call guessANumber with three values
//and if true is returned output - Found it!
if(guessANumber( 1,2,3 )) {
  Log.i("info", "Found It!");
}else{//guessANumber returned false -didn't find it
  Log.i ("info", "Can't find it");
}

//continuing with the rest of the program now
Log.i("info", "Back in onCreate");
  1. 启动模拟器。

  2. 在模拟器上运行应用程序。

  3. 我们所有的控制台消息都有一个名为info的标签。控制台窗口已经出现在编辑窗口下方。我们可以通过在搜索框中输入info来过滤其内容,只显示我们的消息,如下面的截图所示:A working method

在上面的截图中,您可以看到搜索过滤器和控制台输出。我们现在将运行代码并解释输出。

为了清晰起见,这是精确的控制台输出,没有在每行开头添加多余的日期、时间和包名。请记住,我们正在处理一个随机数,因此您的输出可能会有所不同:

info: I am in the onCreate method
info﹕Hi there, I am in the method body
info﹕try1 = 1
info﹕try2 = 2
info﹕try3 = 3
info﹕Our random number = 0
info﹕hmmm
info﹕Can't find it
info﹕Back in onCreate

这里发生了什么。在第 2 步中,我们开始编写我们的第一个方法。我们称它为guessANumber。它有三个int参数,并将返回一个布尔值。请记住,这三个int参数将成为完全初始化的变量。然而,首先在我们的方法中,我们只是输出传入参数的新变量的值,以及一个确认我们的方法中的代码当前正在执行的消息:

boolean guessANumber(int try1, int try2, int try3){
  //all the Log.i lines print to the Android console
  Log.i("info", "Hi there, I am in the method body");
  //prove our parameters have arrived in the method
  //By printing them in the console
  Log.i("info", "try1 = " + try1);
  Log.i("info", "try2 = " + try2);
  Log.i("info", "try3 = " + try3);

在第 3 步中,我们向我们的方法添加了更多代码。我们声明并初始化了一个名为found的布尔变量,我们将使用它来返回一个值给调用代码,并让调用代码知道传入的参数中是否有一个与随机数相同:

//we use the found variable to store our true or false
//setting it to false to begin with
boolean found = false;

接下来(仍然是第 3 步),我们以与本章前面相同的方式生成了一个随机数。我们还使用Log输出随机数,以便我们可以检查发生了什么:

//Create an object of the Random class so we can use it
Random randInt = new Random();
//Generate a random number between 0 and 5
int randNum = randInt.nextInt(6);
//show our random number in the console
Log.i("info", "Our random number = " + randNum);

在第 4 步中,我们使用了一个带有逻辑或运算符的if语句来检测传入的参数中是否有任何一个与我们刚生成的随机数匹配,如下面的代码所示:

//Check if any of our guesses are the same as randNum
if(try1 == randNum || try2 == randNum || try3 == randNum){

如果条件成立,也就是说,如果try1try2try3中的任何一个等于randNum,则运行以下代码。我们的found布尔值设置为true,并打印一条消息:

found = true;
Log.i("info", "aha!");

如果条件不成立,将执行else语句,打印不同的消息,并且found变量保持不变,仍为false

}else{
  Log.i("info", "hmmm");
}

最后,在我们的方法中,我们返回found变量,它将是truefalse,返回给调用代码:

  return found;
 }

现在我们来看第 5 步,即onCreate方法中调用我们的guessANumber方法的代码。我们首先简单地打印一条消息,说明我们目前正在onCreate中:

//all the Log.i lines print to the Android console
Log.i("info", "I am in the onCreate method");

然后我们调用guessANumber并传入三个参数。在这种情况下,我们使用了 1、2 和 3,但任何int值都可以工作。但是,我们将调用包装在一个if语句中。这意味着方法的return值将用于评估if语句。简而言之,如果返回true,则将执行if语句,并打印“Found It”:

//Call guessANumber with three values
//and if true is returned output - Found it!
if(guessANumber(1,2,3)){
  Log.i("info", "Found It!");
  }

相反,如果返回false,则执行else语句,并打印“Can't find it”:

else{//guessANumber returned false -didn't find it
  Log.i ("info", "Can't find it");
}

//continuing with the rest of the program now
Log.i("info", "Back in onCreate");

请记住,我们正在处理随机数,因此您可能需要运行几次才能看到这个输出:

A working method

当然,您应该注意,作为参数发送到函数的猜测是任意的。只要所有数字都在 0 到 5 之间,且不重复,它们一起有 50%的几率找到随机数。

最后,如果您只想读这本书中的一个提示,那就是这个。

提示

将变量值打印到控制台是检查游戏内部发生了什么以及查找错误的好方法。

让我们看一个方法的另一个例子。

探索方法重载

正如我们所学到的,方法作为一个主题确实是多样且深刻的,但希望一步一步地,我们会发现它们并不令人畏惧。当我们增强我们的数学游戏时,我们将使用我们对方法的了解。在第六章OOP – 使用他人的辛勤工作中,我们将更深入地探索方法。然而,现在,我们将看一看关于方法的另一个主题。让我们创建一个新项目来探索方法重载

正如我们将要看到的,我们可以创建多个具有相同名称的方法,只要参数不同。这个项目中的代码比上一个项目简单得多。直到我们稍后分析它的工作原理,这段代码可能看起来有点奇怪:

  1. 创建一个名为“探索方法重载”的新空项目。

  2. 在第一个方法中,我们将简单地调用它printStuff并通过参数传递一个int变量进行打印。将此方法的代码复制到onCreate的结束括号之后,但在MainActivity的结束括号之前。当提示导入任何类时,只需点击确定

void printStuff(int myInt){
  Log.i("info", "This is the int only version");
  Log.i("info", "myInt = "+ myInt);
}
  1. 我们还将调用第二个方法printStuff,但传递一个要打印的string变量。将此方法的代码复制到onCreate的结束括号之后,但在MainActivity的结束括号之前。同样,当提示导入任何类时,只需点击确定
void printStuff(String myString){
  Log.i("info", "This is the String only version");
  Log.i("info", "myString = "+ myString);
}
  1. 再一次,我们将调用这第三个方法printStuff,但传递一个要打印的string变量和一个int变量。与之前一样,将此方法的代码复制到onCreate的结束括号之后,但在MainActivity的结束括号之前:
void printStuff(int myInt, String myString){
  Log.i("info", "This is the combined int and String version");
  Log.i("info", "myInt = "+ myInt);
  Log.i("info", "myString = "+ myString);
}
  1. 现在,在onCreate方法的结束括号之前编写这段代码,以调用方法并将一些值打印到 Android 控制台:
//declare and initialize a String and an int
int anInt = 10;
String aString = "I am a string";

//Now call the different versions of printStuff
//The name stays the same, only the parameters vary
printStuff(anInt);
printStuff(aString);
printStuff(anInt, aString);
  1. 启动模拟器。

  2. 在模拟器上运行应用程序。

以下是控制台输出:

info﹕ This is the int only version
info﹕ myInt = 10
info﹕ This is the String only version
info﹕ myString = I am a string
info﹕ This is the combined int and String version
info﹕ myInt = 10
info﹕ myString = I am a string

正如你所看到的,Java 已经将具有相同名称的三个方法视为完全不同的方法。正如我们刚刚演示的那样,这可能非常有用。这被称为方法重载

提示

方法重载和覆盖的混淆

重载和覆盖的定义如下:

  • 重载发生在我们有多个具有相同名称但不同参数的方法时

  • 覆盖发生在我们实质上用相同名称和相同参数列表替换方法时

我们对重载和覆盖的了解已经足够完成本书,但如果你勇敢而且心神游荡,你可以覆盖重载的方法。然而,这是另一个时间的事情。

这就是前面的代码是如何工作的。在三个步骤(2、3 和 4)中,我们分别创建了一个名为printStuff的方法,但每个printStuff方法都有不同的参数,因此每个方法都是可以单独调用的不同方法:

void printStuff(int myInt){
...
}

void printStuff(String myString){
...
}

void printStuff(int myInt, String myString){
...
}

每个方法的主体都很简单。它只是打印传入的参数,并确认当前调用的方法版本。

我们代码的下一个重要部分是当我们明确指出要调用哪个方法时,使用适当的参数。在第 5 步中,我们依次调用它们,使用适当的参数,以便 Java 知道所需的确切方法:

printStuff(anInt);
printStuff(aString);
printStuff(anInt, aString);

现在我们对方法、循环和随机数的了解已经足够多,可以对我们的数学游戏进行一些改进了。

增强我们的数学游戏

我们将使用我们刚刚学到的关于方法和循环的知识,为我们的数学游戏添加一些功能。

通常情况下,代码可以在代码下载的Chapter4文件夹中找到。该项目位于MathGameChapter4子文件夹中,包括本章中涵盖的所有改进阶段,包括增强 UI、修改游戏活动、setQuestionupdateScoreAndLevelisCorrect和调用我们的新方法。

我们将使游戏在每次尝试答案后更改问题。

我们还将为问题添加难度级别和随机问题,但在适当难度级别范围内。

我们将显示和更新我们的分数。正确回答问题的难度级别越高,分数上升得越快。

如果玩家答错问题,难度将回到最简单的级别,分数将变为零。

增强 UI

让我们继续修改我们的数学游戏 UI,以整合我们的新游戏功能。我们将添加一个 TextView 来显示分数,另一个 TextView 来显示级别。

  1. 在编辑窗口中打开activity_game.xml文件。我们将在 UI 的底部添加一个新的 TextView 来显示我们的分数。

  2. Palette中拖动一个Large Text元素,并将其放置在左侧,放置在我们的三个答案按钮下方。

  3. 现在我们需要更改id属性,以便我们可以从 Java 代码中访问我们的新 TextView。确保通过单击它来选择新的 TextView。现在,在Properties窗口中,将id属性更改为textScore

  4. 为了清晰起见(尽管这一步在编程中没有用处),将text属性更改为Score:999

  5. 现在将另一个Large Text元素放置在我们刚刚配置的元素的右侧,并将id属性更改为textLevel。我们的 UI 的下半部分现在应该是这样的:Enhancing the UI

  6. 再次为了清晰起见(尽管这一步在编程中没有用处),将text属性更改为Level:4

  7. 保存项目。

我们刚刚添加了两个新的 TextView 元素,并为它们分配了一个我们可以在 Java 代码中引用的 ID。

提示

你可能已经意识到,就让游戏运行而言,我们的 UI 元素的精确布局和大小并不重要。这为我们在设计不同屏幕尺寸的布局时提供了很大的灵活性。只要每个屏幕尺寸的布局包含相同的元素类型和相同的 ID,相同的 Java 代码就可以用于不同的布局。如果你想了解更多关于为多个屏幕尺寸设计的信息,请查看developer.android.com/training/multiscreen/screensizes.html

现在我们有了增强的 UI 和对 Java 的Random类如何工作的理解,我们可以添加 Java 代码来实现我们的新功能。

新的 Java 代码

如前所述,项目代码可在可下载代码的Chapter4文件夹中找到。该项目称为MathGameChapter4,包含了本章涵盖的所有改进。

在这个阶段,我们将添加大量新代码,移动一些现有代码,并修改一些现有代码。由于变化如此之大,我们将从头开始处理代码。新代码将被完全解释,移动的代码将被指出原因,保持不变且位置不变的代码将有最少的解释。

我们将首先对我们现有的代码进行一些修改和删除。然后,我们将着手设计和实现每个新方法,以改进我们的代码并添加新功能。

修改 GameActivity

首先,让我们对我们当前的代码进行必要的修改和删除:

  1. 在编辑窗口中打开GameActivity.java文件。

  2. 现在我们需要考虑代表我们 UI 元素的对象的范围。textObjectPartAtextObjectPartB都需要从我们即将创建的方法中访问。因此,让我们像在上一章中对多选按钮所做的那样,将它们的声明移到onCreate方法之外,以便它们在GameActivity类的任何地方都可以访问。以下代码显示了到目前为止所有我们的声明。它们出现在GameActivity类开始之后。最近添加(或移动)的声明已经被突出显示。请注意,我们还为我们的两个新文本视图和分数和等级显示添加了声明。此外,还有两个新的int变量,我们可以用来操作我们的分数和跟踪我们的等级。它们是currentScorecurrentLevel

public class GameActivity extends Activity implements View.OnClickListener{

    int correctAnswer;
    Button buttonObjectChoice1;
    Button buttonObjectChoice2;
    Button buttonObjectChoice3;
    TextView textObjectPartA;
 TextView textObjectPartB;
 TextView textObjectScore;
 TextView textObjectLevel;

 int currentScore = 0;
 int currentLevel = 1;

  1. 为我们的按钮或文本视图对象分配文本的所有代码,以及初始化问题部分并为错误答案分配值的代码,现在都要更改和移动,所以我们需要全部删除。以下代码中显示的所有内容都将被删除:
//Here we initialize all our variables
int partA = 9;
int partB = 9;
correctAnswer = partA * partB;
int wrongAnswer1 = correctAnswer - 1;
int wrongAnswer2 = correctAnswer + 1;
  1. 以下代码片段也需要被删除:
//Now we use the setText method of the class on our objects
//to show our variable values on the UI elements.
textObjectPartA.setText("" + partA);
textObjectPartB.setText("" + partA);

//which button receives which answer, at this stage is arbitrary.
buttonObjectChoice1.setText("" + correctAnswer);
buttonObjectChoice2.setText("" + wrongAnswer1);
buttonObjectChoice3.setText("" + wrongAnswer2);
  1. 为了清晰和上下文,以下是整个onCreate方法的当前状态。这里没有新内容,但你可以看到我们在第 2 步中声明的按钮和文本视图对象的代码。再次强调,这段代码包括我们的两个新文本视图,但其他所有内容,即第 3 步和第 4 步中描述的内容,都已被删除。与以前一样,有一段代码使我们的游戏监听按钮点击:
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //The next line loads our UI design to the screen
        setContentView(R.layout.activity_game);

        /*Here we get a working object based on either the button
          or TextView class and base as well as link our new objects
          directly to the appropriate UI elements that we created previously*/
        textObjectPartA = (TextView)findViewById(R.id.textPartA);

        textObjectPartB = (TextView)findViewById(R.id.textPartB);

 textObjectScore = (TextView)findViewById(R.id.textScore);

 textObjectLevel = (TextView)findViewById(R.id.textLevel);

        buttonObjectChoice1 = (Button)findViewById(R.id.buttonChoice1);

        buttonObjectChoice2 = (Button)findViewById(R.id.buttonChoice2);

        buttonObjectChoice3 = (Button)findViewById(R.id.buttonChoice3);

        buttonObjectChoice1.setOnClickListener(this);
        buttonObjectChoice2.setOnClickListener(this);
        buttonObjectChoice3.setOnClickListener(this);

}//onCreate ends here
  1. 现在我们将删除一些不需要的代码,因为我们将通过将其分隔到我们的新方法中并同时添加新功能来使其更有效。因此,在我们的onClick方法中,在switch语句的每种情况下,我们要删除ifelse语句。我们将完全重写这些内容,但我们将保留初始化answerGiven变量的代码。我们的onClick方法现在将如下所示:
@Override
    public void onClick(View view) {
        //declare a new int to be used in all the cases
        int answerGiven=0;
        switch (view.getId()) {

            case R.id.buttonChoice1:
                //initialize a new int with the value contained in buttonObjectChoice1
                //Remember we put it there ourselves previously
                answerGiven = Integer.parseInt("" + buttonObjectChoice1.getText());

                break;

            case R.id.buttonChoice2:
                //same as previous case but using the next button
                answerGiven = Integer.parseInt("" + buttonObjectChoice2.getText());

                break;

            case R.id.buttonChoice3:
                //same as previous case but using the next button
                answerGiven = Integer.parseInt("" + buttonObjectChoice3.getText());

                break;

        }

    }
  1. 保存你的项目。

哇!那是很多代码,但正如我们一路看到的那样,没有新概念。在第 2 步中,我们只是将按钮和文本视图对象的初始化移到了一个现在可以从我们的类中的任何地方看到的地方。

在第 3 步和第 4 步中,我们进行了相当多的删除,因为我们将不再在onCreate中提出问题或填充多选按钮,因为这样做不够灵活。我们很快就会看到我们如何改进这一点。

在第 6 步中,我们删除了测试答案是否正确或不正确的代码。然而,正如我们所看到的,我们仍然以相同的方式初始化了answerGiven变量——在onClick方法的switch语句的适当情况下。

太棒了!现在我们准备考虑并设计一些新方法,将我们的代码分隔开,避免重复,并添加我们的额外功能。考虑一下我们很快将实现的以下方法。

方法

现在我们将逐步编写一些方法。正如我们将看到的那样,这些方法将把我们的代码分隔开,并防止我们的新功能的实现导致代码变得过长和杂乱:

  • 我们将编写一个setQuestion方法来准备一个适当难度的问题。

  • 我们将编写一个updateScoreAndLevel方法来实现这一点。我们还将编写一个isCorrect方法,我们的其他方法将使用它来评估答案的正确性。

  • 然后我们将策略性地放置调用我们新方法的代码。

我们将逐个执行这些任务,并在此过程中解释代码,因为将解释留到最后会使参考各个步骤变得麻烦。

我们将使用本章和上一章学到的许多 Java 特性。这些包括以下内容:

  • 方法

  • 一个for循环

  • 开关控制结构

所以让我们开始我们的第一个方法。

setQuestion 方法

我们确定我们需要一个方法来为我们准备一个问题;setQuestion似乎是这样一个方法的不错名称。每当我们的玩家通过点击三个多选按钮之一给出答案时,都需要准备一个新的问题。

这个方法将需要为我们的partApartB变量生成值,并在由textObjectPartAtextObjectPartB对象引用的 TextViews 中显示它们。此外,该方法将需要将新的正确答案分配给我们的correctAnswer变量,然后用它来计算一些合适的错误答案。最后,该方法将在我们的多选按钮上显示正确和错误答案。

此外,我们的setQuestion方法将需要考虑currentLevel中保存的级别,以确定它将提出的问题的范围或难度。让我们来看看代码。如果你想一边跟着我们打这段代码,那么请确保你把它放在onClick的闭合括号之后,但在我们的GameActivity类的闭合括号之前:

  1. 首先,我们有方法签名和方法体之前的左花括号:
void setQuestion(){
  1. 这告诉我们返回类型是void,所以setQuestion不会向调用它的代码返回值。此外,这里没有参数,所以它不需要任何值来工作。让我们看看它做了什么。现在我们输入代码来生成问题的两个部分:
//generate the parts of the question
int numberRange = currentLevel * 3;
Random randInt = new Random();

int partA = randInt.nextInt(numberRange);
partA++;//don't want a zero value

int partB = randInt.nextInt(numberRange);
partB++;//don't want a zero value
  1. 在上一步中,我们声明了一个新的int变量numberRange,并通过将玩家的currentLevel值乘以3来初始化它。然后我们得到了一个名为randInt的新的Random对象,并用它来生成基于numberRange的新值。我们对partApartB变量做了这个。随着currentLevel的值的增加,问题的难度也可能增加。现在,就像我们过去写的那样,我们写了这个:
correctAnswer = partA * partB;
int wrongAnswer1 = correctAnswer-2;
int wrongAnswer2 = correctAnswer+2;

textObjectPartA.setText(""+partA);
textObjectPartB.setText(""+partB);
  1. 我们将我们新的乘法问题的答案赋给correctAnswer。然后我们声明并赋值了两个错误答案给新的int变量wrongAnswer1wrongAnswer2。我们还使用了我们的 TextView 对象的setText方法来向玩家显示问题。注意我们还没有显示正确和错误答案。这就是它。试着弄清楚这里发生了什么:
//set the multi choice buttons
//A number between 0 and 2
int buttonLayout = randInt.nextInt(3);
switch (buttonLayout){

case 0:
buttonObjectChoice1.setText(""+correctAnswer);
buttonObjectChoice2.setText(""+wrongAnswer1);
buttonObjectChoice3.setText(""+wrongAnswer2);
   break;

case 1:

buttonObjectChoice2.setText(""+correctAnswer);
buttonObjectChoice3.setText(""+wrongAnswer1);
buttonObjectChoice1.setText(""+wrongAnswer2);
   break;

case 2:
buttonObjectChoice3.setText(""+correctAnswer);
buttonObjectChoice1.setText(""+wrongAnswer1);
buttonObjectChoice2.setText(""+wrongAnswer2);
   break;
 }

}
  1. 在前面的代码中,我们使用了我们的Random对象randInt来生成 0 到 2 之间的数字,并将该值赋给一个名为buttonLayout的新的int变量。然后我们使用buttonLayout来在它的所有可能值之间进行切换:0、1 或 2。每个case语句将正确和错误答案设置为多选按钮上的略有不同的顺序,这样玩家就不能一直点击同一个按钮来获得大量分数。注意在switch结束括号之后有一个额外的闭合括号。这是我们setQuestion方法的结束。

我们在通过代码时相当彻底地解释了代码,但再仔细看一些部分可能是值得的。

在步骤 1 中,我们看到了我们的方法签名,返回类型为void,没有参数。在步骤 2 中,我们生成了一些将在一定范围内的随机数。这个范围并不像乍看起来那么明显。首先,我们这样赋值、声明和初始化了numberRange

int numberRange = currentLevel * 3;

所以如果玩家在第一个问题上,那么currentLevel将保存值1numberRange将被初始化为3。然后我们创建了一个新的Random对象,就像之前讨论的那样,并输入了这行代码:

int partA = randInt.nextInt(numberRange);

这里发生的是Random对象randIntnextInt方法将返回 0、1 或 2 的值,因为我们给它一个种子 3。我们不希望游戏中有任何零,因为它们会导致非常简单的乘法,所以我们输入了这个:

partA++;//don't want a zero value

这个操作符,你可能还记得来自第三章,“说 Java-你的第一个游戏”,当我们讨论操作符时,将 1 添加到partA。然后我们对partB变量做完全相同的操作,这意味着假设玩家仍然处于 1 级,他们将有一个以下问题之一:

1 x 1, 1 x 2, 1 x 3, 2 x 1, 2 x 2, 2 x 3, 3 x 1, 3 x 2, 或 3 x 3

随着级别的提高,问题的潜在范围显着增加。因此,在第 2 级,问题的选项可能是 1 到 6;对于第 3 级,从 1 到 9;等等。在更高的级别仍然可能得到一个简单的问题,但随着级别的提高,这种可能性变得越来越小。最后,在这一步中,我们使用setText方法向玩家显示问题。

在第 3 步中,我们之前已经见过,但这次我们稍微改变了它。我们计算并为correctAnswer分配一个值,并声明并为wrongAnswer1wrongAnswer2分配值,它们将保存按钮的错误答案选择。

第三部分与上一章的onCreate稍有不同,因为我们分别从wrongAnswer1wrongAnswer2中减去和加上 2。这使得猜测乘法问题的答案变得更难,因为你不能根据答案是奇数还是偶数来排除答案。

第 4 步只是随机确定正确和错误答案将放在哪些按钮上。我们不需要跟踪这一点,因为当比较按钮上的值与正确答案时,我们可以简单地使用我们的 Java 代码来发现它,就像我们在第三章,“说 Java-你的第一个游戏”中所做的那样。

更新updateScoreAndLevel方法

这个方法的名称说明了它自己。因为得分的保持并不简单,而且我们希望更高的级别能够产生更高的分数,我们将把代码分隔开来保持我们的程序可读性。如果我们想对记分系统进行修改,它们都可以在那里进行。

让我们写代码。

  1. 这段代码可以放在GameActivity {}的大括号的任何位置,但最好的做法是按照它们将被使用的大致顺序放置。那么为什么不在setQuestion的结束大括号之后开始添加你的代码,但显然在GameActivity的结束大括号之前?这是带有开始大括号的方法签名:
void updateScoreAndLevel(int answerGiven){
  1. 这告诉我们,我们的方法不返回值,但它接收一个int,它将需要执行其操作。参数的名称是我们将要传递的一个重要线索。我们将在一分钟内在主体中看到它的作用,但如果将玩家的答案传递给这个方法而不是isCorrect方法有点困惑,我们将在下一段代码中看到事情变得更清晰。这是要添加的代码的下一部分:
if(isCorrect(answerGiven)){
  for(int i = 1; i <= currentLevel; i++){
    currentScore = currentScore + i;
   }

   currentLevel++;
}
  1. 这里发生了很多事情,所以一旦我们完成了这个方法,我们将更详细地解剖它。基本上,它调用isCorrect方法(我们很快将写出来),如果响应是true,则在for循环中增加玩家的分数。之后,该方法将currentLevel增加 1。这是代码的else部分,以防isCorrect的响应是false
else{
  currentScore = 0;
  currentLevel = 1;
}
  1. 如果响应是false,也就是说,如果玩家答错了,currentScore变量将设置为0,级别回到1。最后,对于这个方法,我们输入以下内容:
  //Actually update the two TextViews
  textObjectScore.setText("Score: " + currentScore);
  textObjectLevel.setText("Level: " + currentLevel);
}
  1. 在上一步中,我们更新了玩家看到的实际 TextViews,显示了新确定的分数和级别。然后方法结束,程序的控制返回到最初调用updateScoreAndLevel的代码。保存你的项目。

我们在进行代码编写时已经解释了大部分代码,但快速回顾并深入研究某些部分可能会有好处,特别是在那个看起来很奇怪的if语句中调用isCorrect

在步骤 1 中,我们从方法签名开始。然后在步骤 2 中,我们从上述奇怪的if开始:

if(isCorrect(answerGiven)){

我们在本章的方法部分的一个有效的方法示例中已经见过这种类型的语句。这里发生的是isCorrect的调用正在替换要评估的语句,或者更确切地说,要评估的语句。因此,isCorrectanswerGiven变量调用。您可能还记得,answerGiven变量被传递给updateScoreAndLevel。这次,它被传递给isCorrect方法,它将对其进行一些处理,也许还有其他一些事情。然后它将返回truefalse的值给if语句。如果问题回答正确,则该值为 true,否则为 false。

假设if语句评估为 true,则程序运行此代码段(也来自步骤 2):

for(int i = 1; i <= currentLevel; i++){
  currentScore = currentScore + i;
}

currentLevel++;

代码进入一个for循环,其中起始变量i被初始化为 1,如此:int i = 1;。此外,循环被指示只要i小于或等于我们的currentLevel变量就继续。然后在for循环内,我们将i添加到当前分数。例如,假设玩家刚刚回答了一个问题正确,我们以currentLevel为 1 进入for循环。玩家的分数仍然为 0,因为这是他们的第一个正确答案。

在第 1 次通过时,我们得到以下结果:

  • i = 1,所以它等于currentLevel,也是 1。所以我们进入for循环

  • i = 1,所以currentScore等于 0

  • 我们将i,即1,加到currentScore

  • 我们的currentScore变量现在等于1

在第 2 次通过时,发生以下步骤:

  • i增加到 2,所以现在大于currentLevel,即 1

  • for循环条件评估为false,我们继续for循环后的代码

  • currentLevel增加 1 到 2

现在让我们再次看看for循环,假设玩家下一个问题也回答正确,我们回到updateScoreAndLevel。这次,isCorrect评估为 true,我们进入for循环,但情况与上次略有不同。

在第 1 次通过时,发生以下步骤:

  • i = 1,所以i小于currentLevel是 2,我们进入for循环

  • i = 1currentScore = 1

  • 我们将i,即 1,加到currentScore

  • 我们的currentScore变量现在等于 2

在第 2 次通过时,发生以下步骤:

  • i增加到 2,现在等于currentLevel,也是 2

  • i = 2currentScore = 2

  • 我们将i,现在等于 2,加到currentScore

  • 我们的currentScore变量现在等于 4

在第 3 次通过时,发生以下步骤:

  • i增加到 3,现在大于currentLevel,即 2。

  • for循环条件评估为 false,我们继续for循环后的代码。

  • currentLevel的值增加 1 到 3。所以下次,我们将有额外的通过我们的for循环。

随着每个级别,玩家将获得通过for循环的另一个奖励,并且每次通过for循环都会为他们的分数增加更大的值。总结一下for循环中发生的情况,这里是一个简短的值表,显示了玩家的分数如何基于currentLevel变量增加:

currentLevel添加到 currentScorefor 循环后的 currentScore
111
23 (1 + 2)4
36 (1 + 2 + 3)10
410 (1 + 2 + 3 + 4)20
515 (1 + 2 + 3 + 4 + 5)35

注意

当然,我们本可以保持非常简单,不使用for循环。也许我们只需使用currentScore = currentScore + level,但这并不像我们当前的解决方案那样提供不断增加的奖励,并且我们也无法练习for循环。

如果if(isCorrect(answerGiven))评估为false,则在第 3 步中它只是将分数重置为 0,将级别重置为 1。然后在第 4 步中,使用我们刚讨论过的变量更新我们的分数和级别的 TextView。

现在我们只需要编写一个方法。当然,这就是我们刚刚调用的isCorrect方法。

isCorrect 方法

这个方法很简单,因为我们之前已经看到了所有相关的代码。我们只需要仔细查看方法签名和返回值:

  1. updateScoreAndLevel方法的右括号之后,但在GameActivity类的右括号之前输入代码。像这样输入方法签名:
boolean isCorrect(int answerGiven){
  1. 在这里,我们可以看到该方法必须返回一个布尔值,truefalse。如果没有,程序就无法编译。这保证了当我们将这个方法用作updateScoreAndLevel方法中的评估表达式时,我们一定会得到一个结果。它可以是 true 或 false。方法签名还向我们展示了传入的answerGiven变量,准备供我们使用。输入这段代码,它将确定结果:
boolean correctTrueOrFalse;
if(answerGiven == correctAnswer){//YAY!
  Toast.makeText(getApplicationContext(), "Well done!", Toast.LENGTH_LONG).show();
   correctTrueOrFalse=true;
}else{//Uh-oh!
    Toast.makeText(getApplicationContext(), "Sorry", Toast.LENGTH_LONG).show();
    correctTrueOrFalse=false;
}
  1. 我们几乎已经看到了前面的所有代码。唯一的例外是,我们声明了一个布尔变量correctTrueOrFalse,如果玩家回答正确则赋值为true,如果不正确则赋值为false。我们知道玩家是否正确,因为我们在if语句中将answerGivencorrectAnswer进行比较。请注意,我们还触发了适当的 Android 弹出式提示消息,就像以前一样。最后,我们这样做:
  return correctTrueOrFalse;
}

我们只返回correctTrueOrFalse中包含的任何值。因此,在我们详细讨论过的updateScoreAndLevel中的关键if语句将知道接下来该做什么。

为了确保我们理解isCorrect中发生了什么,让我们通过代码中的事件顺序进行一遍。在第 1 步中,我们有方法签名。我们看到我们将返回一个truefalse值,并接收int

在第 2 步中,我们声明一个名为correctTrueOrFalse的布尔变量来保存我们即将返回的值。然后我们用if(answerGiven == correctAnswer)来测试答案是否正确。如果两个比较的值匹配,将弹出祝贺消息,并将true赋给我们的布尔变量。当然,如果if语句为false,我们会向玩家表示慰问,并将false赋给我们重要的布尔变量。

最后,在第 3 步中,我们发送truefalse,以便updateScoreAndLevel方法可以继续工作。

我们现在已经实现了所有的方法。是时候让它们发挥作用了。

调用我们的新方法

当然,我们闪亮的新方法在我们调用它们之前不会做任何事情。因此,这是调用这些方法的计划:

  1. 游戏开始时,我们希望为玩家设置一个新问题。因此,在我们的onCreate方法中的最后一行代码中,我们可以这样调用我们的setQuestion方法:
  setQuestion();

}//onCreate ends here
  1. 然后我们转向onClick方法,它已经检测到哪个按钮被按下,并将玩家的答案加载到我们的answerGiven变量中。因此,在onClick方法的结尾,在switch语句的右括号之后,我们只需调用这个函数:
updateScoreAndLevel(answerGiven);
  1. 这将我们玩家尝试的答案发送到updateScoreAndLevel,它使用isCorrect来评估答案,添加分数,并在答案正确时增加分数,如果答案不正确,则重置分数和级别。现在我们只需要另一个问题。添加这行。它会问另一个问题:
setQuestion();

现在玩家通过在其 Android 设备上点击其图标来开始我们的数学游戏。我们的GameActivity类声明了一些我们需要访问的变量:

int correctAnswer;
Button buttonObjectChoice1;
Button buttonObjectChoice2;
Button buttonObjectChoice3;
TextView textObjectPartA;
TextView textObjectPartB;
TextView textObjectScore;
TextView textObjectLevel;

int currentScore = 0;
int currentLevel = 1;

然后onCreate初始化一些变量,并准备好接收玩家点击的按钮,然后通过调用setQuestion来询问第一个问题。游戏然后等待玩家尝试回答。当玩家尝试回答时,它将由onClickupdateScoreAndLevelisCorrect处理。然后程序控制再次回到onClick,再次调用setQuestion,然后我们再次等待玩家的答案。

最后的修饰

我们的数学游戏进展顺利。不幸的是,我们很快就要继续了。该项目已经达到了它展示一些 Java 编程基础以及一些关键 Android 功能的目的。现在我们需要开始介绍一些更多与游戏相关的主题。

在继续之前,有两件非常容易的事情可以使我们的游戏变得更酷更完整。如果你想知道高分按钮,我们将在第五章中看到如何实现它,游戏和 Java 基础。然后你将有足够的信息来轻松地回来并自己实现高分。

另一个真正完善我们的游戏并使其更具可玩性的功能是整体或每个问题的时间限制。也许根据正确答案给出的速度来增加分数会有所帮助。在我们谈论线程时,我们需要一些新的 Java 技巧,但我们将在第五章中看到我们如何测量和响应时间,游戏和 Java 基础

现在我们将快速学习两个改进:

  • 锁定屏幕方向

  • 更改主屏幕图像

全屏和锁定方向

您可能已经注意到,如果在应用程序运行时旋转设备,不仅游戏 UI 会变形,而且游戏进度也会丢失。出现问题的原因是当设备旋转时,将调用onPauseonStop方法。然后应用程序将重新启动。我们可以通过重写onPause方法并保存我们的数据来处理这个问题。我们稍后会这样做。现在无论如何我们都不希望屏幕旋转,所以如果我们停止它,我们就解决了两个问题。

在向此文件添加代码时,Android Studio 可能会尝试通过添加额外的格式来“帮助”。如果出现红色错误指示器,您可以将您的AndroidManifest.xml文件与Chapter4/MathGameChapter4文件夹中的代码下载中的文件进行比较。或者,您可以简单地用下载文件中的文件内容替换您的文件内容。这个指南详细说明了逐步的更改,只是为了突出发生了什么变化:

  1. 这是将应用程序锁定为纵向的第一步。打开AndroidManifest.xml文件。它位于项目资源资源管理器中的res文件夹直接下方。在代码中找到第一个开放的<activity

  2. 输入以下新行:

android:screenOrientation="portrait"
  1. 在第二个<activity实例之后重复步骤 2。我们现在已经将菜单和游戏屏幕都锁定在纵向模式下。

  2. 为了使游戏全屏,在同一文件中,找到以下文本,并在其后添加粗体行,但在关闭>符号之前:

<activity
android:name="com.packtpub.mathgamechapter4.app.MainActivity"
android:label="@string/app_name"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">

</activity>

  1. GameActivity活动进行相同的更改。同样,这里是上下文中的代码,以避免出现这些>符号的错误:
<activity
android:name="com.packtpub.mathgamechapter4.app.GameActivity"
android:label="@string/title_activity_game"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
</activity>

  1. 保存项目。

现在,在游戏过程中旋转设备时,纵向方向将被固定。

添加自定义图像(而不是 Android 图标)

我们可能不希望在我们完成的游戏主屏幕上有 Android 图像,所以这是更改它的过程。这个快速指南依赖于您是否有想要使用的图像:

  1. 首先,我们需要将所需的图像添加到布局文件夹中。通过在Windows 资源管理器中单击图像文件,然后使用Ctrl + C进行复制。

  2. 现在在 Android Studio 项目资源管理器中找到drawable-mdpi文件夹。点击该文件夹。

  3. 使用Ctrl + V将图像粘贴到文件夹中。

  4. 现在图像已经成为我们项目的一部分。我们只需要像之前选择 Android 机器人的图像一样选择它。在编辑窗口中打开activity_main.xml,然后点击ImageView(当前是一个 Android 机器人)。

  5. 属性窗口中,找到src属性。点击它,然后点击**...**。

  6. 搜索你的图像并选择它。

  7. 保存你的项目。

  8. 现在你在主屏幕上有了你选择的图像。

自测问题

Q1)猜猜这个方法有什么问题:

void doSomething(){
  return 4;
}

Q2)在这段代码结束时,x将等于多少?

int x=19;
do{
  x=11;
  x++;
}while(x<20)

总结

在这一章中,我们走了很长的路。你对 Java 循环有了深入的了解,并且第一次深入地了解了 Java 方法以及如何使用它们。你学会了如何生成随机数,并且利用你所学到的知识显著增强了你的数学游戏。

随着章节的进行,游戏会变得越来越像真正的游戏。在下一章中,我们将制作一个测试玩家记忆的游戏。它将有声音、动画,并且实际上还会保存玩家的最高分。

恭喜你迄今为止的进步,但让我们继续前进。