第 1 章 Android开发初体验

第 1 章 Android开发初体验

本章介绍编写Android应用需掌握的一些新的概念和UI组件。学完本章,如果没能理解全部内容,也不必担心。后续章节还会涉及这些概念并有更加详细的讲解。

马上要编写的首个应用名为GeoQuiz,它能测试用户的地理知识。用户单击TRUE或FALSE按钮来回答屏幕上的问题,GeoQuiz可即时反馈答案正确与否。

图1-1显示了用户点击FALSE按钮的结果。

{%}

图1-1 正确答案应该是伊斯坦布尔(Istanbul),而不是君士坦丁堡

1.1 应用开发基础

GeoQuiz应用由一个activity和一个布局(layout)组成。

  • activity是Android SDK中Activity类的一个具体实例,负责管理用户与信息屏的交互。

    应用的功能是通过编写一个个Activity子类来实现的。简单的应用可能只需一个子类,而复杂的应用则会有多个。

    GeoQuiz是个简单应用,因此它只有一个名为QuizActivityActivity子类。QuizActivity管理着图1-1所示的用户界面。

  • 布局定义了一系列用户界面对象以及它们显示在屏幕上的位置。组成布局的定义保存在XML文件中。每个定义用来创建屏幕上的一个对象,如按钮或文本信息。

    GeoQuiz应用包含一个名为activity_quiz.xml的布局文件。该布局文件中的XML标签定义了图1-1所示的用户界面。

QuizActivity与activity_quiz.xml文件的关系如图1-2所示。

{%}

图1-2 QuizActivity管理着activity_quiz.xml文件定义的用户界面

学习了这些基本概念后,我们来创建本书第一个应用。

1.2 创建Android项目

首先我们创建一个Android项目。Android项目包含组成一个应用的全部文件。

启动Android Studio程序,首次运行的话,会看到如图1-3所示的欢迎对话框。

{%}

图1-3 欢迎来到Android Studio

在欢迎界面,选择创建Android Studio新项目选项(Start a new Android Studio project);非首次运行的话,选择File → New Project…菜单项即可。

现在,你应该打开了新建项目向导界面。在此界面的应用名称(Application name)处输入GeoQuiz,如图1-4所示。在公司域名(Company Domain)处输入android.bignerdranch.com。此时自动产生的包名称(Package name)会变为com.bignerdranch.android.geoquiz。至于项目存储位置(Project location),这就看个人喜好了。

{%}

图1-4 创建新项目

注意,以上包名遵循了“DNS反转”约定,也就是将企业组织或公司的域名反转后,在尾部附加上应用名称。遵循此约定可以保证包名的唯一性,这样,同一设备和Google Play商店的各类应用就可以区分开来。

单击Next按钮,接下来配置应用支持哪些版本的Android设备。GeoQuiz应用只能在手机上运行,所以这里勾选Phone and Tablet选项。SDK最低版本选择API 16: Android 4.1 (Jelly Bean),如图1-5所示。第6章会介绍Android不同SDK版本的差异。

{%}

图1-5 设备支持配置

单击Next按钮继续。

在接下来的窗口中,需要为GeoQuiz应用的启动初始屏选择模板,如图1-6所示。选择Empty Activity后单击Next按钮继续。

{%}

图1-6 选择activity种类(空activity)

