结束 Android 开发(入门)课程 的第一部分《布局和交互》后,来到第二部分《多屏幕应用》,这部分课程要完成一个 Miwok Dictionary 的多屏幕应用,这个应用由 Numbers、Colors、Family Members、Phrases 四个类别的 English 与 Miwok 对照列表以及对应图片和音频文件内容组成,每个列表有一个屏幕 (Activity)。
从现在开始,学员不再是初学者,所以课程的难度开始增加,进度也逐渐加快了。不过练习的机会也变多了,保证了学员能跟上节奏。
Miwok App 分五节课完成,每个课程的进度分配如下:
- 使 App 能切换显示多个屏幕,每个屏幕暂时保持空白,主要处理用户的触摸事件;
- 使 App 显示 English 与 Miwok 对照列表,主要涉及数组 Array 和列表 List 这两个存储数据的 Java 数据结构;
- 改进 App 列表,在单词旁显示图片,微调排版;
- 在 App 列表添加播放音频按钮,播放由 Andrea Delgado-Olsen 录制的 Miwok mp3 文件;
- 利用四个 fragment 将四个屏幕整合到一个 Activity 。
这是第二部分《多屏幕应用》的第一节课,导师是 Katherine Kuan 和 Jessica Lin。
关键词:Implicit Intent & Explicit Intent、AndroidManifest.xml、Event Listeners、Abstract Method、Interface
Activity
通常与他人协作时,无需自己从头开始新建项目,可以从 Github 下载已有项目 zip,解压后在 Android Studio 选择 "Import Project…" 导入工程;或者直接在 Android Studio 初始界面选择 "Check out projects from version control" 导入带版本控制的工程。
作为一名开发者,要学会阅读别人的项目代码,可以通过描绘图表来了解大致的项目结构。对于一些大工程,可以关注一些关键文件,例如 App 的主屏幕 app/src/main/java/MainActivity.java,资源文件 app/src/main/res,包括
- layout 目录下保存的布局 XML 文件;
- mipmap 目录下保存的 App icon 图标图片文件;
- values 目录下保存的 colors 颜色声明 XML 文件、dimens 尺寸 (dp & sp) 声明 XML 文件、strings 字符串声明 XML 文件、styles 主题样式定义 XML 文件。
来看看 MainActivity.java 的文件结构,下面是一个新工程的 MainActivity.java 文件,可分为五部分理解。
package com.example.android.miwok;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set the content of the activity to use the activity_main.xml layout file
setContentView(R.layout.activity_main);
}
}
- 第一行,文件包名,由 Java 关键词
package
开头,后面跟着新建项目时指定的唯一包名,用于 Android 设备 和 Play Store 识别 App 的唯一标识。 - 第二部分,import statement,引用 Activity 需要的由 Android 团队提供的 Android 框架代码,主要是 Java Class,随着代码的增多,import 语句也会增多,Android Studio 可开启自动添加 import 声明功能。
- 第三部分,Class statement,
public class MainActivity
代表 MainActivity 的公共类,在声明内的代码包含 MainActivity 类的定义;extends AppCompatActivity
代表这个类继承了 Super Class(超级类)AppCompatActivity 的所有行为,包括在设备上显示窗口和应用栏。 - 第四部分,Method override,即 MainActivity 改写了 AppCompatActivity 的 onCreate method,自定义 App 初始化后执行的代码,一般 App 都会出现这个 override,这也是 Android Studio 自动生成的原因。
- 第五部分,将一个 XML 布局资源文件设置为内容视图。
Android Manifest
在 Android Studio 新建 Activity,可以右键 app/Activity/Empty Activity,输入名称,点击完成。成功新建 Activity 后会自动在 AndroidManifest.xml 添加一个新的 Activity XML 元素。
来看看 AndroidManifest.xml 的文件结构。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.miwok">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".NumbersActivity"
android:label="@string/category_numbers" />
</application>
</manifest>
每个 App 都有 AndroidManifest.xml 文件,由 Android Studio 自动生成并根据项目进度自动更新,它相当于一个 App 的目录,存储了关于 App 的重要信息,例如
<!-- 保存 App 的 icon 图标 -->
android:icon="@mipmap/ic_launcher"
<!-- 保存 App 在应用栏显示的名称 -->
android:label="@string/app_name"
<!-- 保存 App 的主题样式路径 -->
android:theme="@style/AppTheme"
设备在安装 App 后,在运行任何代码之前,会首先查看 AndroidManifest.xml 文件。
在 MainActivity 部分,可以看到 <intent-filter>
,这表示 MainActivity 能够接受和响应 intent 请求,中间包含的两条语句
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
表示 App 启动时进入的是 MainActivity,相当于 App 启动时 Android 系统向 App 发送了一个 intent,由 MainActivity 接受和响应。
新添加的 Activity 紧接着出现在 MainActivity 后面,支持修改 Activity 的属性,例如 Google 搜索 "Android Manifest Activity tag" 可以找到 Android <activity> 文档,其中 android:label 属性可以修改 Activity 在应用栏显示的标签名。
Intent
两个 Activity 之间可以通过 intent 联系,这里对之前 Just Java App 对邮件的 intent 作详细介绍,intent 可分为 Explicit intent(显性 intent)和 Implicit intent(隐性 intent)。
- Implicit intent 不指定响应 Activity,通常用于外部 App。
- Explicit intent 须指定响应 Activity,App 内使用,不能用来 intent 外部 App,因为设备上不一定有指定的 App。
两者的代码也有区别。
Implicit intent 代码包含 Action,Data URL,以及帮助 Android 判断什么 App 适合处理此 intent 的 Category,components,extras 可选数据,还有最后 resolveActivity 的判断语句来处理设备上没有 App 响应 intent 的情况。例如
// Create the text message with a string
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage);
sendIntent.setType("text/plain");
// Verify that the intent will resolve to an activity
if (sendIntent.resolveActivity(getPackageManager()) != null) {
startActivity(sendIntent);
}
Explicit intent 代码则较简单,说明 context 和 component(通常是 Class 或 Activity)即可,也可以包含可选的 Data URL。例如
// Executed in an Activity, so 'this' is the Context
// The fileUrl is a string URL, such as "http://www.example.com/image.png"
Intent downloadIntent = new Intent(this, DownloadService.class);
downloadIntent.setData(Uri.parse(fileUrl));
startService(downloadIntent);
Explicit intent 和 Implicit intent 代码的共同点是都包含下面两条语句
Intent downloadIntent = new Intent(this, DownloadService.class);
startService(downloadIntent);
即对象的声明(隐形 intent 需要操作字符串,显性 intent 需要提供 context 和 component(实例化))以及 startService 语句。
Event Listener
当用户点击屏幕时,会由 XML 的 View(onClick 是 View 的属性,Button、TextView、ImageView 都属于 View 的子类,所以 TextView 或 ImageView 也可以使用 onClick 属性)执行 onClick 指定函数,这种行为实际上是 Android 为 Event Listeners(事件监听器)做的快捷方式。事实上,用户交互会触发 Event Listeners,随后事件监听器再识别用户交互的 View。因此,要实现不修改 XML 而仅在 Java 端实现捕捉和响应触摸、长按、拖动等用户交互,可以通过直接操作 Event Listeners 做到。例如要监听触摸事件,可以利用 onClickListener 实现,然后与相关的 View 连接(onClick)进行更改。
首先来看 Event Listeners 作为一种 Interface(接口)的定义:接口只有 Abstract methods(抽象 method,指定返回值、名称、输入参数,分号结束,大括号中间的代码实现由开发者自由发挥的 method)。类似的,还有 Abstract Class(抽象类:部分实现的类,包括状态 state,一些完全实现的 method,一些抽象 method)。如下图所示,从 methods 实现程度来看,“完全实现的类”到“抽象类”再到“接口”是一个连续过程,像 TextView 这种完全实现的类,Android 设计成完全的标准化;到 ViewGroups 这种抽象类,包含了 TextView 等标准化的类,也包含 LinearLayout 等子类是抽象的;再到 onClickListener 这种接口,只规范了抽象的 method,具体实现是开放的(回调函数)。
设置 Event Listeners 的具体操作(代替 XML 的 android:onClick 及其 Java 对应的函数)。
- 定义 Event Listeners 并具化抽象 methods;(引用 OnClickLitstener)
In NumbersActivity.java
public class NumbersClickListner implements OnClickLitstener {
@override
Pubilc void onClick(View view) {
Toast.makeText(view.getContext(), “Open the list of numbers”, Toast.LENTH_SHORT).show();
}
}
- 通过构造函数为 Event Listeners 创建一个新的对象实例
In MainActivity.java
NumbersClickListener clickListener = new NumbersClickListener();
- 为 Event Listeners 连接要操作的 View
In MainActivity.java
Button buttonView = findViewById(R.id.button);
buttonView.setOnClickListener(clickListener);
上述代码可以简化为如下。注意括号配对。
In MainActivity.java
Button buttonView = findViewById(R.id.button);
buttonView.setOnClickListener(new NumbersClickListener(){
@Override
pubilc void onClick(View view) {
Toast.makeText(view.getContext(), "Open the list of numbers", Toast.LENTH_SHORT).show();
}
});
这里直接操作 Event Listener 实现了用户点击按钮时出现一条 "Open the list of numbers" 的 Toast 信息,没有通过 Button 的 android:onClick 属性实现。
完成第一节课后,我做了第四个实战项目:NotEasyMusic 音乐应用结构,项目托管在我的 GitHub 上。主要应用了这节课学习的 OnClickListeners 和 Explicit Intent,详细介绍我写在 GitHub 的 README 上。App 目前的效果如下:
目前它只是一个应用结构,没有实现任何音乐播放器的功能,很多地方都是硬编码,但随着课程深入,我会逐步完善这个 App。
这次项目提交上去后,导师依旧给出很棒的建议,例如在局部变量加上 private 防止外部类修改变量,还有编写函数来批量处理多次的 intent 和 onClickListener。我按照导师给出的建议修改了代码,记录都可以在我的 GitHub 上找到。