Kotlin 中的 Android 基础知识 - Kotlin
教程取自于Google官方课程之Android Basics in Kotlin
Android Basics in Kotlin
Welcome to Android Basics in Kotlin! In this course, you'll learn the basics of building Android apps with the Kotlin programming language. Along the way, you'll develop a collection of apps to start your journey as an Android developer.
Unit 1: Kotlin basics
1.3 构建基本布局
了解如何向 Android 应用中添加图片和文本。
1.3.2 创建 Birthday Card 应用
界面简介
应用的界面 (UI) 就是您在屏幕上所看到的内容,包括文本、图片、按钮和许多其他类型的元素。它既是应用向用户显示内容的方式,也是用户与应用展开互动的载体。
其中的每个元素都是所谓的 View。您在应用屏幕上看到的所有内容几乎均属于 View。Views 还可以是互动元素,例如可点击的按钮或可修改的输入字段。
Android 应用中的 Views 并非独自悬浮在屏幕上。各个 Views 之间彼此存在关联。例如,图片可能位于某些文本旁边,几个按钮也可能会自成一行。若要整理 Views,您可以将它们放到一个容器中。ViewGroup 就是一个可在其中放入 View 对象的容器,负责排列内部的各个 Views。排列方式(即布局)可能会因运行应用的 Android 设备的屏幕尺寸和宽高比而发生变化,并且可以根据设备是处于纵向还是横向模式做出调整。
ConstraintLayout 就是一种 ViewGroup,可帮助您灵活地排列其内部的 Views。
ViewGroup布局编辑器简介
打开布局编辑器后,您会看到许多窗口。在本 Codelab 中,您将学习如何使用其中的大部分窗口。请参考下方带有注解的屏幕截图,帮助您识别布局编辑器中的窗口。在更改应用时,您将会对每一个窗口作进一步了解。
- 左侧标注 (1) 处就是您之前已看到的 Project 窗口,其中列出了构成您项目的所有文件。
- 在中心区域,您能看到标注 (4) 和 (5) 的两个绘图窗口,代表应用的屏幕布局。左侧标注 (4) 的窗口呈现的是应用在运行时屏幕将呈现的近似效果,也就是所谓的 Design 视图。
- 右侧标注 (5) 的窗口呈现的是 Blueprint 视图。在执行特定操作时,该视图会非常有用。
- 标注 (2) 的 Palette 窗口包含的是您可以向应用添加的各类 Views 的列表。
- 标注 (3) 的 Component Tree 窗口则是屏幕视图的另一种呈现形式。它会列出屏幕的所有视图。
- 最右边标注 (6) 的窗口是 Attributes 视图,其中显示了 View 的各个属性。您可在此处更改这些属性。
放置 TextView
向顶部和左侧添加的约束条件将带有外边距。外边距用于指定 View 离它所在容器的边缘有多远。
-
在右侧的 Attributes 中,找到 Layout 部分中的 Constraint Widget。其中显示的方形代表您的视图。
Constraint Widget - 点击方形顶部的 +。这会在文本视图的顶部与约束布局的上边缘之间添加约束条件。
- 系统会显示一个带数字的字段,用于设置上外边距。该外边距是指从 TextView 到容器(即 ConstraintLayout)边缘的距离。显示的数字会因您放下 TextView 的位置而有所不同。在您设置上外边距时,Android Studio 还会在文本视图的顶部和 ConstrainLayout 的顶部之间自动添加约束条件。
1.3.3 向 Android 应用添加图片
为项目添加图片
- 在 Android Studio 中,在菜单中依次点击 View > Tool Windows > Resource Manager,或者点击 Project 窗口左侧的 Resource Manager 标签页。
-
点击 Resource Manager 下方的 +,然后选择 Import Drawables。系统随即会打开一个文件浏览器。
Resource Manager
3.在文件浏览器中,找到已下载的图片文件,然后点击 Open。点击 Next。Android Studio 会向您显示该图片的预览。点击 Import。
如果图片导入成功,Android Studio 就会将此图片添加到 Drawable 列表中。此列表包含该应用的所有图片和图标。
添加 ImageView 并设置其图片
- 在布局编辑器中,转到 Palette 并将 ImageView 拖动到应用上,将其放在靠近中心的位置,且不与任何文本重叠
此时,系统会打开 Pick a Resource 对话框。此对话框列出了可供您应用使用的所有图片资源。请注意生日图片列于 Drawable 标签页下。可绘制资源是图形的一般概念,是指可在屏幕上绘制的图形,其中包括图片、位图、图标以及许多其他类型的绘图资源。 - 在 Pick a Resource 对话框中,从 Drawable 列表里找到蛋糕图片。点击该图片,然后点击 OK。
放置 ImageView 并调整其大小
- 在布局编辑器中点击并拖动 ImageView。在拖动时,您会看到 Design 视图中应用屏幕的周围会显示粉色矩形边框。粉色矩形边框表示放置 ImageView 的屏幕边界。
- 拖动 ImageView,使其左右边缘与粉色矩形对齐。当您靠近时,Android Studio 便会让该图片“贴靠”边缘。ConstraintLayout 中的 Views 需要具有水平和垂直约束条件,以便告知 ConstraintLayout 如何放置这些视图。接下来,您将添加这些约束条件。
- 将指针悬停在 ImageView 轮廓顶部的圆圈上,这个圆圈便会与另一个圆圈一起突出显示。如果将圆圈拖向应用屏幕的顶部,那么在您拖动时,会有一个箭头将该圆圈连接到指针。请进行拖动,直至贴靠到屏幕顶部。这样,您就在 ImageView 的顶部与ConstraintLayout 的顶部之间添加了约束条件。
在 ImageView 的底部和 ConstraintLayout 的底部之间添加约束条件。有可能会因为距离边缘太近而无法像对顶部那样进行拖动。在这种情况下,您可以点击 Attributes 窗口中 Constraint Widget 底部的 +,添加约束条件。请务必将外边距设为0。现在,图片已经居中,但尚未占据整个屏幕。在接下来的步骤中,您将解决此问题。
- 在 Constraints 部分的 Constraint Widget 下,将 layout_width 设为 0dp (match constraint)。0dp 是一种简写形式,用于指示 Android Studio 对 ImageView 的宽度使用匹配约束条件。“匹配约束条件”意味着,由于您刚刚添加的约束条件,这会让它与 ConstraintLayout 一样宽,所有外边距都会被减去。将 layout_height 设为 0dp (match constraint)。由于您添加的约束条件,这会让 ImageView 与 ConstraintLayout 一样高,所有外边距都会被减去。
ImageView 与应用屏幕一样高、一样宽,但图片位于 ImageView 的中心,并且图片的上方和下方还有很多空白区域。这看起来不太好看,因此请调整 ImageView 的 scaleType,告知系统如何调整图片的大小和对齐方式。 - 找到 scaleType 属性。您可能需要向下滚动或搜索该属性。尝试为 scaleType 设置不同的值,看看不同值的效果。完成后,请将 scaleType 设为 centerCrop。这样一来,图片将填满整个屏幕,而不会变形。
将 ImageView 移到文本后面
在 Component Tree 中,点击 ImageView,将它拖动到 TextViews 的上面,ConstraintLayout 的正下方。系统会显示一个带三角形的蓝色线条,指示 ImageView 的目标位置。请将 ImageView 拖至 ConstraintLayout 的正下方。
1.4 向应用中添加按钮
了解类、对象和逻辑条件等更多 Kotlin 编程概念,并运用所学知识为用户构建一个互动式应用。
1.4.1 Kotlin 中的类和对象实例
游戏通常具有随机性。您可以赢得随机奖励,或在棋盘游戏中移动随机步数。在日常生活中,您可以使用随机数字和字母来生成更安全的密码!
您可以编写一个程序来模拟掷骰子,而无需真的掷骰子。每次掷骰子时,结果可以是可能值范围内的任何数字。幸运的是,您无需为此类程序自行构建随机数字生成器。大多数编程语言(包括 Kotlin)都内置有用于生成随机数字的方法。在此任务中,您将使用 Kotlin 代码来生成随机数字。
使用随机函数
- 在 main() 函数中,将一个变量定义为名为 diceRange 的 val。将其分配给 1 到 6 的 IntRange,用于表示 6 面骰子可以掷出的整数值范围。
val diceRange = 1..6
- 在 main() 中,将一个变量定义为名为 randomNumber 的 val。
为 randomNumber 设置对 diceRange 范围调用 random() 的结果值,如下所示。
val randomNumber = diceRange.random()
- 为了显示随机生成的数字,请使用字符串格式表示法(也称为“字符串模板”)${randomNumber} 进行输出,如下所示。
println("Random number: ${randomNumber}")
定义 Dice 类
在 main() 函数下,添加一个空行,然后添加代码以创建 Dice 类。如下所示,先输入关键字 class,后跟类名称,再跟左大括号和右大括号。在大括号内留出空间,以便放入类的代码。在 Dice 类中,添加一个名为 sides 的 var,用于表示骰子将具有的面数。将 sides 设置为 6。
class Dice {
var sides = 6
}
创建 Dice 类的实例
类与实例- 为了创建 Dice 的对象实例,请在 main() 函数中创建一个名为 myFirstDice 的 val,并将其初始化为 Dice 类的实例。请注意类名称后面的括号,其表示您在通过该类创建一个新的对象实例。
fun main() {
val myFirstDice = Dice()
}
- 在 myFirstDice 声明的下方,添加 println() 语句以输出 myFirstDice. 的 sides 数量。
println(myFirstDice.sides)
掷骰子
- 在 Dice 类的 sides 变量下方,插入一个空行,然后创建一个用于掷骰子的新函数。首先输入 Kotlin 关键字 fun,再输入方法的名称,然后是括号 (),然后再是左大括号和右大括号 {}。可以在大括号内留一个空行,以便输入更多代码,如下所示。您的类应如下所示。
class Dice {
var sides = 6
fun roll() {
}
}
- 在 roll() 方法中,创建一个 val randomNumber。在 1..6 范围内为其分配一个随机数字。使用点分表示法对该范围调用 random()。生成随机数字后,将其输出到控制台。完成后的 roll() 方法应如以下代码所示。
fun roll() {
val randomNumber = (1..6).random()
println(randomNumber)
}
- 为了真实地滚动 myFirstDice,请在 main() 中对 myFirstDice 调用 roll() 方法。您使用“点分表示法”调用方法。因此,为了对 myFirstDice 调用 roll() 方法,请输入 myFirstDice.roll(),这读作“myFirstDice dot roll()”。
myFirstDice.roll()
1.4.2 创建交互式 Dice Roller 应用
为应用创建布局
-
将 Button 从 Palette 拖动到 Design 视图中,并将其置于“Hello World”TextView 下方。在 Design 视图中,在 Button 的顶部边缘,按住带蓝色边框的白色圆圈。拖动指针,箭头将跟随指针移动。当指针到达“Hello World”TextView 的下边缘时释放。这样就建立了布局约束条件,Button 会滑动到 TextView 正下方。
确定 Button 的位置 -
请查看布局编辑器右侧的 Attributes。在 Constraint Widget 中,您会看到针对 TextView 的底部设置的一个新的布局约束条件,即, Top → BottomOf textView (0dp)。(0dp) 表示边距为 0。您还会遇到缺少水平约束条件的错误。
缺少水平约束条件 -
添加一个从 Button 的左侧到父级 ConstraintLayout 的左侧的水平约束条件。然后对右侧执行同样的操作,将 Button 的右边缘与 ConstraintLayout 的右边缘连接起来。结果应如下所示:
水平约束条件 -
在 Design Editor 中,选择 TextView,使其属性出现在 Attributes 窗口中。将 TextView 的 textSize 更改为 36sp,使其足够大,易于用户读取。您可能需要滚动才能找到 textSize。清除 TextView 的 text 属性。在用户掷骰子之前,您不需要在 TextView 中显示任何内容。
-
在 Component Tree 中选择 TextView。在 Common Attributes 下,找到 text 属性,在其下方会显示另一个带有工具图标的 text 属性。text 属性是应用运行时向用户显示的内容。带有工具图标的 text 属性是专为开发者提供的“tools text”属性。在 TextView 中将 tools text 设置为“1”(以假设您掷骰子的结果为 1)。“1”只会在 Android Studio 的 Design Editor 中显示,在实际设备或模拟器上运行应用时不会显示。
工具图标的 text 属性
Activity 简介
Activity 简介
Activity 提供窗口供应用在其中绘制界面。通常,Activity 会占用正在运行的应用的整个屏幕。每个应用都有一个或多个 Activity。顶级 Activity 或第一个 Activity 通常称为 MainActivity。例如,在“Photo Gallery”应用中,您可以使用一个 Activity 来显示照片网格,一个 Activity 来查看单张照片,以及另一个 Activity 来修改单张照片。
点击 Button 时显示消息
- 在调用 setContentView() 后,将以下代码添加到 onCreate() 方法中。findViewById() 方法在布局中找到 Button。R.id.button 是 Button 的资源 ID,该 ID 是 Button 的唯一标识符。代码将 Button 对象的引用保存到名为 rollButton 的变量中,而不是保存 Button 对象本身。
val rollButton: Button = findViewById(R.id.button)
onCreate() 方法现在应如下所示:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val rollButton: Button = findViewById(R.id.button)
}
- 使用
rollButton
对象,并调用setOnClickListener()
方法为其设置点击监听器。实际上,在方法名称后面您将使用大括号,而不在方法名称后面使用圆括号。这是用于声明 Lambda 的特殊语法,在未来的 Codelab 中可了解详情。
通过调用 Toast.makeText() 创建包含文字 "Dice Rolled!" 的 Toast。
然后,通过调用 show() 方法告知 Toast 自行显示。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val rollButton: Button = findViewById(R.id.button)
rollButton.setOnClickListener {
Toast.makeText(this, "Dice Rolled!", Toast.LENGTH_SHORT).show()
}
}
}
添加掷骰子逻辑
-
在 MainActivity 类中最后一个大括号后,使用 roll() 方法创建 Dice 类。将指针悬停在 numSides 上,系统会显示一个弹出式窗口,显示 Property ‘numSides' could be private。然后点击 Make ‘numSides' ‘private'。
系统会显示一个弹出式窗口 - 通过调用 rollDice() 替换用于将文字设置为“6”的点击监听器中的代码。选择 Create function ‘rollDice'。Android Studio 会为 MainActivity 内的函数创建空定义。
private fun rollDice() {
TODO("Not yet implemented")
}
- 在 rollDice() 中,删除 TODO() 调用。添加代码以创建包含 6 个面的骰子。通过调用 roll() 方法掷骰子,并将结果保存在名为 diceRoll 的变量中。通过调用 findViewById() 查找 TextView。将 diceRoll 转换为字符串,并使用该字符串更新 resultTextView 的文本。
private fun rollDice() {
val dice = Dice(6)
val diceRoll = dice.roll()
val resultTextView: TextView = findViewById(R.id.textView)
resultTextView.text = diceRoll.toString()
}
运行您的应用。除 6 外,骰子结果应更改为其他值!由于它是一个 1 到 6 的随机数,值 6 有时也会出现。
骰子结果
1.4.3 在 Kotlin 中添加条件行为
在代码内做出决策
在您的 Lucky Dice Roller 程序中,应用应处理不同的情况,例如:
- if 用户掷出幸运数字,就显示祝贺消息!
- else if 用户未掷出幸运数字,就显示重试消息。
查看是否已掷出幸运数字
先创建幸运数字,然后将掷出的数字与该数字进行比较。
- 在 main() 中,删除 println() 语句。在 main() 中,添加一个名为 luckyNumber 的 val,并将其设置为 4。您的代码应如下所示。
fun main() {
val myFirstDice = Dice(6)
val rollResult = myFirstDice.roll()
val luckyNumber = 4
}
- 在下面添加一个 if 语句,在括号 () 中放置条件,用于检查 rollResult 是否等于 (==) luckyNumber。在大括号 {} 内留一些空间,以便添加更多代码。在大括号 {} 内,添加一个 println 语句以输出 "You win!"
fun main() {
val myFirstDice = Dice(6)
val rollResult = myFirstDice.roll()
val luckyNumber = 4
if (rollResult == luckyNumber) {
println("You win!")
}
}
在尚未掷出幸运数字时做出响应
添加 else if 语句以针对每次掷骰输出不同的消息。如果需要,请参考您在上一任务中学到的格式。
fun main() {
val myFirstDice = Dice(6)
val rollResult = myFirstDice.roll()
val luckyNumber = 4
if (rollResult == luckyNumber) {
println("You win!")
} else if (rollResult == 1) {
println("So sorry! You rolled a 1. Try again!")
} else if (rollResult == 2) {
println("Sadly, you rolled a 2. Try again!")
} else if (rollResult == 3) {
println("Unfortunately, you rolled a 3. Try again!")
} else if (rollResult == 5) {
println("Don't cry! You rolled a 5. Try again!")
} else {
println("Apologies! You rolled a 6. Try again!")
}
}
使用 when 语句
- 在您的程序内,在 main() 中,选择从第一个 if 语句到括住最后一个 else 语句的大括号 } 之间的代码并将其删除。在 main() 中的 luckyNumber 声明下方,创建一个 when 语句。由于 when 需要针对掷出的结果进行测试,因此请将 rollResult 放置在括号 () 内。
fun main() {
val myFirstDice = Dice(6)
val rollResult = myFirstDice.roll()
val luckyNumber = 4
when (rollResult) {
}
}
- 在 when 语句的大括号 {} 内,添加一个针对 luckyNumber 测试 rollResult 的语句,如果二者相同,就输出获胜消息。使用相同格式为可能的掷骰结果 1 至 6(4 除外)添加代码行和消息,如下所示。完成后的 main() 函数应如下所示。
fun main() {
val myFirstDice = Dice(6)
val rollResult = myFirstDice.roll()
val luckyNumber = 4
when (rollResult) {
luckyNumber -> println("You won!")
1 -> println("So sorry! You rolled a 1. Try again!")
2 -> println("Sadly, you rolled a 2. Try again!")
3 -> println("Unfortunately, you rolled a 3. Try again!")
5 -> println("Don't cry! You rolled a 5. Try again!")
6 -> println("Apologies! you rolled a 6. Try again!")
}
}
1.4.4 向 Dice Roller 应用添加图片
下载骰子图片
- 打开此网址,将包含骰子图片的 ZIP 文件下载到您的计算机。等待下载完成。在计算机上找到该文件(可能在 Downloads 文件夹中)。双击 ZIP 文件进行解压缩。这将新建一个
dice_images
文件夹,其中包含 6 个骰子图片文件,分别显示从 1 到 6 的骰子值。
骰子图片 -
在 Android Studio 中,依次点击菜单中的 View > Tool Windows > Resource Manager,或者点击 Project 窗口左侧的 Resource Manager 标签页。点击 Resource Manager 中左上角的 +,然后选择 Import Drawables。系统随即会打开一个文件浏览器。
导入图片 - 查找并选中上述 6 个骰子图片文件。您可以先选中第一个骰子图片,然后在按住 Shift 键的同时选中其他图片。点击 Open。点击 Next,然后点击 Import,以确认您要导入这 6 项资源。如果文件已成功导入,那么应用的 Resource Manager (app>res>drawable) 中会显示这 6 张图片。
使用骰子图片
-
在 Design Editor 中,选择 ImageView。在 Declared Attributes 部分的 Attributes 中,找到设置为头像图片的工具 srcCompat 属性。点击头像的小预览图。系统会打开一个对话框,供您选择要用于此 ImageView 的新资源。选择 dice_1 可绘制对象,然后点击 OK。
替换图片 -
在 Constraints Widget 下的 Attributes 窗口中,找到 layout_width 和 layout_height 属性。它们当前设置为 wrap_content,这意味着 ImageView 的高度和宽度将与其中的内容(源图片)保持一致。为 ImageView 设置 160dp 的固定宽度和 200dp 的固定高度。按 Enter 键。在 Constraint Widget 中将按钮的上边距设置为 16dp。
调整图片
点击按钮时改变骰子图片
- 在 rollDice() 中,创建一个新变量 diceImage(类型为 ImageView)。将其设置为与布局中的 ImageView 相同。使用 findViewById() 方法,并将 ImageView 的资源 ID R.id.imageView 作为输入参数传入。
如果您想知道如何确定 ImageView 的确切资源 ID,请查看 Attributes 窗口顶部的 id。
资源 ID
- 添加以下代码行,以测试点击按钮时是否可以正确更新 ImageView。掷出的骰子并不总是“2”,我们只是使用 dice_2 图片进行测试。rollDice() 方法现在应如下所示:
private fun rollDice() {
val dice = Dice(6)
val diceRoll = dice.roll()
val diceImage: ImageView = findViewById(R.id.imageView)
diceImage.setImageResource(R.drawable.dice_2)
}
-
运行应用以验证它是否正常运行而不出现错误。启动应用时应显示空白屏幕(只有 Roll 按钮)。点按按钮后,会出现显示值 2 的骰子图片。太好了!!
显示骰子图片
根据掷骰子结果显示正确的骰子图片
- 在 rollDice() 方法中,删除每次将图片资源 ID 设置为 dice_2 图片的代码行。将其替换为 when 语句,该语句可根据 ImageView 值更新 diceRoll。完成更改后,rollDice() 方法应如下所示。
private fun rollDice() {
val dice = Dice(6)
val diceRoll = dice.roll()
val diceImage: ImageView = findViewById(R.id.imageView)
when (diceRoll) {
1 -> diceImage.setImageResource(R.drawable.dice_1)
2 -> diceImage.setImageResource(R.drawable.dice_2)
3 -> diceImage.setImageResource(R.drawable.dice_3)
4 -> diceImage.setImageResource(R.drawable.dice_4)
5 -> diceImage.setImageResource(R.drawable.dice_5)
6 -> diceImage.setImageResource(R.drawable.dice_6)
}
}
- 运行应用。点击 Roll 按钮会将骰子图片更改为除 2 以外的值。成功啦!
优化您的代码
您可能已经注意到,在 when 语句中对 diceImage.setImageResource() 调用了 6 次。每种情况之间唯一改变的是所使用的资源 ID。这意味着您可以创建一个变量,用于存储要使用的资源 ID。然后,在代码中只调用 diceImage.setImageResource() 一次并传递正确的资源 ID 即可。
- 将上述代码替换为以下代码。
val drawableResource = when (diceRoll) {
1 -> R.drawable.dice_1
2 -> R.drawable.dice_2
3 -> R.drawable.dice_3
4 -> R.drawable.dice_4
5 -> R.drawable.dice_5
6 -> R.drawable.dice_6
}
diceImage.setImageResource(drawableResource)
这里运用的新概念是,when 表达式实际上可以返回一个值。使用此新代码段后,when 表达式会返回正确的资源 ID,该 ID 将存储在 drawableResource 变量中。然后,您可以使用该变量更新显示的图片资源。
- 请注意,when 现在带红色的下划线。如果将指针悬停在它的上面,您会看到一条错误消息:‘when' expression must be exhaustive, add necessary ‘else' branch。出现该错误的原因是 when 表达式的值被赋给 drawableResource,因此 when 必须涵盖所有情况,它必须处理所有可能的情况,这样,即使您更改为 12 面的骰子,也始终会返回值。Android Studio 建议添加一个 else 分支。您可以通过将 6 的情况更改为 else 来解决此问题。从 1 到 5 的情况不变,但包括 6 在内的所有其他情况都通过 else 处理。
val drawableResource = when (diceRoll) {
1 -> R.drawable.dice_1
2 -> R.drawable.dice_2
3 -> R.drawable.dice_3
4 -> R.drawable.dice_4
5 -> R.drawable.dice_5
else -> R.drawable.dice_6
}
diceImage.setImageResource(drawableResource)