(Android Studio更新频繁,因此新版本的向导画面看起来可能与本书所示略有不同。通常,这不是问题,工具更新后,向导画面的配置选项应该不会有太大差别。如果向导画面看起来大有不同,可以肯定开发工具已进行了重大更新。不要担心,请访问本书论坛http://forums.bignerdranch.com,获取最新版本开发工具的使用信息。)

在应用向导的最后一个窗口,命名activity子类为QuizActivity,如图1-7所示。注意子类名的Activity后缀。尽管不是必需的,但我们建议遵循这种规范的命名约定。

{%}

图1-7 配置新建的activity

保持Generate Layout File处于选中状态。为体现布局与activity间的对应关系,布局名(Layout Name)会自动更新为activity_quiz。布局的命名规则是:将activity名称的单词顺序颠倒过来并全部转换为小写字母,然后在单词间添加下划线。对于后续章节中的所有布局以及将要学习的其他资源,建议统一采用这种命名风格。

如果在你的Android Studio版本中还有其他选项,保持默认选择不变。单击Finish按钮,Android Studio会完成创建并打开新的项目。

1.3 Android Studio使用导航

如图1-8所示,Android Studio已在工作区窗口里打开新建项目。整个工作区窗口分为不同的区域,这里统称为工具窗口(Tool Window)。

{%}

图1-8 新的项目窗口

左边是项目工具窗口(project tool window)视图,通过它可以管理所有项目相关的文件。

中间部分是代码编辑区(editor)视图。为便于开发,Android Studio默认在代码编辑区打开了activity_quiz.xml文件。(如果你在代码区看到的是图片,可点击底部的Text页切换。)当然,你也可以设置在右边窗口预览当前编辑的文件。

点击工作区窗口左边、右边以及底部标有各种名称的工具按钮区域,可显示或隐藏各类工具窗口。当然,也可以直接使用它们对应的快捷键。假如看不到某个工具按钮的话,可以点击左下角的灰色方形区域或单击View → Tool Buttons菜单项找到它。

1.4 用户界面设计

当前,activity_quiz.xml文件定义了默认的activity布局。应用默认的XML布局文件内容经常改变,但总是与代码清单1-1所示文件相似。

代码清单1-1 默认的activity布局(activity_quiz.xml)

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".QuizActivity">

    <TextView
        android:text="@string/hello_world"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>

应用activity的默认布局定义了两个组件(widget):RelativeLayoutTextView

组件是用户界面的构造模块。组件可以显示文字或图像,与用户交互,甚至布置屏幕上的其他组件。按钮、文本输入控件和选择框等都是组件。

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

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

图1-9 显示在屏幕上的默认组件

不过,图1-9所示的默认组件并不是我们需要的,QuizActivity的用户界面需要下列五个组件:

  • 一个垂直LinearLayout组件;

  • 一个TextView组件;

  • 一个水平LinearLayout组件;

  • 两个Button组件。

图1-10展示了以上组件是如何构成QuizActivity用户界面的。

图1-10 布置并显示在屏幕上的组件

下面我们在activity_quiz.xml文件中定义这些组件。

如代码清单1-2所示,修改activity_quiz.xml文件。注意,需删除的XML已打上删除线,需添加的XML以粗体显示。本书统一使用这样的版式约定。

代码清单1-2 在XML文件(activity_quiz.xml)中定义组件

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".QuizActivity">

    <TextView
        android:text="@string/hello_world"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</RelativeLayout>

<LinearLayout xmlns: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开头的代码可能会产生错误信息。暂时忽略它们,稍后会处理。

将XML文件与图1-10所示的用户界面进行对照,可以看出组件与XML元素一一对应。元素的名称就是组件的类型。

各元素均有一组XML属性属性可以看作是如何配置组件的指令。

为便于理解元素与属性的工作原理,接下来我们将以层次等级视角来研究布局。

1.4.1 视图层级结构

组件包含在视图对象的层级结构中,这种结构称作视图层级结构(view hierarchy)。图1-11展示了代码清单1-2所示XML布局对应的视图层级结构。

{%}

图1-11 布局中组件及属性的层级结构

从布局的视图层级结构可以看到,其根元素是一个LinearLayout组件。作为根元素,LinearLayout组件必须指定Android XML资源文件的命名空间属性为http://schemas.android.com/apk/res/android

LinearLayout组件继承自View子类的ViewGroup组件。ViewGroup组件是个包含并配置其他组件的特殊组件。如需以一列或一排的样式布置组件,使用LinearLayout组件就可以了。其他ViewGroup子类还包括FrameLayoutTableLayoutRelativeLayout

若某个组件包含在一个ViewGroup中,该组件与ViewGroup即构成父子关系。根LinearLayout有两个子组件:TextViewLinearLayout。作为子组件的LinearLayout本身还有两个Button子组件。

1.4.2 组件属性

下面我们一起来看看配置组件的一些常用属性。

1. android:layout_widthandroid:layout_height属性

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

  • match_parent:视图与其父视图大小相同。

  • wrap_content:视图将根据其展示的内容自动调整大小。

(以前还有个fill_parent属性值,等同于match_parent,目前已废弃不用。)

LinearLayout组件的高度与宽度属性值均为match_parentLinearLayout虽然是根元素,但它也有父视图(View)——Android提供该父视图来容纳应用的整个视图层级结构。

其他包含在界面布局中的组件,其高度与宽度属性值均被设置为wrap_content。请参照图1-10理解该属性值定义尺寸大小的作用。

TextView组件比其包含的文字内容区域稍大一些,这主要是android:padding="24dp"属性的作用。该属性告诉组件在决定大小时,除内容本身外,还需增加额外指定量的空间。这样屏幕上显示的问题与按钮之间便会留有一定的空间,使整体显得更为美观。(不理解dp的意思?dp即density-independent pixel,指与密度无关像素,第8章将介绍有关它的概念。)

2. android:orientation属性

android:orientation属性是两个LinearLayout组件都具有的属性,它决定两者的子组件是水平放置还是垂直放置。根LinearLayout是垂直的,子LinearLayout是水平的。

LinearLayout子组件的定义顺序决定其在屏幕上显示的顺序。在竖直的LinearLayout中,第一个定义的子组件出现在屏幕的最上端;而在水平的LinearLayout中,第一个定义的子组件出现在屏幕的最左端。(如果设备文字从右至左显示,如Arabic或者Hebrew,第一个定义的子组件则出现在屏幕的最右端。)

3. android:text属性

TextViewButton组件具有android:text属性。该属性指定组件要显示的文字内容。

请注意,android:text属性值不是字符串值,而是对字符串资源(string resources)的引用。

字符串资源包含在一个独立的名为strings的XML文件中,虽然可以硬编码设置组件的文本属性值,如android:text="True",但这通常不是个好主意。比较好的做法是:将文字内容放置在独立的字符串资源XML文件中,然后引用它们。这样也便于对应用进行本地化。

需要在activity_quiz.xml文件中引用的字符串资源目前还不存在。现在我们来添加这些资源。

1.4.3 创建字符串资源

每个项目都包含一个名为strings.xml的默认字符串文件。

在项目工具窗口中,找到app/res/values目录,显示目录内容,然后打开strings.xml文件。

可以看到,项目模版已经添加了一些字符串资源。删除不需要的hello_world部分,添加应用布局需要的三个新的字符串,如代码清单1-3所示。

代码清单1-3 添加字符串资源(strings.xml)

<resources>
    <string name="app_name">GeoQuiz</string>

    <string name="question_text">
        Constantinople is the largest city in Turkey.
    </string>
    <string name="true_button">TRUE</string>
    <string name="false_button">FALSE</string>
</resources>

(基于不同的Android Studio版本,你可能有其他字符串,请勿删除它们,否则将导致与应用菜单相关的其他文件发生版式错误。)

现在,在GeoQuiz项目的任何XML文件中,只要引用到@string/false_button,应用运行时,就会得到文本“FALSE”。

保存strings.xml文件。这时,activity_quiz.xml布局曾经提示缺少字符串资源的信息应该不会再出现了。(如仍有错误信息,那么检查一下这两个文件,确认是否存在输入或拼写错误。)

默认的字符串文件名为strings.xml,当然也可以按个人喜好任意取名。一个项目也可以有多个字符串文件。只要这些文件都放置在res/values/目录下,并且含有一个resources根元素,以及多个string子元素,应用就能找到并正确使用它们。

1.4.4 预览界面布局

至此,应用的界面布局已经完成,现在我们使用图形布局工具来进行实时预览。首先,确认保存了所有相关文件并且无错误发生,然后回到activity_quiz.xml文件,点击编辑区右边的Tab页打开预览工具窗口(如果还没打开的话),如图1-12所示。

图1-12 在图形布局工具中预览界面布局(activity_quiz.xml)

1.5 从布局XML到视图对象

想知道activity_quiz.xml中的XML元素是如何转换为视图对象的吗?答案就在于QuizActivity类。

在创建GeoQuiz项目的同时,我们也创建了一个名为QuizActivityActivity子类。QuizActivity类文件存放在项目的app/java目录下。java目录是项目全部Java源代码的存放处。

在项目工具窗口中,依次展开app/java目录与com.bignerdranch.android.geoquiz包,显示其中的内容。然后打开QuizActivity.java文件,查看其中的代码,如代码清单1-4所示。

代码清单1-4 QuizActivity的默认类文件(QuizActivity.java)

package com.bignerdranch.android.geoquiz;

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

public class QuizActivity extends AppCompatActivity {

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

}

(是不是不明白AppCompatActivity的作用?它实际就是一个Activity子类,主要是为Android旧版本系统提供兼容性支持。第13章会详细介绍AppCompatActivity。)

如果无法看到全部类包导入语句,请单击第一行导入语句左边的⊕符号来显示它们。

该Java类文件包含一个Activity方法:onCreate(Bundle)

(如果你的文件还包含onCreateOptionsMenu(Menu)onOptionsItemSelected(MenuItem)方法,暂时不用理会。第13章会详细介绍它们。)

activity子类的实例创建后,onCreate(Bundle)方法会被调用。activity创建后,它需要获取并管理属于自己的用户界面。获取activity的用户界面,可调用以下Activity方法:

public void setContentView(int layoutResID)

根据传入的布局资源ID参数,该方法生成指定布局的视图并将其放置在屏幕上。布局视图生成后,布局文件包含的组件也随之以各自的属性定义完成实例化。

资源与资源ID

布局是一种资源资源是应用非代码形式的内容,比如图像文件、音频文件以及XML文件等。

项目的所有资源文件都存放在目录app/res的子目录下。在项目工具窗口中可以看到,activity_quiz.xml布局资源文件存放在res/layout/目录下。包含字符串资源的strings文件存放在res/values/目录下。

可以使用资源ID在代码中获取相应的资源。activity_quiz.xml定义的布局的资源ID为R.layout.activity_quiz。

查看GeoQuiz应用的资源ID需要切换项目视图。Android Studio默认使用Android项目视图,如图1-13所示。为让开发者专注于最常用的文件和目录,默认视图隐藏了Android项目的真实文件目录结构。

{%}

图1-13 切换项目视图

在项目工具窗口的最上部找到倒三角下拉菜单,从Android项目视图切换至Project视图。Project视图会显示出当前项目的所有文件和目录。

展开目录app/build/generated/source/r/debug,找到项目包名并打开其中的R.java文件,即可看到GeoQuiz应用当前所有的资源。R.java文件在Android项目编译过程中自动生成,如该文件头部的警示所述,请不要修改该文件的内容。

修改资源后,资源文件不会实时刷新。Android Studio另外还存有一份代码编译用的R.java隐藏文件。当前编辑区打开的R.java文件仅在应用安装至设备或模拟器前产生,因此只有在Android Studio中点击运行应用时,它才会得到更新。

R.java文件通常比较大,代码清单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;

public final class R {
    public static final class anim {
        ...
    }
    ...

    public static final class id {
        ...
    }
    public static final class layout {
        ...
        public static final int activity_quiz=0x7f030017;
    }
    public static final class mipmap {
        public static final int ic_launcher=0x7f030000;
    }
    public static final class string {
        ...
        public static final int app_name=0x7f0a0010;
        public static final int correct_toast=0x7f0a0011;
        public static final int false_button=0x7f0a0012;
        public static final int incorrect_toast=0x7f0a0013;
        public static final int question_text=0x7f0a0014;
        public static final int true_button=0x7f0a0015;
    }
}

可以看到R.layout.activity_quiz即来自该文件。activity_quiz是R的内部类layout里的一个整型常量名。

应用需要的字符串同样具有资源ID。目前为止,我们还未在代码中引用过字符串,如果需要,可以使用以下方法:

setTitle(R.string.app_name);

Android为整个布局文件以及各个字符串生成资源ID,但activity_quiz.xml布局文件中的组件除外,因为不是所有的组件都需要资源ID。在本章中,我们只用到两个按钮,因此只需为这两个按钮生成相应的资源ID即可。

本书主要使用Android项目视图,生成资源ID之前,记得检查当前视图模式。当然,如果你更喜欢Project视图,也不会有什么问题。

要为组件生成资源ID,请在定义组件时为其添加android:id属性。在activity_quiz.xml文件中,分别为两个按钮添加上android:id属性,如代码清单1-6所示。

代码清单1-6 为按钮添加资源ID(activity_quiz.xml)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
... >

  <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,而对字符串资源只是做引用

1.6 组件的实际应用

既然按钮有了资源ID,就可以在QuizActivity中直接获取它们。首先,在QuizActivity.java文件中增加两个成员变量。

在QuizActivity.java文件中输入代码清单1-7所示代码。(请勿使用代码自动补全功能。)

代码清单1-7 添加成员变量(QuizActivity.java)

public class QuizActivity extends AppCompatActivity {

    private Button mTrueButton;
    private Button mFalseButton;

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

}

文件保存后,会看到两个错误提示。没关系,这点错误马上就可以搞定。请注意新增的两个成员(实例)变量名称的m前缀。该前缀是Android编程应遵循的命名约定,本书将始终遵循该约定。

现在,在将鼠标移至代码左边的错误提示处时,会看到两条同样的错误信息:Cannot resolve symbol ‘Button’。

该错误提示告诉我们需要在QuizActivity.java文件中导入android.widget.Button类包。可在文件头部手动输入以下代码:

import android.widget.Button;

或者使用Option+Return(或Alt+Enter)组合键,让Android Studio自动为你导入。代码有误时,也可以使用该组合键来修正。记得要常用。

类包导入完成后,刚才的错误提示应该就消失了。(如果错误提示仍然存在,请检查Java代码以及XML文件,确认是否存在输入或拼写错误。)

接下来,我们来编码使用按钮组件,这需要以下两个步骤:

  • 引用生成的视图对象;

  • 为对象设置监听器,以响应用户操作。

1.6.1 引用组件

在activity中,可通过以下Activity方法引用已生成的组件:

public View findViewById(int id)

该方法以组件的资源ID作为参数,返回一个视图对象。

在QuizActivity.java文件中,使用按钮的资源ID获取生成的对象,赋值给对应的成员变量,如代码清单1-8所示。注意,赋值前,必须先将返回的View转型(cast)为Button

代码清单1-8 引用组件(QuizActivity.java)

public class QuizActivity extends AppCompatActivity {

    private Button mTrueButton;
    private Button mFalseButton;

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

        mTrueButton = (Button) findViewById(R.id.true_button);
        mFalseButton = (Button) findViewById(R.id.false_button);
    }

}

