课程 2A: 制作一款交互性应用

这节课是 Android 开发(入门)课程 的第一部分《布局和交互》的第三节课,导师是 Katherine Kuan 和 Kunal Chawla,主要内容是 Android Studio 使用技巧、初识 Java 和 Just Java App。

关键词:Button,android:onClick,Java,Activity,变量 (Variables) 及其作用域,Reformat Code & Rearrange Code

经过前两节课以及一次课程实践,可以知道 Udacity 的纳米学位课程不会像大学的那样,着重要求掌握一门语言的特定语法和概念,Udacity 更倾向于从实用的角度出发,从实际的操作和代码中介绍知识点。更关键的是,Udacity 提供了积极的学习方法和资源,无论是提倡从 Google 搜索或在 stack overflow 等社区寻求帮助,还是建议养成阅读技术文档和技术博客的习惯,都比捧着一本教材死磕来的强得多。同时这也消除了初学者担心培训教学不系统的问题,因为一门语言永远都在更新发展,永远都不可能完全掌握,拥有一套高效有用的方法才是王道。
因此,接下来的学习笔记,除了我提炼出来的知识点外,都会结合实际的代码进行。

Android Studio 使用技巧
  1. 右侧实时显示效果预览的 Preview 标签有问题时,临时解决办法:降低 API 版本,按钮为标签内右上角的绿色 Android 小人。

  2. 每完成里程碑的代码都运行到手机进行验证,勾选设备选择界面中的 "Use same device for future launches" 可以对同一设备免去设备选择界面的操作(在新版 Android Studio 中去除了此选项)。

  3. 布局 (Layout) 代码在默认名 activity_main.xml 文件中编辑,文件在左侧 Project 标签 Android 视图中 app→res→layout 路径下。
    活动 (Activity) 代码在默认名 MainActivity.java 文件中编辑,文件在左侧 Project 标签 Android 视图中 app→java→com.example.hsujin.justjava(安装包名)路径下。

  4. Activity 代码专注于一类用户可操作的事项,包括交互和布局。例如 Gmail 的 Conversation List Activity 专注于查阅邮件的对话清单活动,Compose Activity 则专注于创建新邮件的写作活动。

  5. 编译期错误 (compile time error):能够识别并字体标红,右侧红色方块指示错误位置。
    运行期错误 (run time error):App 运行时发生的错误,错误信息记录在 Android 输出日志 (logcat) 中,在下侧 Android Monitor→logcat 标签中选择 "Show only selected application" 可找到红色错误信息,例如课程 2A 展示的如下错误信息。

04-07 21:38:11.792 32516-32516/com.example.android.justjava E/AndroidRuntime: FATAL EXCEPTION: main
                                                                             Process: com.example.android.justjava, PID: 32516
                                                                             java.lang.IllegalStateException: Could not find method submitOrder(View) in a parent or ancestor Context for android:onClick attribute defined on view class android.support.v7.widget.AppCompatButton with id 'button'
                                                                                 at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.resolveMethod(AppCompatViewInflater.java:325)
                                                                                 at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:284)
                                                                                 at android.view.View.performClick(View.java:4781)
                                                                                 at android.view.View$PerformClick.run(View.java:19874)
                                                                                 at android.os.Handler.handleCallback(Handler.java:739)
                                                                                 at android.os.Handler.dispatchMessage(Handler.java:95)
                                                                                 at android.os.Looper.loop(Looper.java:135)
                                                                                 at android.app.ActivityThread.main(ActivityThread.java:5254)
                                                                                 at java.lang.reflect.Method.invoke(Native Method)
                                                                                 at java.lang.reflect.Method.invoke(Method.java:372)
                                                                                 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:904)
                                                                                 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:699)

阅读这段堆栈信息。

# 标识了是哪个 App 的错误信息
Process: com.example.android.justjava, PID: 32516
# 无需全部理解,寻找关键词,定位问题点,再重点排查代码
# 此处的关键词为button、android:onClick、submitOrder
java.lang.IllegalStateException: Could not find method submitOrder(View) in a parent or ancestor Context for android:onClick attribute defined on view class android.support.v7.widget.AppCompatButton with id 'button'
# 随后的信息进一步说明错误
  1. 定点调试,一行一行地执行代码。如下图所示。

