开发你的第一个 Android 应用

题图 | 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是个简单应用,它只有一个名叫MainActivityActivity子类。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):ConstraintLayoutTextView

视图是用户界面的构造模块。显示在屏幕上的一切都是视图。用户能看到并与之交互的视图称为部件(widget)。有些部件可以用来显示文字或图像,有些部件(比如按钮)可以点击以触发事件任务。

Android SDK内置了多种部件,通过配置各种部件可获得应用所需的外观及行为。每一个部件都是View类或其子类(比如TextViewButton)的一个具体实例。

我们得想办法告诉部件它们在屏幕上该位于哪里。ViewGroup就是这样一种特殊的View,它包含并布置其他视图。ViewGroup视图本身不显示内容,它规划其他视图内容应该显示在哪里。ViewGroup通常又称为布局。

在当前默认布局里,ConstraintLayout这个ViewGroup布置了一个TextView部件,这是它唯一的子部件。有关布局和部件的知识,以及如何使用ConstraintLayout,第10章将详述。

图1-10展示了代码清单1-1中定义的ConstraintLayoutTextView是如何在屏幕上显示的。

图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子类还有ConstraintLayoutFrameLayout

如果某个视图包含在一个ViewGroup中,该视图与ViewGroup即构成父子关系。根LinearLayout有两个子部件:TextView和另一个LinearLayout。作为子部件的LinearLayout自己还有两个Button子部件。

1.4.2 部件属性

下面来看看配置部件时常用的一些属性。

  1. android:layout_widthandroid:layout_height属性

    几乎每类部件都需要android:layout_widthandroid:layout_height属性。以下是它们的两个常见属性值(二选一)。

    LinearLayout部件的高度与宽度属性值均为match_parentLinearLayout虽然是根元素,但它也有父视图——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属性

    TextViewButton部件具有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项目的同时,向导也创建了一个名为MainActivityActivity子类。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 组件库并开始使用第三方库。

    京东传送门

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值