1.6.2 设置监听器

Android应用属于典型的事件驱动类型。不像命令行或脚本程序,事件驱动型应用启动后,即开始等待行为事件的发生,如用户单击某个按钮。(事件也可以由操作系统或其他应用触发,但用户触发的事件更直观。)

应用等待某个特定事件的发生,也可以说应用正在“监听”特定事件。为响应某个事件而创建的对象叫作监听器(listener)。监听器是实现特定监听器接口的对象,用来监听某类事件的发生。

无需自己编写,Android SDK已经为各种事件内置开发了很多监听器接口。当前应用需要监听用户的按钮“单击”事件,因此监听器需实现View.OnClickListener接口。

首先处理TRUE按钮。在QuizActivity.java文件中,在onCreate(...)方法的变量赋值语句后输入下列代码,如代码清单1-9所示。

代码清单1-9 为TRUE按钮设置监听器(QuizActivity.java)

    ...

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

        mTrueButton = (Button) findViewById(R.id.true_button);
        mTrueButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Does nothing yet, but soon!
            }
        });

        mFalseButton = (Button) findViewById(R.id.false_button);
    }
}

(如果遇到View cannot be resolved to a type的错误提示,请使用Option+Return或Alt+Enter快捷键导入View类。)

在代码清单1-9中,我们设置了一个监听器。当按钮mTrueButton被点击后,监听器会立即通知我们。传入setOnClickListener(OnClickListener)方法的参数是一个监听器。该参数是一个实现了OnClickListener接口的对象。

