题图 | Technology vector created by stories - www.freepik.com
以下内容节选自经典权威 Android 入门与进阶图书
《Android编程权威指南(第4版)》
顺便,广而告知下,本书电子版已经上架,一直催更电子版的同学,Finally,终于上架了!文末通过「阅读原文」去图灵社区购买。
「进入干货,长,准备好」
下面将带你开发本书第一个应用,并借此学习一些Android基本概念以及构成应用的用户界面(UI)部件。学完本章,如果没能全部理解,也不必担心,后续章节还会涉及这些内容并有更加详细的讲解。
马上要开发的应用名叫GeoQuiz,它能提出一道道地理知识问题。用户点击TRUE或FALSE按钮来回答屏幕上的问题,GeoQuiz会即时做出反馈。
图1-1显示了用户点击TRUE按钮的结果。
图1-1 你是澳洲人吗
1.1 Android开发基础
GeoQuiz应用由一个activity和一个布局(layout)组成。
activity是Android SDK中
Activity
类的一个实例,负责管理用户与应用界面的交互。应用的功能通过编写
Activity
子类来实现。对于简单的应用来说,一个Activity
子类可能就够了,而复杂的应用会有多个Activity
子类。GeoQuiz是个简单应用,它只有一个名叫
MainActivity
的Activity
子类。MainActivity
管理着图1-1所示的用户界面。
布局定义了一系列UI对象以及它们显示在屏幕上的位置。组成布局的定义保存在XML文件中。每个定义用来创建屏幕上的一个对象,比如按钮或文本信息。
GeoQuiz应用包含一个名叫activity_main.xml的布局文件。该布局文件中的XML标签定义了图1-1所示的用户界面。
MainActivity
与activity_main.xml文件的关系如图1-2所示。
图1-2 MainActivity
管理着activity_main.xml定义的用户界面
有了这些Android基本概念之后,我们来创建GeoQuiz应用。
1.2 创建Android项目
首先我们创建一个Android项目。Android项目包含组成一个应用的全部文件。
启动Android Studio程序。如果是首次运行,会看到如图1-3所示的欢迎界面。
图1-3 欢迎使用Android Studio
创建新项目之前,请先关闭Android Studio的Instant Run功能。这项功能的设计初衷是提高开发效率。代码修改后,无须生成新APK,开发人员就能立即看到变化。不过,很可惜,它的实际表现不及预期,因此建议一开始就彻底禁用这一功能。
在欢迎界面的底部,点击Configure,再选择Settings,会弹出如图1-4所示的新项目首选项界面。展开左边的Build, Execution, Deployment选项并选中Instant Run,取消勾选Enable Instant Run to hot swap code/resource changes on deploy (default enabled),然后点击OK按钮。
图1-4 新项目首选项
(如果之前用过Android Studio工具,看不到欢迎界面的话,可以通过选择Android Studio →Preferences菜单项,然后扩展Build, Execution, Deployment选项并继续上面的操作。)
回到欢迎界面,选择创建新项目选项(Start a new Android Studio project);如果并非首次运行Android Studio,请选择File → New → New Project…菜单项。
现在,你应该打开了新建项目向导界面,如图1-5所示。确认选中Phone and Tablet选项页和Empty Activity,然后点击Next按钮继续。
图1-5 选择项目模板
配置项目窗口弹出了。在此界面的应用名称(Name)处输入GeoQuiz。在包名(Package name)处输入com.bignerdranch.android.geoquiz。至于项目存储位置(Save location),就看个人喜好了。接下来开发语言选Kotlin,SDK最低版本选API 21: Android 5.0 (Lollipop)。第7章会介绍Android不同SDK版本的差异。最后,勾选Use AndroidX artifacts,完成后的界面如图1-6所示。
图1-6 配置新项目
注意,以上包名遵循了“DNS反转”约定,也就是将组织或公司的域名反转后,在尾部附加上应用名称。遵循此约定可以保证包名的唯一性,这样,同一设备和Google Play商店的各类应用就可以区分开来。
本书撰写时,Android Studio新建项目默认使用Java语言。选Kotlin是让Android Studio准备好该语言相关的各种工具和依赖,以便编写和构建Kotlin应用。
一直以来,Java是Android开发唯一的官方支持语言,直到2017年5月,Android开发团队在Google I/O大会上宣布Kotlin为Android开发又一官方支持语言。如今,包括我们在内,Kotlin已成为大多数开发人员的首选语言。如果你的项目依然选用Java也没关系,本书所教概念和内容同样适用。
过去,Google一直维护着庞大的支持库,用来协助开发和解决兼容性问题。作为改进,AndroidX将这个巨型库拆分为一个个独立的开发和版本库,统称为Jetpack。勾选Use AndroidX artifacts就是让新项目能用上这些独立工具库。第4章将详细介绍AndroidX和Jetpack,本书中会用到各种各样的Jetpack库。
(Android Studio更新频繁,因此新版本的向导界面可能与本书略有不同。这不是什么大问题,一般来讲,工具更新后,向导界面的配置选项应该不会有太大差别。如果大有不同,说明开发工具有了重大更新。不要担心,请访问本书论坛,我们会教你如何使用新版本的开发工具。)
点击Finish按钮,Android Studio会完成创建并打开新项目。
1.3 Android Studio使用导航
如图1-7所示,Android Studio已在工作区窗口里打开新建项目。如果并非首次运行Android Studio,你看到的窗口配置可能稍有不同。
图1-7 新的项目窗口
整个工作区窗口分为不同的区域,这里统称为工具窗口(tool window)。
左边是项目工具窗口(project tool window),通过它可以查看和管理所有与项目相关的文件。
工作区底部是构建工具窗口(build tool window),可以在这里看到项目的编译过程和构建状态。新建项目时,Android Studio会自动进行项目构建。可以看到,构建工具窗口显示构建已成功完成。
在项目工具窗口中,点击app旁边的展开箭头,Android Studio会自动打开activity_main.xml和MainActivity.kt文件。如图1-8所示,打开文件所在的区域叫编辑工具窗口(editor tool window),或直接叫代码编辑区(editor)。依然要提醒的是,如果并非首次运行Android Studio,代码编辑区会自动打开所建项目文件。
图1-8 编辑工具窗口
注意Activity
类名的前缀,此前缀不加也可以,但这是个很好的命名约定,建议遵循。
点击工作区窗口左边、右边以及底部标有各种名称的工具按钮区域,可显示或隐藏各类工具窗口。当然,也可以直接使用它们对应的快捷键。如果看不到某个工具按钮,可以点击左下角的灰色方形区域或点击View → Tool Buttons菜单项找到它。
1.4 用户界面设计
点击activity_main.xml布局文件页,会在编辑工具窗口打开布局编辑器,如图1-9所示。如果看不到布局文件,请在项目工具窗口展开app/res/layout/找到它并双击打开。如果看到的是activity_main.xml文件的XML代码,请点击底部的Design页,切换显示布局预览。
图1-9 布局编辑器
按照约定,布局文件的命名基于其关联的activity:activity_作为前缀,activity子类名的其余部分全部转小写并紧随其后,单词之间以下划线隔开。例如,当前新建项目的布局文件名为activity_main.xml,或者说你有个activity名为SplashScreenActivity
,那么对应的布局就命名为activity_splash_screen。对于后续章节中的所有布局以及将要学习的其他资源,都建议采用这种命名风格。
布局编辑器展示的是文件的图形化预览界面,你可以点击底部的Text页切换显示布局的XML代码。
当前,activity_main.xml文件定义了默认的activity布局。默认的XML布局文件内容经常有变,但相比代码清单1-1,一般不会有很大出入。
代码清单1-1 默认的activity布局(res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/></androidx.constraintlayout.widget.ConstraintLayout>
应用activity的默认布局定义了两个视图(view):ConstraintLayout
和TextView
。
视图是用户界面的构造模块。显示在屏幕上的一切都是视图。用户能看到并与之交互的视图称为部件(widget)。有些部件可以用来显示文字或图像,有些部件(比如按钮)可以点击以触发事件任务。
Android SDK内置了多种部件,通过配置各种部件可获得应用所需的外观及行为。每一个部件都是View
类或其子类(比如TextView
或Button
)的一个具体实例。
我们得想办法告诉部件它们在屏幕上该位于哪里。ViewGroup
就是这样一种特殊的View
,它包含并布置其他视图。ViewGroup
视图本身不显示内容,它规划其他视图内容应该显示在哪里。ViewGroup
通常又称为布局。
在当前默认布局里,ConstraintLayout
这个ViewGroup
布置了一个TextView
部件,这是它唯一的子部件。有关布局和部件的知识,以及如何使用ConstraintLayout
,第10章将详述。
图1-10展示了代码清单1-1中定义的ConstraintLayout
和TextView
是如何在屏幕上显示的。
图1-10 显示在屏幕上的默认视图
不过,图1-10所示的默认部件并不是我们需要的,MainActivity
的用户界面需要以下五个部件:
一个垂直
LinearLayout
部件;一个
TextView
部件;一个水平
LinearLayout
部件;两个
Button
部件。
图1-11展示了以上部件是如何构成MainActivity
用户界面的。
图1-11 布置并显示在屏幕上的部件
下面我们在布局XML文件中定义这些部件。对照代码清单1-2,修改activity_main.xml文件内容。注意,需删除的XML代码已打上删除线,需添加的XML以粗体显示。本书统一使用这样的代码增删处理模式。
代码清单1-2 在XML文件中定义部件(res/layout/activity_main.xml)
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/></androidx.constraintlayout.widget.ConstraintLayout><LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="24dp"
android:text="@string/question_text"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/true_button"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/false_button"/>
</LinearLayout></LinearLayout>
参照代码清单输入代码,暂时不理解这些代码也没关系,你会在后续学习中逐渐弄明白的。注意,开发工具无法校验布局XML内容,拼写错误早晚会出问题,应尽量避免。
可以看到,有三行以android:text
开头的代码出现了错误信息。暂时忽略它们,稍后会处理。
对照图1-11所示的用户界面查看XML文件,可以看出部件与XML元素一一对应。元素名称就是部件的类型。
各元素均有一组XML属性。属性可以看作关于如何配置部件的指令。
为方便理解元素与属性的工作原理,接下来我们将以层级视角来研究布局。
1.4.1 视图层级结构
部件包含在视图对象的层级结构中,这种结构又称作视图层级结构(view hierarchy)。图1-12展示了代码清单1-2所示的XML布局对应的视图层级结构。
图1-12 布局部件的层级结构
从布局的视图层级结构可以看到,其根元素是一个LinearLayout
部件。作为根元素,LinearLayout
部件必须指定Android XML资源文件的命名空间属性。
LinearLayout
部件继承自ViewGroup
部件(也是一个View
子类)。ViewGroup
部件是包含并布置其他视图的特殊视图。想要以一列或一排的样式布置部件,就可以使用LinearLayout
部件。其他ViewGroup
子类还有ConstraintLayout
和FrameLayout
。
如果某个视图包含在一个ViewGroup
中,该视图与ViewGroup
即构成父子关系。根LinearLayout
有两个子部件:TextView
和另一个LinearLayout
。作为子部件的LinearLayout
自己还有两个Button
子部件。
1.4.2 部件属性
下面来看看配置部件时常用的一些属性。
android:layout_width
和android:layout_height
属性几乎每类部件都需要
android:layout_width
和android:layout_height
属性。以下是它们的两个常见属性值(二选一)。根
LinearLayout
部件的高度与宽度属性值均为match_parent
。LinearLayout
虽然是根元素,但它也有父视图——Android提供该父视图来容纳应用的整个视图层级结构。其他包含在界面布局中的部件,其高度与宽度属性值均被设置为
wrap_content
。请参照图1-11理解该属性值定义尺寸大小的作用。TextView
部件比其包含的文字内容区域稍大一些,这主要是android:padding="24dp"
(dp即density-independent pixel,指与密度无关的像素,详见第10章)属性的作用。该属性告诉部件在决定大小时,除内容本身外,还需增加额外指定量的空间。这样屏幕上显示的问题与按钮之间便会留有一定的空间,使整体显得更为美观。
match_parent
:视图与其父视图大小相同。wrap_content
:视图将根据其显示内容自动调整大小。
android:orientation
属性android:orientation
属性是两个LinearLayout
部件都具有的属性,它决定两者的子部件是水平放置还是垂直放置。根LinearLayout
是垂直的,子LinearLayout
是水平的。子部件的定义顺序决定其在屏幕上显示的顺序。在垂直的
LinearLayout
中,第一个定义的子部件出现在屏幕的最上端;而在水平的LinearLayout
中,第一个定义的子部件出现在屏幕的最左端。(如果设备文字从右至左显示,比如阿拉伯语或者希伯来语,则第一个定义的子部件出现在屏幕的最右端。)
android:text
属性TextView
与Button
部件具有android:text
属性。该属性指定部件要显示的文字内容。请注意,
android:text
属性值不是字符串值,而是以@string/
语法形式对字符串资源(string resource)的引用。字符串资源包含在一个独立的名叫strings的XML文件中(strings.xml),虽然可以硬编码设置部件的文本属性值,比如
android:text="True"
,但这通常不是个好办法。比较好的做法是将文字内容放置在独立的字符串资源XML文件中,然后引用它们。这样会方便应用的本地化(详见第17章)。需要在activity_main.xml文件中引用的字符串资源还没添加,现在就来处理。
1.4.3 创建字符串资源
每个项目都包含一个默认字符串资源文件res/values/strings.xml。
打开res/values/strings.xml文件,可以看到,项目模板已经添加了一个字符串资源。如代码清单1-3所示,添加应用布局需要的三个新字符串。
代码清单1-3 添加字符串资源(res/values/strings.xml)
<resources> <stringname="app_name">GeoQuiz</string> <stringname="question_text">Canberra is the capital of Australia.</string> <stringname="true_button">True</string> <stringname="false_button">False</string></resources>
(Android Studio某些版本的strings.xml默认带有其他字符串,这些字符串可能与其他文件有关联,请勿随意删除。)
现在,在GeoQuiz项目的任何XML文件中,只要引用到
@string/false_button
,应用运行时,就会得到“False”文本。保存strings.xml文件。这时,activity_main.xml布局缺少字符串资源的提示信息应该消失了。(如仍有错误提示,请检查一下这两个文件,确认没有拼写错误。)
默认的字符串文件虽然已命名为strings.xml,但你仍可以按个人喜好重新命名。一个项目也可以有多个字符串文件。只要这些文件都放在res/values/目录下,含有一个
resources
根元素,以及多个string
子元素,应用就能找到并正确使用它们。1.4.4 预览布局
至此,应用的界面布局已经完成,可以使用图形布局工具实时预览了。回到activity_main.xml文件,在编辑器工具窗口的底部点击Design页进行布局预览,结果如图1-13所示。
图1-13 在Design页预览activity_main.xml布局
图1-13展示了两种布局预览模式。在工具栏左上角,有个钻石按钮,我们可以通过它的下拉菜单切换显示不同的布局预览模式——设计(Design)预览或蓝图(Blueprint)预览,或者并排显示设计预览和蓝图预览。
在图1-13中,左边是设计预览模式,用来展示布局在设备上的效果,也包括主题样式;右边是蓝图预览模式,用来展示部件的尺寸以及它们之间的位置关系。
在设计预览模式下,你还可以查看布局在不同的设备配置下的样子。通过预览窗口上方的面板,可以指定设备类型、Android模拟器版本、设备主题以及设备使用区域,查看布局的不同渲染结果。你甚至可以模拟某个语言区域的自右到左的文字显示模式。
除了预览,你也可以直接使用布局编辑器摆放部件,布置布局。如图1-14所示,项目窗口左边有个面板,包括了Android所有的内置部件。你可以将它们从面板拖曳到视图上,或者拖到左下方的部件树上,更精准地控制如何摆放部件。
图1-14 图形化布局编辑器
图1-14展示了带布局装饰(layout decoration)的布局预览。这些装饰元素有设备状态栏、带GeoQuiz标签的应用栏,以及虚拟设备按钮栏。要添加这些装饰,点击预览窗口上方工具栏中的眼睛图标,选择Show Layout Decorations菜单项即可。
图形化布局编辑器非常有用,尤其是在使用
ConstraintLayout
时,后面学习第10章内容时,你将有所体会。1.5 从布局XML到视图对象
知道activity_main.xml中的XML元素是如何转换为视图对象的吗?答案就在于
MainActivity
类。在创建GeoQuiz项目的同时,向导也创建了一个名为
MainActivity
的Activity
子类。MainActivity
类文件存放在项目的app/java目录下。继续学习之前,就app/java这个目录名问题,简单说两句:这里依然使用java作为目录名是因为Android之前仅支持Java语言。新建项目时,我们虽然选了Kotlin语言(不过Kotlin可以和Java完全互操作),但Kotlin源码默认还是放在java目录里。当然,你完全可以新建一个Kotlin目录,把Kotlin代码文件都移过去。但前提是,你要明确告诉Android Studio:源码放在新文件夹里了,请帮它们添加到项目里。大多数情况下,按语言区分管理源码文件意义不大,所以绝大多数项目接受Kotlin文件存放在java目录里。
MainActivity.kt文件应该已经在编辑器窗口打开了,如果没有,在项目工具窗口中,依次展开app/java目录与com.bignerdranch.android.geoquiz包。(注意,以灰绿色显示包名的是测试包。生产包名并未加灰。)找到并打开MainActivity.kt文件,查看其中的代码,如代码清单1-4所示。
代码清单1-4 默认
MainActivity
类文件(MainActivity.kt)package com.bignerdranch.android.geoquiz import androidx.appcompat.app.AppCompatActivityimport android.os.BundleclassMainActivity:AppCompatActivity(){ override fun onCreate(savedInstanceState:Bundle?){ super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) }}
(是不是不明白
AppCompatActivity
的作用?它实际就是一个Activity
子类,能为Android旧版本系统提供兼容支持。第14章会详细介绍AppCompatActivity
。)如果无法看到全部类包导入语句,请点击第一行导入语句左边的
+
号来显示它们。该类文件有一个
Activity
函数:onCreate(Bundle?)
。activity子类的实例创建后,
onCreate(Bundle?)
函数会被调用。activity创建后,它需要获取并管理用户界面。要获取activity的用户界面,可以调用以下Activity
函数:Activity.setContentView(layoutResID:Int)
根据传入的布局资源ID参数,该函数生成指定布局的视图并将其放置在屏幕上。布局视图生成后,布局文件包含的部件也随之以各自的属性定义完成实例化。
资源与资源ID
布局是一种资源。资源是应用非代码形式的内容,比如图像文件、音频文件以及XML文件等。
项目的所有资源文件都存放在目录app/res的子目录下。在项目工具窗口中可以看到,activity_main.xml布局资源文件存放在res/layout/目录下。strings.xml字符串资源文件存放在res/values/目录下。
可以使用资源ID在代码中获取相应的资源。activity_main.xml布局的资源ID为R.layout.activity_main。
查看GeoQuiz应用的资源ID需要切换项目视角,你必须勇闯自动生成代码的世界——Android构建工具为你编写的代码。首先,点击Android Studio窗口顶部工具栏上的锤子按钮运行编译工具。
如图1-15所示,Android Studio默认使用Android项目视角。为让开发者专注于最常用的文件和目录,默认项目视角隐藏了Android项目的真实文件目录结构。在项目工具窗口的最上部找到下拉菜单,从Android视角切换至Project视角。Project视角会显示出当前项目的所有文件和目录。
图1-15 项目工具窗口:Android视角与Project视角
在Project视角下,逐级展开GeoQuiz目录,直至看到GeoQuiz/app/build/generated/not_namespaced_r_class_sources/debug/processDebugResources/r/,再找到项目包名以及其中的R.java文件,如图1-16所示。
图1-16 查看R.java文件
双击打开R.java文件。它是在Android项目编译过程中自动生成的,所以如该文件头部的警示所述,请不要修改该文件的内容,如代码清单1-5所示。
代码清单1-5 GeoQuiz应用当前的资源ID(R.java)
/* AUTO-GENERATED FILE. DO NOT MODIFY. * * This class was automatically generated by the * aapt tool from the resource data it found. It * should not be modified by hand. */package com.bignerdranch.android.geoquiz;publicfinalclass R { publicstaticfinalclass anim { ... } ... publicstaticfinalclass id { ... } publicstaticfinalclass layout { ... publicstaticfinalInt activity_main=0x7f030017; } publicstaticfinalclass mipmap { publicstaticfinalInt ic_launcher=0x7f030000; } publicstaticfinalclassstring{ ... publicstaticfinalInt app_name=0x7f0a0010; publicstaticfinalInt false_button=0x7f0a0012; publicstaticfinalInt question_text=0x7f0a0014; publicstaticfinalInt true_button=0x7f0a0015; }}
顺便要说的是,修改布局或字符串等资源后,R.java文件不会实时更新。Android Studio另外还存有一份代码编译用的R.java隐藏文件。代码清单1-5中打开的R.java文件仅在应用安装至设备或模拟器前生成,因此只有在Android Studio中点击运行应用时,它才会得到更新。
R.java文件通常比较大,代码清单1-5仅展示了部分内容。
可以看到R.layout.activity_main即来自该文件。
activity_main
是R的内部类layout
里的一个整型常量名。GeoQuiz应用需要的字符串同样具有资源ID。目前为止,我们还未在代码中引用过字符串,如果需要,可以使用以下函数:
setTitle(R.string.app_name)
Android为整个布局文件以及各个字符串生成资源ID,但activity_main.xml布局文件中的部件除外,因为不是所有部件都需要资源ID。在本章中,我们要在代码里与两个按钮交互,因此只需为它们生成资源ID即可。
要为部件生成资源ID,请在定义部件时为其添加
android:id
属性。如代码清单1-6所示,在activity_main.xml文件中,分别为两个按钮添加android:id
属性(需要从布局预览模式切换至XML代码模式)。代码清单1-6 为按钮添加资源ID(res/layout/activity_main.xml)
<LinearLayout ... > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="24dp" android:text="@string/question_text"/> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal"> <Button android:id="@+id/true_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/true_button"/> <Button android:id="@+id/false_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/false_button"/> </LinearLayout></LinearLayout>
注意,
android:id
属性值前面有一个+
标志,android:text
属性值则没有。这是因为我们在创建资源ID,而对字符串资源只是做引用。继续学习之前,关闭R.java文件,从Project视角切回至Android视角。本书主要使用Android视角,当然,如果你就喜欢使用Project视角,也没有问题。
1.6 部件的实际应用
接下来,我们来编码使用按钮部件,这需要以下两个步骤:
引用生成的视图对象;
为对象设置监听器,以响应用户操作。
1.6.1 引用部件
既然按钮有了资源ID,我们就可以在MainActivity中引用它们了。在MainActivity.kt文件中输入代码清单1-7所示的代码(不要使用代码自动补全功能,直接手动输入)。保存文件时,会看到代码错误提示,不用理会,稍后会修复。
代码清单1-7 通过资源ID访问视图对象(MainActivity.kt)
classMainActivity:AppCompatActivity(){ private lateinit var trueButton:Button private lateinit var falseButton:Button override fun onCreate(savedInstanceState:Bundle?){ super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) trueButton = findViewById(R.id.true_button) falseButton = findViewById(R.id.false_button) }}
在activity中,可以调用
Activity.findViewById(Int)
函数引用已生成的部件。该函数以部件的资源ID作为参数,返回一个视图对象。不过,这里直接返回的不是View
视图,而是其已做类型转换后的Button
子类。在上述代码中,我们使用按钮的资源ID获取视图对象,赋值给对应的视图属性。既然只有在
onCreate(...)
函数里调用setContentView(...)
函数后,视图对象才会实例化到内存里,那么在属性声明时,我们就得使用lateinit
修饰符。这实际是告诉编译器,在使用属性内容时,我们会保证提供非空的View
值。然后,在onCreate(...)
中,找到视图对象并赋值给对应的视图属性。第3章还会深入学习onCreate(...)
函数和activity生命周期的知识。现在让我们来修正前面的代码错误。将鼠标移动到红色的错误指示处,可以看到两个相同的错误提示:Unresolved reference: Button。
这实际是告诉你,要在MainActivity.kt文件中导入
android.widget.Button
类。你可以在Kotlin文件的头部手动输入import android.widget.Button
,也可以使用Option+Return(或Alt+Enter)快捷键,让Android Studio自动为你导入。可以看到,文件顶部有了新的类导入语句。当代码遇到类引用相关问题时,这种快速导入方法往往很有用,建议经常采用。现在,代码错误提示应该消失了(如果仍然有错误,记得检查代码或XML文件,确认无输入错误)。代码错误解决了,接下来是时候让应用支持交互了。
1.6.2 设置监听器
Android应用属于典型的事件驱动类型。不像命令行或脚本程序,事件驱动型应用启动后,即开始等待行为事件的发生,比如用户点击某个按钮。(事件也可以由操作系统或其他应用触发,但用户触发的事件更直观,比如点击按钮。)
应用等待某个特定事件的发生,也可以说应用正在“监听”特定事件。为响应某个事件而创建的对象叫作监听器(listener)。监听器会实现特定事件的监听器接口(listener interface)。
无须自己动手,Android SDK已经为各种事件内置了很多监听器接口。当前应用需要监听用户的按钮“点击”事件,因此监听器需实现
View.OnClickListener
接口。首先处理TRUE按钮。在MainActivity.kt文件中,在
onCreate(Bundle?)
函数的变量赋值语句后输入代码清单1-8所示的代码。代码清单1-8 为TRUE按钮设置监听器(MainActivity.kt)
override fun onCreate(savedInstanceState:Bundle?){ super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) trueButton = findViewById(R.id.true_button) falseButton = findViewById(R.id.false_button) trueButton.setOnClickListener { view:View-> // Do something in response to the click here }}
(如果遇到Unresolved reference: View错误提示,请使用Option+Return(Alt+Enter)快捷键导入
View
类。)在代码清单1-8中,我们设置了一个监听器。按钮
trueButton
被点击后,监听器会立即通知我们。Android框架定义了View.OnClickListener
这样只有一个onClick(View)
单方法的Java接口。在Java世界里,这种带有单一抽象方法(single abstract method)的接口设计模式很常见,它有个专门的名字叫SAM。作为和Java互操作实现的一部分,Kotlin对此模式设计有特别支持。你只需编写一个函数字面量(function literal),让Kotlin负责将其转换为实现这种SAM接口的对象。这种内部转换又叫作SAM转换(SAM conversion)。
这里,点击监听器是使用lambda表达式实现的。参照代码清单1-9为FALSE按钮设置类似的事件监听器。
代码清单1-9 为FALSE按钮设置监听器(MainActivity.kt)
override fun onCreate(savedInstanceState:Bundle?){ ... trueButton.setOnClickListener { view:View-> // Do something in response to the click here } falseButton.setOnClickListener { view:View-> // Do something in response to the click here }}
1.7 创建提示消息
接下来要实现的是,分别点击两个按钮,弹出我们称之为toast的提示消息。Android的toast是用来通知用户的简短弹出消息,用户无须输入什么,也不用做任何干预操作。这里,我们要用toast来反馈答案,如图1-17所示。
图1-17 toast消息反馈
首先回到strings.xml文件,如代码清单1-10所示,为toast添加消息显示用的字符串资源。
代码清单1-10 增加toast字符串(res/values/strings.xml)
<resources> <stringname="app_name">GeoQuiz</string> <stringname="question_text">Canberra is the capital of Australia.</string> <stringname="true_button">True</string> <stringname="false_button">False</string> <stringname="correct_toast">Correct!</string> <stringname="incorrect_toast">Incorrect!</string></resources>
接下来更新监听器代码以创建并展示toast消息。输入代码时可利用Android Studio的代码自动补全功能,这可以节省大量时间,所以越早熟悉它的使用越好。
参照代码清单1-11,在MainActivity.kt文件中依次输入代码。当输入到
Toast
类后的点号时,Android Studio会弹出一个窗口,给出建议使用的Toast
类的常量与函数。可以使用上下键进行选择。(如果不想使用代码自动补全功能,请不要按Tab键、Return/Enter键,或用鼠标点击弹出窗口,只管继续输入代码直至完成。)
在建议列表里,选择
makeText(context: Context, resId: Int, duration: Int)
,代码自动补全功能会自动添加完整的函数调用。完成
makeText(...)
函数的全部参数设置,完成后的代码如代码清单1-11所示。代码清单1-11 创建提示消息(MainActivity.kt)
override fun onCreate(savedInstanceState:Bundle?){ ... trueButton.setOnClickListener { view:View-> // Do something in response to the click here Toast.makeText( this, R.string.correct_toast, Toast.LENGTH_SHORT) .show() } falseButton.setOnClickListener { view:View-> // Do something in response to the click here Toast.makeText( this, R.string.incorrect_toast, Toast.LENGTH_SHORT) .show() }}
为了创建toast,我们调用了
Toast.makeText(Context!, Int, Int)
静态函数。该函数会创建并配置Toast
对象。该函数的Context
参数通常是Activity
的一个实例(Activity
本身就是Context
的子类)。这里,我们传入MainActivity
作为Context
值参。第二个参数是toast要显示字符串消息的资源ID。
Toast
类必须借助Context
才能找到并使用字符串资源ID。第三个参数通常是两个Toast
常量中的一个,用来指定toast消息的停留时间。创建toast后,可调用
Toast.show()
在屏幕上显示toast消息。由于使用了代码自动补全功能,因此你就不用自己导入
Toast
类了,Android Studio会自动导入相关类。好了,现在可以运行应用了。
1.8 使用模拟器运行应用
运行Android应用需使用硬件设备或虚拟设备(virtual device)。包含在开发工具中的Android设备模拟器可提供多种虚拟设备。
要创建Android虚拟设备(AVD),在Android Studio中,选择Tools → AVD Manager菜单项。当AVD管理器窗口弹出时,点击窗口左下角的+Create Virtual Device…按钮。
如图1-18所示,在随后弹出的对话框中,可以看到有很多配置虚拟设备的选项。作为首个虚拟设备,我们选择模拟运行Pixel 2设备,然后点击Next继续。
图1-18 选择虚拟设备
如图1-19所示,接下来选择模拟器的系统镜像。选择x86 Pie模拟器后点击Next按钮继续。(点击Next按钮之前,如果需要下载模拟器组件,按提示操作即可。)
图1-19 选择系统镜像
最后,如图1-20所示,可以对模拟器的各项参数做最终修改并确认。当然,如果需要,后面再修改模拟器的参数也行。现在,为模拟器取个便于识别的名字,点击Finish按钮完成虚拟设备的创建。
图1-20 模拟器参数调整
AVD创建成功后,就可以用它运行GeoQuiz应用了。点击Android Studio工具栏上的Run按钮,或使用Control+R快捷键。在随后出现的Select Deployment Target 对话框里,选中刚才配置的虚拟设备后点击OK按钮,Android Studio会启动它,安装应用包(APK)并运行应用。
模拟器的启动过程比较耗时,请耐心等待。等设备启动、应用运行后,就可以在应用界面点击按钮,让toast告诉你答案了。
假如启动时或在点击按钮时GeoQuiz应用崩溃,可以在Android的LogCat工具窗口中看到有用的诊断信息。(如果LogCat没有自动打开,可点击Android Studio窗口底部的Logcat按钮打开它。)在LogCat工具窗口的搜索对话框中输入
MainActivity
可过滤日志信息。如图1-21所示,查看日志,可看到抢眼的红色1异常信息。1本书彩图可到图灵社区本书页面“随书下载”处查看。——编者注
图1-21
NullPointerException
异常示例将你输入的代码与书中的代码做一下比较,找出错误并修正,然后尝试重新运行应用(第3章和第5章还会深入介绍LogCat和代码调试的知识)。
学习过程中最好不要关掉模拟器,这样就不必在反复运行调试应用时浪费时间等待AVD启动了。
点击AVD模拟器上的后退按钮可以停止应用。这个后退按钮的形状像一个指向左侧的三角形(在较早版本的Android中,它像一个U型箭头)。需要调试变更时,再通过Android Studio重新运行应用。
模拟器虽然好用,但在实体设备上测试应用能获得更准确的结果。在第2章中,我们会在实体设备上运行GeoQuiz应用,还会为GeoQuiz应用添加更多地理知识问题。
1.9 深入学习:Android编译过程
学到这里,你可能迫切想了解Android是如何编译的。你已看到,在项目文件有变动时,Android Studio无须指示便会自动进行编译。在整个编译过程中,Android开发工具将资源文件、代码以及AndroidManifest.xml文件(包含应用的元数据)编译生成.apk文件。为了在模拟器上运行,.apk文件还需以debug key签名。(分发.apk应用给用户时,应用必须以release key签名。要进一步了解编译过程,可参考Android开发文档。)
那么,activity_main.xml布局文件的内容是如何转变为
View
对象的呢?作为编译过程的一部分,aapt2(Android Asset Packaging Tool)将布局文件资源编译压缩紧凑后,打包到.apk文件中。然后,在MainActivity
类的onCreate(Bundle?)
函数调用setContentView(...)
函数时,MainActivity
使用LayoutInflater
类实例化布局文件中定义的每一个View
对象,如图1-22所示。图1-22 activity_main.xml中的视图实例化
(除了在XML文件中定义视图外,也可以在activity里使用代码创建视图类。不过,从设计角度来看,应用展现层与逻辑层分离有很多好处,其中最主要的一点是可以利用SDK内置的设备配置变更,这一点将在第3章中详细讲解。)
有关XML不同属性的工作原理以及视图如何显示在屏幕上等更多信息,请参见第10章。
Android编译工具
当前,我们看到的项目编译都是在Android Studio里执行的。编译功能已整合到IDE中,IDE负责调用aapt2等Android标准编译工具,但编译过程本身仍由Android Studio管理。
有时,出于某种原因,可能需要脱离Android Studio编译代码。最简单的方法是使用命令行编译工具。Android编译系统使用的编译工具叫Gradle。
(注意,能读懂本节内容并按步骤操作是最好的。如果看不懂,甚至不知道为什么要手动编译代码,或者是无法正确使用命令行,也不必太在意,请继续学习下一章内容。命令行工具的具体使用不在本书讨论范围内。)
要从命令行使用Gradle,请切换至项目目录并执行以下命令:
$ ./gradlew tasks
如果是Windows系统,执行以下命令:
> gradlew.bat tasks
执行以上命令会显示一系列可用任务。你需要的任务是installDebug,因此,再执行以下命令:
$ ./gradlew installDebug
如果是Windows系统,执行以下命令:
> gradlew.bat installDebug
以上命令将把应用安装到当前连接的设备上,但不会运行它。要运行应用,需要在设备上手动启动。
1.10 关于挑战练习
本书大部分章末安排了挑战练习,需要你独立完成。有些较简单,就是练习所学知识。有些较难,需要较强的问题解决能力。
希望你一定完成这些练习。攻克它们不仅可以巩固所学知识,树立信心,还可以让自己从被动学习者快速成长为自主开发的Android程序员。
尝试完成挑战练习时,若一时陷入困境,可稍作休息,厘清头绪,重新再来。如果仍然无法解决,可访问本书论坛,看看其他读者发布的解决方案。当然你也可以发布问题和答案,与其他读者一起交流学习。
为避免搞乱当前项目,建议你在Android Studio中先复制当前项目,然后在复制的项目上做练习。
在你的机器上,通过文件浏览器找到项目文件的根目录,复制一份GeoQuiz文件并重命名为GeoQuiz Challenge。回到Android Studio中,选择File → Import Project...菜单项,通过导入功能找到GeoQuiz Challenge并导入。这样,复制项目就在新窗口中打开了。开始挑战吧!
1.11 挑战练习:定制toast消息
这个练习要你定制toast消息,改在屏幕顶部而不是底部显示弹出消息。这要用到
Toast
类的setGravity
函数,并使用Gravity.TOP
重力值。具体如何使用,请参考Android开发者文档。节选自
经典权威Android入门与进阶图书
《Android编程权威指南(第4版)》
作者:Kristin Marsicano,Brian Gardner,Bill Phillips,Chris Stewart
译者:王明发
| 图书特色
实战项目引导,全面覆盖 Android 开发知识点
使用 Kotlin 编写,兼容 Android 5.0 至 11.0
随书附赠 Android 开发速查表,随用随查
业内专家张鸿洋、张涛、丰生强、丁志虎、2BAB 推荐阅读
Big Nerd Ranch 是美国一家专业的移动开发技术培训机构。本书主要以其 Android 训练营教学课程为基础,融合了几位作者多年的心得体会,是一本完全面向实战的 Android 编程权威指南。全书共 32 章,详细介绍了 7 个 Android 应用的开发过程。通过这些精心设计的应用,读者可掌握很多重要的理论知识和开 发技巧,获得宝贵的开发经验。
第 4 版较之前版本做了重大更新,每一章的内容都做了修改。开发语言从 Java 换成了 Kotlin。全面引入了 Android Jetpack 组件库并开始使用第三方库。
京东传送门