这节课是 Android 开发(入门)课程 的第一部分《布局和交互》的最后一节课,导师是 Katherine Kuan 和 Kunal Chawla,主要内容是更多的 XML 布局与 Java 代码,完善 Just Java App。这节课出现了很多第一次应用的 Views,但在掌握了高效的学习方法的情况下,可以体验到快速上手的感觉,下面就带领大家开始。
关键词:CheckBox、ScrollView、EditText、strings.xml、styles.xml,if-else 流控语句、Intent
CheckBox
-
Google 搜索 "checkbox android" 第一个结果是 Android 文档;第二个结果是代码示例,也可以在 Common Android Views Cheat Sheets 中找到。
-
Just Java App 中的 CheckBox 代码如下:
<CheckBox
android:id="@+id/whipped_cream_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:paddingEnd="24dp"
android:paddingStart="24dp"
android:text="@string/whipped_cream"
android:textSize="16sp" />
复制代码
CheckBox 是 TextView 的子 class,所以拥有 TextView 的属性,如 android:text
。其中,
android:paddingEnd="24dp"
android:paddingStart="24dp"
复制代码
将勾选框和文字分开 24dp。paddingStart 应用于从左往右的视图,paddingEnd 应用于从右往左的视图。
-
CheckBox 是一个对象,它有一个布尔 (Boolean) 类型的的变量来保存勾选框的状态,显然只有真 (true) 或假 (false) 两种,也就是说布尔变量只有两个值。Google 搜索 "java data type" 查找 Java 的基本数据类型 (Primitive Data Types) 进一步了解。
-
Just Java App 中的 CheckBox 对象如下:
CheckBox whippedCreamCheckBox = (CheckBox) findViewById(R.id.whipped_cream_checkbox);
boolean hasWhippedCream = whippedCreamCheckBox.isChecked();
//Log.v("MainActivity", "Has whipped cream:" + hasWhippedCream);
复制代码
(1)布尔变量名同样遵循变量命名规则,常以 has 或 is 开头,如 hasWhippedCream。
(2)在调试时将信息打印到 logcat 中,能检查代码的运行情况。格式:
Log.v(“class名”, “字符串”);
复制代码
- 注意打印信息的长度,避免刷屏刷掉错误等重要信息。调试结束后可将此行注释掉。
- 布尔变量与字符串连接会变成字符串 true 或 false,如 "Has whipped cream: " + hasWhippedCream → Has whipped cream: true 或 Has whipped cream: false。
ScrollView
Google 搜索 "how to scroll in android app" 找到 stack overflow 论坛的结果:利用 ScrollView 实现屏幕垂直方向滚动。这对设备横屏状态下非常有用。ScrollView 只能有一个子 View,Just Java App 中的 ScrollView 如下:
<ScrollView 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"
tools:context="com.example.android.justjava.MainActivity">
…
</ScrollView>
复制代码
fill_parent 是 match_parent 的旧版本写法。
EditText
-
Google 搜索 "user input android" 找到 Best Practices for User Input 结果,选择 Keyboard Input 可找到 EditText 的代码示例,也可以在 Common Android Views Cheat Sheets 中找到。
-
EditText 允许用户在 App 中输入文字,Just Java App 中的 EditText 如下:
<EditText
android:id="@+id/name_field"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/name"
android:inputType="textCapWords" />
复制代码
在 Android 文档中查找 EditText 的属性含义。
android:hint
设置在 EditText 无输入时显示的提示文字。android:inputType
设置文字在 EditText 中的显示方式,textCapWords 表示每个单词首字母自动大写,但对 Google 拼音输入法无效。
-
Google 搜索 "how do i get text from edittext field android" 找到 stack overflow 论坛的结果:通过链式调用 EditText 的 methods 来获取 EditText 中的文字。
-
Just Java App 中的 EditText 对象如下:
EditText nameField = (EditText) findViewById(R.id.name_field);
String name = nameField.getText().toString();
//Log.v("MainActivity", "Name: " + name);
复制代码
getText() 的返回值类型为 Editable 对象,进一步调用 toString() 确定返回值为字符串。
strings.xml
- 在 Android Studio 中左侧 Project 标签 Android 视图下 app→res→values→strings.xml 文件应保存 App 的所有字符串资源,所以应将先前硬编码的字符串取到 strings.xml 中。
- Just Java App 中的 strings.xml 代码如下:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Title for the application. [CHAR LIMIT=12] -->
<string name="app_name">Just Java</string>
<!-- Hint text display in the empty field for the user's name [CHAR LIMIT=20] -->
<string name="name">Name</string>
<string name="toppings">Toppings</string>
<string name="whipped_cream">Whipped Cream</string>
<string name="chocolate">Chocolate</string>
<string name="quantity">Quantity</string>
<string name="decrement">-</string>
<string name="initial_quantity_value">1</string>
<string name="increment">+</string>
<string name="order">Order</string>
<!--
Name for the order summary. It will be shown in the format of "Name: Amy" where Amy is the
user's name. [CHAR LIMIT=NONE]
-->
<string name="order_summary_name">Name: <xliff:g id="name" example="Amy">%s</xliff:g></string>
<!--
Whipped cream topping for the order summary. It will be shown in the format of
"Add whipped cream? true" or "Add whipped cream? false". [CHAR LIMIT=NONE]
-->
<string name="order_summary_whipped_cream">Add Whipped Cream? <xliff:g id="addWhippedCream" example="true">%b</xliff:g></string>
<!--
Chocolate topping for the order summary. It will be shown in the format of
"Add chocolate? true" or "Add chocolate? false". [CHAR LIMIT=NONE]
-->
<string name="order_summary_chocolate">Add chocolate? <xliff:g id="addChocolate" example="true">%b</xliff:g></string>
<!--
Quantity of coffee cups for the order summary. It will be shown in the format of
"Quantity: 2", where 2 is the number of cups ordered. [CHAR LIMIT=NONE]
-->
<string name="order_summary_quantity">Quantity: <xliff:g id="quantity" example="2">%d</xliff:g></string>
<!--
Total price for the order summary. It will be shown in the format of
"Total: $10" where $10 is the price. [CHAR LIMIT=NONE]
-->
<string name="order_summary_price">Total: <xliff:g id="price" example="$10">%s</xliff:g></string>
<!-- Thank you message for the order summary. [CHAR LIMIT=NONE] -->
<string name="thank_you">Thank you!</string>
<!--
Subject line for the order summary email. It will be in the format of
"Just Java order for Amy" where Amy is the user's name. [CHAR LIMIT=NONE]
-->
<string name="order_summary_email_subject">Just Java order for <xliff:g id="name" example="Amy">%s</xliff:g></string>
</resources>
复制代码
(1)所有内容在 <resources …>... </resources>
内。
(2)字符串定义格式:
<string name=“字符串名”>字符串</string>
复制代码
(3)这里使用了 xliff 语句在字符串中添加变量,同时作为占位符表示这部分内容无需翻译,如
<!-- 指定 xliff 命名空间的 URL -->
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"
<!-- 在字符串中添加“姓名”字符串变量 -->
<string name="order_summary_name">Name: <xliff:g id="name" example="Amy">%s</xliff:g></string>
<!-- 在字符串中添加布尔类型变量 -->
<string name="order_summary_chocolate">Add chocolate? <xliff:g id="addChocolate" example="true">%b</xliff:g></string>
复制代码
从上面的例子可以看出,xliff 语句起止于 <xliff:g ...>...</xliff:g>
,中间内容依次为
- 变量 ID,如
id="name"
- 变量内容示例,如
example="Amy"
- 变量格式说明符 (Format Specifiers),如 %s 指字符串,%b 指布尔类型,这篇文章列出了更多 Java 格式说明符。
(4)XML注释格式:
<!-- … -->
复制代码
写下具体的注释使代码易理解,对于字符串复用和翻译很有帮助。
- 在 strings.xml 定义所有字符串资源后,可在 XML 和 Java 中调用。
(1)XML 调用字符串,格式:@string/字符串名
<TextView
android:id="@+id/quantity_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:text="@string/initial_quantity_value"
android:textColor="#000000"
android:textSize="16sp" />
复制代码
(2)Java 调用字符串,格式:R.string.字符串名
/**
* Create summary of the order..
*
* @param name of the Customer
* @param price of the order
* @param addWhippedCream is whether or not the user wants whipped cream topping
* @param addChocolate is whether or not the user wants chocolate topping
* @return text summary
*/
private String createOrderSummary(String name, int price, boolean addWhippedCream, boolean addChocolate) {
return getString(R.string.order_summary_name, name) + "\n" +
getString(R.string.order_summary_whipped_cream, addWhippedCream) + "\n" +
getString(R.string.order_summary_chocolate, addChocolate) + "\n" +
getString(R.string.order_summary_quantity, quantity) + "\n" +
getString(R.string.order_summary_price, NumberFormat.getCurrencyInstance().format(price)) + "\n" +
getString(R.string.thank_you);
}
复制代码
- Android App 有默认 (Default) 资源和替代 (Alternate) 资源,使 App 可面对不同的运行环境加载不同的资源,Google 搜索 "localization checklist" 进一步了解。支持多语言是本地化的其中一项工作,Google 搜索 "iso 639-1" 找到 Wikipedia 结果,了解全球语言缩写,如中文为 zh。
- 在 app→res 路径下新建 values-zh 目录并新建 strings.xml,翻译字符串为中文:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Title for the application. [CHAR LIMIT=12] -->
<string name="app_name">Just Java</string>
<!-- Hint text display in the empty field for the user's name [CHAR LIMIT=20] -->
<string name="name">姓名</string>
<string name="toppings">配料</string>
<string name="whipped_cream">奶盖</string>
<string name="chocolate">巧克力</string>
<string name="quantity">数量</string>
<string name="decrement">-</string>
<string name="initial_quantity_value">1</string>
<string name="increment">+</string>
<string name="order">下单</string>
<!--
Name for the order summary. It will be shown in the format of "Name: Amy" where Amy is the
user's name. [CHAR LIMIT=NONE]
-->
<string name="order_summary_name">姓名: <xliff:g id="name" example="Amy">%s</xliff:g></string>
<!--
Whipped cream topping for the order summary. It will be shown in the format of
"Add whipped cream? true" or "Add whipped cream? false". [CHAR LIMIT=NONE]
-->
<string name="order_summary_whipped_cream">需要添加奶盖? <xliff:g id="addWhippedCream" example="true">%b</xliff:g></string>
<!--
Chocolate topping for the order summary. It will be shown in the format of
"Add chocolate? true" or "Add chocolate? false". [CHAR LIMIT=NONE]
-->
<string name="order_summary_chocolate">需要添加巧克力? <xliff:g id="addChocolate" example="true">%b</xliff:g></string>
<!--
Quantity of coffee cups for the order summary. It will be shown in the format of
"Quantity: 2", where 2 is the number of cups ordered. [CHAR LIMIT=NONE]
-->
<string name="order_summary_quantity">数量:<xliff:g id="quantity" example="2">%d</xliff:g></string>
<!--
Total price for the order summary. It will be shown in the format of
"Total: $10" where $10 is the price. [CHAR LIMIT=NONE]
-->
<string name="order_summary_price">价格:<xliff:g id="price" example="$10">%s</xliff:g></string>
<!-- Thank you message for the order summary. [CHAR LIMIT=NONE] -->
<string name="thank_you">谢谢!</string>
<!--
Subject line for the order summary email. It will be in the format of
"Just Java order for Amy" where Amy is the user's name. [CHAR LIMIT=NONE]
-->
<string name="order_summary_email_subject">Just Java 订单 <xliff:g id="name" example="Amy">%s</xliff:g></string>
</resources>
复制代码
在 Android Studio 中左侧 Project 标签 Android 视图下找到 app→res→values→strings.xml 文件,右键菜单中找到并点击 "Open Editor",可打开对照翻译界面,如下图所示。
更多资源本地化的信息可到 Localizing with Resources | Android Developers 网站查看。
styles.xml
- 在 Android Studio 中左侧 Project 标签 Android 视图下 app→res→values→styles.xml 文件可统一定义 App 的主题以及 Views 的样式,减少代码冗余。
- Just Java App 中的 styles.xml 代码如下:
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<!-- Style for a header TextView. -->
<style name="HeaderTextStyle" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">48dp</item>
<item name="android:gravity">center_vertical</item>
<item name="android:textAllCaps">true</item>
<item name="android:textSize">15sp</item>
</style>
</resources>
复制代码
(1)所有内容在 <resources …>... </resources>
内。
(2)同一样式在 <style …>... </style>
内。
(3)属性定义格式:<item name=“属性名”>真实值</item>
- 在 styles.xml 定义样式资源后,可在 XML 中调用:
<TextView
style="@style/HeaderTextStyle"
android:text="@string/toppings" />
<TextView
style="@style/HeaderTextStyle"
android:text="@string/quantity" />
复制代码
两个 TextView 相同处应用同一样式,不同处自行定义属性,减少代码冗余。
- 样式可应用于单个的 View,而主题的 style 是应用于某类活动或整个应用的,例如所有文字的颜色,应用栏或状态栏的颜色。如
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
复制代码
其中 parent="Theme.AppCompat.Light.DarkActionBar"
表示 App 主题通过 Appcompat 兼容使用了 Light.DarkActionBar 的主题。同时 AppTheme 在 AndroidManifest.xml 内引用,对于多屏幕应用,不同主题的 Activity 就在这个文件内指定。
if-else 语句
- 控制代码执行顺序称为控制流 (Control Flow),利用 if-else 语句可以使程序跳过部分代码。
- Just Java App 中的 if-else 语句如下:
/**
* This method is called when the + button is clicked.
*/
public void increment(View view) {
if (quantity == 100) {
// Show an error message as a toast
Toast.makeText(this,
"You cannot have more than 100 coffees!", Toast.LENGTH_SHORT).show();
// Exit this method early because there's nothing left to do
return;
}
quantity += 1;
displayQuantity(quantity);
}
复制代码
(1)当满足 if 小括号内的条件时,执行大括号内的代码,否则执行 else 大括号内的代码。
(2)当 else 无需任何操作时可删去。
(3)利用 return;
提前结束 method。
(4)运算符 +=
可精简代码。
(5)用 toast 告知用户错误信息或提示,以防用户误以为 App 故障或停止运行。Google 搜索 "toast android" 进一步了解。
Intent
- 利用 Android 框架 Intent 请求其它 App 组件完成动作,使 App 能使用其他 App 的功能,避免重复开发,如相机、地图、浏览器、邮件等。Google 搜索 "common intents" 进一步了解。
- Intent 就像传球,无需指定某一个 App 接球,只要设备中有 App 响应,把球接住即可。
- Intent 的内容通常有常规动作 (Action)、动作的数据 (Data),还有 Category、Component、Extra。
- Just Java App 中的 Intent 代码如下:
Intent intent = new Intent(Intent.ACTION_SENDTO);
// only email apps should handle this
intent.setData(Uri.parse("mailto:"));
intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.order_summary_email_subject, name));
intent.putExtra(Intent.EXTRA_TEXT, createOrderSummary(name, price, hasWhippedCream, hasChocolate));
if (intent.resolveActivity(getPackageManager()) != null) {
startActivity(intent);
}
复制代码
(1)Intent的常规动作 (Aciton): ACTION_SENDTO,常量(全大写、值不变)。
(2)Intent 的动作数据 (Data): Uri.parse(“mailto:”),有标准格式,Uniform Resource Identifier。
(3)Intent 的额外数据 (Extra): EXTRA_SUBJECT、EXTRA_TEXT 分别设置邮件主题和内容的文本。
(4)if 语句判断设备中是否有 App 能响应 Intent,防止 App 崩溃。
Intent 的内容会在接下来课程中详细介绍。至此 Just Java App 的开发就告一段落了,现阶段这个咖啡订购 App 实现的功能有
- 输入订购人姓名;
- 选择订购数量,范围为 1~100;
- 选择是否添加奶盖和巧克力;
- 将订购信息(包含价格)发送到邮箱。
第一部分《布局和交互》的课程完成了,同时也迎来了入门课程的第三个实战项目:AUdacityQuiz 小测验应用,它应用了除 Intent 外已学的所有知识点,
以下是在 coding 过程中发现的几个关键点:
- 在 AndroidManifest.xml 内的 MainActivity 添加
android:windowSoftInputMode="stateHidden"
使 App 启动时输入法不会自动弹出。 - 为了防止手机在旋转后 RadioButton 的状态丢失,override 了 onSaveInstanceState 和 onRestoreInstanceState 用于保存 RadioButton 的状态。
// Save the isQuestionTwoRight and isQuestionTwoRight variable state
// When phone rotate to the landscape mode or portrait mode
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
// Save UI state changes to the savedInstanceState.
// This bundle will be passed to onCreate if the process is killed and restarted.
savedInstanceState.putBoolean("isQuestionTwoRight", isQuestionTwoRight);
savedInstanceState.putBoolean("isQuestionThreeRight", isQuestionThreeRight);
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// Restore UI state from the savedInstanceState.
// This bundle has also been passed to onCreate.
isQuestionTwoRight = savedInstanceState.getBoolean("isQuestionTwoRight");
isQuestionThreeRight = savedInstanceState.getBoolean("isQuestionThreeRight");
}
复制代码
- 利用 TextView 属性使两行内容的 Toast 文字居中显示。
Toast toast = Toast.makeText(this, getString(R.string.toast_line_one, result) + "\n" + getString(R.string.toast_line_two, score), Toast.LENGTH_SHORT);
// Center each of the two lines Toast texts
TextView v = toast.getView().findViewById(android.R.id.message);
if (v != null) {
v.setGravity(Gravity.CENTER);
}
toast.show();
复制代码
- 在 ImageView 中通过两条指令使图片完整显示。
android:adjustViewBounds="true"
android:scaleType="centerInside"
复制代码
- 通过 if/else 语句添加了一条错误提示的 Toast。
这次项目导师还给出了一些很棒的建议,例如定义 style 时应该注意的一些问题:
- 所有 style 都应该有一个 parent;
- 给 style 命名时应该遵循 parent 的命名惯性;
- 最好让自定义 style 继承自 AppCompat 而不是系统的 style,以确保能向后兼容性。