使用匿名内部类

传入SetOnClickListener(OnClickListener)方法的监听器参数是一个匿名内部类(anonymous inner class)实现,语法看上去稍显复杂,不过有个助记小技巧:最外层括号内的全部实现代码就是传入SetOnClickListener(OnClickListener)方法内的一个参数。该参数就是新建的一个匿名内部类的实现代码。

mTrueButton.setOnClickListener(new View.OnClickListener() {
     @Override
     public void onClick(View v) {
         // Does nothing yet, but soon!
     }
});

本书所有的监听器都以匿名内部类来实现。这样做有两大好处。第一,因为匿名内部类的使用,我们可在同一处实现监听器方法,代码更清晰可读;第二,事件监听器一般只在同一处使用,使用匿名内部类可避免不必要的命名类实现。

匿名内部类实现了OnClickListener接口,因此它也必须实现该接口唯一的onClick(View)方法。onClick(View)方法的代码暂时是一个空结构。虽然实现监听器接口需要实现onClick(View)方法,但具体如何实现由使用者决定,因此即使是空的实现方法,编译器也可以编译通过。

(如果匿名内部类、监听器、接口等概念你已忘得差不多了,现在就去复习复习Java语言的基础知识,或者手边放一本参考书备查。)

参照代码清单1-10为FALSE按钮设置类似的事件监听器。