(1)在编辑文件时,点击行数右边的空白区域,可添加任意数量的红色断点 (breakpoint);
(2)打开下侧 Debug→Debugger 标签,关注三个地方。

  • 左侧绿色继续运行按钮可在代码遇到断点暂停运行时恢复;暂停按钮可在代码运行时暂停;红色停止按钮可用来停止调试。

  • 上侧三类带箭头的按钮:Step Over (F8) 运行到下一句代码;Step Into (F7) 跳进这一句代码内部;Step Out (Shift+F8) 跳出这段代码。

  • 变量 (Variables):调试对象,通过控制代码的运行来观察变量的变化。

  1. 在设置 (Android Studio→Preferences) 中搜索 Auto Import 找到并勾选 "Add unambiguous imports on the fly" 能在发现未知量时立即添加清晰的 imports,通常是导入 Java 的库或包,例如课程 2A 中需要 import(导入)Java 提供的统一数字格式的包。

  2. code→Reformat Code: 格式化代码,将代码对齐、换行以提高可读性,而不是硬盘等存储设备的数据擦出概念。
    code→Rearrange Code: 按 Android 代码规范的风格调整代码的顺序。

这两项操作都不会处理空行,不选中代码时作用对象为当前文件的全部代码。

  1. 善用 Android Studio 的代码自动完成功能,出现候选项时可以上下键选择,回车键确认。
初识 Java
  1. 在写 Java 代码之前,可用伪代码 (Pseudo code) 这种无格式要求的解释代码的易懂语言来整理思路,清晰条理。

  2. Java 注释:单行//...;多行/*...*/。养成写注释的习惯,帮助他人和自己日后理解代码。另外修改代码时使用注释而不是直接删除,在 Android Studio 自动保存的机制下,避免了多次撤销或重做产生的混乱。

  3. 单句必须以分号 ; 结束;多条代码用大括号 {...} 包含起来。

  4. 方法 (method) 起止于 {...},可包含多行代码,按顺序执行,操作应先声明后调用。method 可认为是多条代码的集合,方便在其他场合统一引用。

  5. 变量 (Variables):当多处要用到一个经常变化的值时,使用变量可通过仅改变变量值就实现其他所有值的更新,无需修改每一条代码。变量增加代码的鲁棒性 (Robust)。与 method 相同,变量也应先声明后引用。

变量声明

首先变量声明的格式为

变量类型 变量名 = 初始值;

(1)变量类型:定义变量的数据类型,如 int (integer) 定义变量为整数。
(2)变量名:命名规则可 Google 搜索 "variable names java" 找到 Oracle 的说明文档 。通常变量名不能太长也不能短到一两个字母。若是一个单词则全小写;若是多个单词则用小驼峰命名法。可见变量名区分大小写。在 Android Studio 中声明却未引用的变量名显示为灰色,method 名也同样适用。
(3)=: 赋值符号,而不是等号。这里需要养成右手边阅读习惯,即把右边的值赋予左边的变量。另外,赋值符号两边各有一个空格,这种编程风格可提高代码的可读性。
(4)初始值:赋予变量的数值,通常是字面值 (Literal),即具体给出的一个数。
(5)单句代码以分号结束。

变量赋值

声明变量后,通常会对其进行赋值,例如课程 2A 中展示的

quantity = quantity + 1;

(1)声明变量时才需要指定数据类型,赋值不用。
(2)与变量声明相同,这里也要养成右手边阅读习惯,即把右边的值赋予左边的变量。
(3)Java 可输入算式,支持的运算符可 Google 搜索 "arithmetic operators java" 找到 Oracle 的说明文档 。需要注意的是,作为函数真实值的算式,不会对变量 quantity 赋值,如执行完 display(quantity - 1); 这条代码后 quantity 的值不会改变。
(4)同样地,运算符两边也各有一个空格,可提高代码的可读性。
(5)单句代码以分号结束。

变量作用域

根据变量作用域的分类,变量可分为全局变量和局部变量。变量作用域 (Variable Scope) 可简单地理解为变量所在大括号的作用范围。也就是说,

(1)全局变量在类 (class) 的大括号内,而 class 包括许多 methods,所以全局变量在 methods 之间有效,可以保存数据,在 Android Studio 中为紫色。
(2)局部变量在 method 的大括号内,就只在 method 内部有效,在 Android Studio 中为黑色。