代码清单1-10 为FALSE按钮设置监听器(QuizActivity.java)

...
    mTrueButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // Does nothing yet, but soon!
        }
    });

    mFalseButton = (Button) findViewById(R.id.false_button);
    mFalseButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // Does nothing yet, but soon!
        }
    });
}

1.7 创建提示消息

接下来要实现的就是,分别单击两个按钮,弹出我们称为toast的提示消息。Android的toast是用来通知用户的简短弹出消息,用户无需输入或进行任何操作。这里,我们要用toast来告知用户其答案正确与否,如图1-14所示。

图1-14 toast反馈消息提示

首先回到strings.xml文件,如代码清单1-11所示,为toast添加消息显示用的字符串资源。

代码清单1-11 增加toast字符串(strings.xml)

<resources>
  <string name="app_name">GeoQuiz</string>

  <string name="question_text">Constantinople is the largest city in Turkey.</string>
  <string name="true_button">TRUE</string>
  <string name="false_button">FALSE</string>
  <string name="correct_toast">Correct!</string>
  <string name="incorrect_toast">Incorrect!</string>
</resources>

调用来自Toast类的以下方法,可创建一个toast:

public static Toast makeText(Context context, int resId, int duration)

该方法的Context参数通常是Activity的一个实例(Activity本身就是Context的子类)。第二个参数是toast要显示字符串消息的资源ID。Toast类必须借助Context才能找到并使用字符串的资源ID。第三个参数通常是两个Toast常量中的一个,用来指定toast消息显示的持续时间。

创建toast后,可调用Toast.show()方法在屏幕上显示toast消息。

QuizActivity代码里,分别对两个按钮的监听器调用makeText(...)方法,如代码清单1-12所示。在添加makeText(...)时,可利用Android Studio的代码自动补全功能,让代码输入更加轻松。

使用代码自动补全

代码自动补全功能可以节约大量开发时间,越早掌握受益越多。

参照代码清单1-12,依次输入代码。当输入到Toast类后的点号时,Android Studio会弹出一个窗口,窗口内显示了建议使用的Toast类的常量与方法。

要选择需要的建议方法,使用上下方向键。(如果不想使用代码自动补全功能,请不要按Tab键、Return/Enter键,或使用鼠标点击弹出窗口,只管继续输入代码直至完成。)

在列表建议清单里,选择makeText(Context context, int resID, int duration)方法,代码自动补全功能会自动添加完整的方法调用。

完成makeText方法的全部参数设置,完成后的代码如代码清单1-12所示。

代码清单1-12 创建提示消息(QuizActivity.java)

...
mTrueButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(QuizActivity.this,
                       R.string.incorrect_toast,
                       Toast.LENGTH_SHORT).show();
            // Does nothing yet, but soon!
    }
});

mFalseButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(QuizActivity.this,
                       R.string.correct_toast,
                       Toast.LENGTH_SHORT).show();
            // Does nothing yet, but soon!
    }
});