全局变量与局部变量可重名,且互不影响。在 method 内局部变量的优先级高于全局变量,即名称相同的两种变量在 method 内只有局部变量有效,不影响全局变量。

Just Java App

接下来的课程通过制作一个咖啡订购应用 Just Java 来学习 Android。 创建一个新 App 工程,App 名称为 Just Java,域名 android.example.com 以及 API 15: Android 4.0.3 (IceCreamSandwich)。

Fun Facts
Java 这个词来源于印度尼西亚的爪哇岛,因盛产咖啡闻名,所以 Java 与 咖啡 有着千丝万缕的联系,Java 的 logo 就是一杯冒着热气的咖啡。
课程 2A 制作的咖啡订购应用名为 Just Java ,算是 Java 语言和咖啡的双关语吧。

这节课要通过 Java 的变量和 method,用按钮 (Button) 来实现 Just Java App 的交互。

In activity_main.xml

<LinearLayout 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:orientation="vertical"
   android:paddingBottom="16dp"
   android:paddingLeft="16dp"
   android:paddingRight="16dp"
   android:paddingTop="16dp"
   tools:context="com.example.android.justjava.MainActivity">

   <TextView
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginBottom="16dp"
       android:text="quantity"
       android:textAllCaps="true" />

   <Button
       android:layout_width="48dp"
       android:layout_height="48dp"
       android:onClick="increment"
       android:text="+" />

   <TextView
       android:id="@+id/quantity_text_view"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="0"
       android:textColor="#000000"
       android:textSize="16sp" />

   <Button
       android:layout_width="48dp"
       android:layout_height="48dp"
       android:onClick="decrement"
       android:text="-" />

   <TextView
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginBottom="16dp"
       android:layout_marginTop="16dp"
       android:text="price"
       android:textAllCaps="true" />

   <TextView
       android:id="@+id/price_text_view"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="$0"
       android:textColor="#000000"
       android:textSize="16sp" />

   <Button
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginTop="16dp"
       android:onClick="submitOrder"
       android:text="order" />
</LinearLayout>

按照选择、放置、设置 Views 的三步布局 (Layout) 构建方法,可见此处有四个 TextView 和三个 Button。

  1. 布局选择了垂直分布 (vertical) 的 LinearLayout;
  2. TextView 的设置有 id、全部字母大写、字体黑色、padding/margin 等;
  3. Button 第一次使用,"+" 和 "-" 按钮的大小为 48dp x 48dp,内部文字默认全部大写,三个 Button 都通过 android:onClick 设置了按钮按下时的调用 method,分别名为 increment、decrement、submitOrder。

In MainActivity.java

package com.example.android.justjava;

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;

import java.text.NumberFormat;

/**
* This app displays an order form to order coffee.
*/
public class MainActivity extends ActionBarActivity {

   int quantity = 0;

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

   /**
    * This method is called when the order button is clicked.
    */
   public void submitOrder(View view) {
       displayPrice(quantity * 5);
   }

   /**
    * This method is called when the + button is clicked.
    */
   public void increment(View view) {
       quantity = quantity + 1;
       display(quantity);
   }

   /**
    * This method is called when the - button is clicked.
    */
   public void decrement(View view) {
       quantity = quantity - 1;
       display(quantity);
   }

   /**
    * This method displays the given quantity value on the screen.
    */
   private void display(int number) {
       TextView quantityTextView = (TextView) findViewById(
               R.id.quantity_text_view);
       quantityTextView.setText("" + number);
   }

   /**
    * This method displays the given quantity value on the screen.
    */
   private void displayPrice(int number) {
       TextView priceTextView = (TextView) findViewById(R.id.price_text_view);
       priceTextView.setText(NumberFormat.getCurrencyInstance().format(number));
   }
}
  1. 在设置中勾选 "Add unambiguous imports on the fly" 会自动添加代码
    import java.text.NumberFormat;
  2. 声明全局变量
    int quantity = 0;
  3. 变量赋值
    quantity = quantity + 1;
  4. 引用其他 method
    displayPrice(quantity * 5);

目前 Just Java App 实现了增加和减少数量,以及显示价格的功能,如下图。

更多的代码解释会在接下来的课程出现。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,126评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,254评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,445评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,185评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,178评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,970评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,276评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,927评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,400评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,883评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,997评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,646评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,213评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,204评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,423评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,423评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,722评论 2 345

推荐阅读更多精彩内容