makeText(...)里,传入QuizActivity实例作为Context的参数值。注意此处应输入的参数是QuizActivity.this,不要想当然地直接输入this。因为匿名类的使用,这里的this指的是监听器View.OnClickListener

使用代码自动补全功能,再也不用担心某个类忘记导入了,因为Android Studio会自动导入所需的类。

现在保存,然后看看这个应用的运行情况。

1.8 使用模拟器运行应用

运行Android应用需使用硬件设备或者虚拟设备(virtual device)。包含在开发工具中的Android设备模拟器可提供多种虚拟设备。

要创建Android虚拟设备(AVD),在Android Studio中,选择Tools → Android → AVD Manager菜单项。AVD管理器窗口弹出时,点击窗口左边的Create Virtual Device…按钮。

在随后弹出的对话框中,可以看到有很多配置虚拟设备的选项。对于首个虚拟设备,我们选择模拟运行Nexus 5设备,如图1-15所示。点击Next继续。

{%}

图1-15 创建新的AVD

如图1-16所示,接下来选择模拟器的系统镜像。选择x86 Lollipop模拟器后点击Next按钮继续。

{%}

图1-16 选择系统镜像

最后,可以对模拟器的各项参数做最后修改并确认,如图1-17所示。当然,如有需要,也可以事后再编辑修改模拟器的各项参数。现在,为模拟器取个便于识别的名称,点击Finish按钮完成虚拟设备的创建。

{%}

图1-17 模拟参数调整

AVD创建成功后,我们用它运行GeoQuiz应用。点击Android Studio工具栏上的Run按钮,或者使用Control+R快捷键。Android Studio会自动找到新建的虚拟设备,安装应用包(APK),然后启动并运行应用。

虚拟机启动过程非常缓慢,请耐心等待。设备启动完成,应用运行后,就可以在应用界面点击按钮,让toast告诉我们答案。(注意,如果应用启动运行后,我们凑巧不在电脑旁,回来时就可能需要解锁AVD。跟真实设备一样,AVD闲置一定时间会自动锁上。)

假如GeoQuiz应用启动时或在点击按钮时发生崩溃,可以在Android DDMS工具窗口的LogCat视图中看到有用的诊断信息。(如果LogCat没有自动打开,可点击Android Studio窗口底部的Android按钮打开它。)查看日志,可看到抢眼的红色异常信息,如图1-18所示。日志中的Text列可看到异常的名字以及发生问题的具体位置。

{%}

图1-18 第21行代码发生了NullPointerException异常

将输入的代码与书中的代码作一下比较,找出错误并修改后,尝试重新运行应用(在接下来的两章,还会深入介绍LogCat和代码调试的知识)。

建议保持模拟器一直运行,这样就不必在反复运行调试应用时,痛苦地等待AVD启动了。

单击AVD模拟器上的Android后退按钮可以停止应用。这个后退按钮的形状像一个指向左侧的三角形(在较早版本的Android中,它像一个U型箭头)。需要调试变更时,再通过Android Studio重新运行应用。

模拟器虽然有用,但在真实设备上测试应用能获得更准确的结果。在第2章中,我们会在实体设备上运行GeoQuiz应用,还会为GeoQuiz应用添加更多挑战用户的地理知识问题。

1.9 深入学习:Android编译过程

学习到这里,你可能对Android编译的工作方式充满疑惑。你已经知道在项目文件发生变化时,无需使用命令行工具,Android Studio便会自动进行编译。在整个编译过程中,Android开发工具将资源文件、代码以及AndroidManifest.xml文件(包含应用的元数据)编译生成.apk文件。.apk应用要在模拟器上运行,.apk文件还需以debug key签名。(分发.apk应用给用户时,应用必须以release key签名。如需了解更多有关编译过程的信息,可参考Android开发文档http://developer.android.com/tools/publishing/preparing.html。)

图1-19展示了完整的编译过程。

{%}

图1-19 编译GeoQuiz应用

那么,应用的activity_quiz.xml布局文件的内容该如何转变为View对象呢?作为编译过程的一部分,aapt(Android Asset Packaging Tool)将布局文件资源编译压缩紧凑后,打包到.apk文件中。然后,在QuizActivity类的onCreate(...)方法调用setContentView(...)方法时,QuizActivity使用LayoutInflater类实例化布局文件中定义的每一个View对象,如图1-20所示。

{%}

图1-20 activity_quiz.xml中的视图实例化

(除了在XML文件中定义视图的方式外,也可以在activity里使用代码来创建视图类。不过,从设计角度来看,应用展现层与逻辑层分离好处多多,其中最主要的一点是可以利用SDK内置的设备配置改变,这一点将在第3章中详细讲解。)

有关XML不同属性的工作原理以及视图如何显示在屏幕上等更多信息,请参见第8章。

Android编译工具

截至目前,我们所看到的项目编译都是在Android Studio里执行的。编译功能已整合到IDE中,IDE负责调用aapt等Android标准编译工具,但编译过程本身仍由Android Studio管理。

有时,出于种种原因,可能需要脱离Android Studio编译代码。最简单的方法是使用命令行编译工具。现代Android编译系统使用Gradle编译工具。

(注意,能读懂本小节内容并按步骤操作那是最好了。如果看不懂,甚至不知道为什么要手工编译代码,或者是无法正确使用命令行,也不必太在意,请继续学习下一章内容。如何使用命令工具以及为什么要使用命令行工具,不在本书的讨论范围之内。)

要使用Gradle,请切换到项目目录并执行以下命令:

$ ./gradlew tasks

如果是Windows系统,执行以下命令:

> gradlew.bat tasks

执行以上命令会显示一系列可用任务。你需要的任务是installDebug,因此,再执行以下命令:

$ ./gradlew installDebug

如果是Windows系统,执行以下命令。

> gradlew.bat installDebug

以上命令将把应用安装到当前连接的设备上,但不会运行它。要运行应用,需要在设备上手动启动。

目录

  • 版权声明
  • 献词
  • 致谢
  • 如何学习Android开发
  • 开发必备工具
  • 第 1 章 Android开发初体验
  • 第 2 章 Android与MVC设计模式
  • 第 3 章 Activity的生命周期
  • 第 4 章 Android应用的调试
  • 第 5 章 第二个activity
  • 第 6 章 Android SDK版本与兼容
  • 第 7 章 UI fragment与fragment管理器
  • 第 8 章 使用布局与组件创建用户界面
  • 第 9 章 使用RecyclerView显示列表
  • 第 10 章 使用fragment argument
  • 第 11 章 使用ViewPager
  • 第 12 章 对话框
  • 第 13 章 工具栏
  • 第 14 章 SQLite数据库
  • 第 15 章 隐式intent
  • 第 16 章 使用intent拍照
  • 第 17 章 Master-Detail用户界面
  • 第 18 章 Assets
  • 第 19 章 使用SoundPool播放音频
  • 第 20 章 样式与主题
  • 第 21 章 XML drawable
  • 第 22 章 深入学习intent和任务
  • 第 23 章 HTTP与后台任务
  • 第 24 章 Looper、Handler和HandlerThread
  • 第 25 章 搜索
  • 第 26 章 后台服务
  • 第 27 章 broadcast intent
  • 第 28 章 网页浏览
  • 第 29 章 定制视图与触摸事件
  • 第 30 章 属性动画
  • 第 31 章 地理位置和Play服务
  • 第 32 章 使用地图
  • 第 33 章 material design
  • 第 34 章 编后语