课程实践 2: 制作 Court Counter App

这是 Android 开发(入门)课程 的第一部分《布局和交互》的第二次课程实践,导师是 Lyla Fujiwara,主要内容是变量练习和 Court Counter App。

变量练习

这节课通过代码找茬来练习变量的使用,可总结出两种类型的错误:编译期错误和逻辑错误。

编译期错误
Android Studio 能够识别并字体标红,右侧红色方块指示错误位置,在声明和初始化变量时常犯。

声明变量可理解为创建一个新变量,如

int total;

初始化变量则为新变量设定一个初始值,如

total = 10;

通常习惯将声明和初始化变量写在一起,如

int total = 10;

这两项操作有严格的格式规范:

数据类型 变量名 = 初始值;
  1. 数据类型应与系统设定完全相同,如整型 int 不能是 Integer、integer 和 INT,字符串 String 首字母大写。数据类型在 Android Studio 中默认为蓝色。

  2. 数据类型与变量名之间的空格不能省略,也不允许其他任何字符,如 _ 或 - 。

  3. 变量名应遵循命名规则,可 Google 搜索 "variable names java" 找到 Oracle 的说明文档 。通常变量名不能太长也不能短到一两个字母;若是一个单词则全小写;若是多个单词则用小驼峰命名法。变量名不能与数据类型相同,也不能有符号和空格。

  4. 初始值要与数据类型匹配,如字符串的值应在双引号内,不允许出现

     int total = “10”;
    
  5. 分号结束。

逻辑错误
非 XML 或 Java 语法错误,而是代码功能无法实现的错误,所以 Android Studio 无法识别。可关注以下几点:

  1. 运算符优先级,可 Google 搜索 "arithmetic operators java" 找到 Oracle 的说明文档 。例如乘除的优先级比加减的高,逻辑与的比逻辑或的高。若无法确定运算符优先级时用小括号分隔,如

     // 最先做加法,然后乘法,最后减法
     total = 1 * ( 2 + 3 ) - 5; 
    
  2. 变量仅在赋值时改变值,运算不会。

  3. 局部变量在 method 内声明,只在 method 内部有效,在 Android Studio 中为黑色。
    全局变量在 method 外声明,在 method 之间有效,可以保存数据,在 Android Studio 中为紫色。
    若变量不跨 method 使用时用局部变量即可。

  4. 代码按从上到下的顺序运行,先前执行过的代码不会受后面的影响。

Court Counter App

首先构建布局,按步骤进行:分解 Views→画 Views 层级图→写 XML 草稿→代码实现。

  1. 分解 Views。下图为应用最终布局图。

这里用到了嵌套ViewGroups,有两对水平对称的 TextView 和三个 Button,每对垂直排列,通过 vertical 的 LinearLayout 实现;中间有一个 View 呈垂直线条状,三者用 horizontal 的 LinearLayout 能实现;底部居中一个 Button,因此根 Views 应为 RelativeLayout。

  1. 画 Views 层级图,即树状图
  1. 写XML草稿,清晰条理
<RelativeLayout...>
    <LinearLayout...>
        <LinearLayout...>
            <TextView... />
            <TextView... />
            <Button.../>
            <Button.../>
            <Button.../>
        </LinearLayout>
        <View... />
        <LinearLayout...>
            <TextView... />
            <TextView... />
            <Button... />
            <Button... />
            <Button.../>
        </LinearLayout>
    </LinearLayout>
    <Button... />
</RelativeLayout>
  1. 代码实现

In activity_main.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">

   <LinearLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="horizontal"
       tools:context="com.example.android.courtcouter.MainActivity">

       <LinearLayout
           android:layout_width="0dp"
           android:layout_height="wrap_content"
           android:layout_weight="1"
           android:orientation="vertical">

           <TextView
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:fontFamily="sans-serif-medium"
               android:gravity="center_horizontal"
               android:padding="16dp"
               android:text="Team A"
               android:textColor="#616161"
               android:textSize="14sp" />

           <TextView
               android:id="@+id/team_a_score"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:fontFamily="sans-serif-light"
               android:gravity="center_horizontal"
               android:paddingBottom="24dp"
               android:text="0"
               android:textColor="#000000"
               android:textSize="56sp" />

           <Button
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:layout_marginBottom="8dp"
               android:layout_marginLeft="24dp"
               android:layout_marginRight="24dp"
               android:onClick="addThreeForTeamA"
               android:text="+3 Points" />

           <Button
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:layout_marginBottom="8dp"
               android:layout_marginLeft="24dp"
               android:layout_marginRight="24dp"
               android:onClick="addTwoForTeamA"
               android:text="+2 Points" />

           <Button
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:layout_marginBottom="8dp"
               android:layout_marginLeft="24dp"
               android:layout_marginRight="24dp"
               android:onClick="addOneForTeamA"
               android:text="Free Throw" />
       </LinearLayout>

       <View
           android:layout_width="1dp"
           android:layout_height="match_parent"
           android:layout_marginTop="16dp"
           android:background="@android:color/darker_gray" />

       <LinearLayout
           android:layout_width="0dp"
           android:layout_height="wrap_content"
           android:layout_weight="1"
           android:orientation="vertical">

           <TextView
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:fontFamily="sans-serif-medium"
               android:gravity="center_horizontal"
               android:padding="16dp"
               android:text="Team B"
               android:textColor="#616161"
               android:textSize="14sp" />

           <TextView
               android:id="@+id/team_b_score"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:fontFamily="sans-serif-light"
               android:gravity="center_horizontal"
               android:paddingBottom="24dp"
               android:text="0"
               android:textColor="#000000"
               android:textSize="56sp" />

           <Button
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:layout_marginBottom="8dp"
               android:layout_marginLeft="24dp"
               android:layout_marginRight="24dp"
               android:onClick="addThreeForTeamB"
               android:text="+3 Points" />

           <Button
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:layout_marginBottom="8dp"
               android:layout_marginLeft="24dp"
               android:layout_marginRight="24dp"
               android:onClick="addTwoForTeamB"
               android:text="+2 Points" />

           <Button
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:layout_marginBottom="8dp"
               android:layout_marginLeft="24dp"
               android:layout_marginRight="24dp"
               android:onClick="addOneForTeamB"
               android:text="Free Throw" />
       </LinearLayout>
   </LinearLayout>

   <Button
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_alignParentBottom="true"
       android:layout_centerHorizontal="true"
       android:layout_marginBottom="8dp"
       android:layout_marginLeft="22dp"
       android:layout_marginRight="22dp"
       android:onClick="resetScore"
       android:text="reset" />
</RelativeLayout>
  1. 写完代码后可在 activity_main.xml 文件的 Design 标签下的 component Tree 验证嵌套 ViewGroups 层次是否正确。
  2. TextView 中的文字使用了 gravity 属性实现居中对齐,这种方式使得在 LinearLayout 中实现类似 RelativeLayout 中的对齐布局。TextView 的边界较大时效果更明显。
  3. 两个第二层 LinearLayout 使用了 layout_weight 属性实现水平平分宽度,注意此时双方的初始宽度应为 0dp,否则不为零的一方将多占一部分宽度。
  4. 中间的垂直线条可用宽度为 1dp 的 Views 来实现。
  5. 注意内边距 padding 与外边距 margin 的区别。padding 会使 Views 内的内容距离指定位置有间隔,而 margin 会使子 Views 距离父 Views 中的指定位置有间隔,不会改变子 Views。

styles.xml 文件能控制 App 的基本样式,统一修改布局。文件在左侧 Project 标签 Android 视图中 app→res→values 路径下。以下代码将 Court Counter App 的主色调改为橙色。

<resources>

   <!-- Base application theme. -->
   <style name="AppTheme" parent="Theme.AppCompat.Light">
       <!-- Customize your theme here. -->
       <item name="colorPrimary">#FF9800</item>
       <item name="android:colorButtonNormal">#FF9800</item>
   </style>

</resources>

最后通过 Button 的 android:onClick 属性连接 XML 与 Java,在 Java 中通过 method 实现功能。

In MainActivity.java

package com.example.android.courtcouter;

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

public class MainActivity extends AppCompatActivity {


   int scoreTeamA = 0, scoreTeamB = 0;


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

   /**
    * Increase the score for Team A by 3 points.
    */
   public void addThreeForTeamA(View view) {
       scoreTeamA = scoreTeamA + 3;
       displayForTeamA(scoreTeamA);
   }

   /**
    * Increase the score for Team A by 2 points.
    */
   public void addTwoForTeamA(View view) {
       scoreTeamA = scoreTeamA + 2;
       displayForTeamA(scoreTeamA);
   }

   /**
    * Increase the score for Team A by 1 points.
    */
   public void addOneForTeamA(View view) {
       scoreTeamA = scoreTeamA + 1;
       displayForTeamA(scoreTeamA);
   }

   /**
    * Increase the score for Team B by 3 points.
    */
   public void addThreeForTeamB(View view) {
       scoreTeamB = scoreTeamB + 3;
       displayForTeamB(scoreTeamB);
   }

   /**
    * Increase the score for Team B by 2 points.
    */
   public void addTwoForTeamB(View view) {
       scoreTeamB = scoreTeamB + 2;
       displayForTeamB(scoreTeamB);
   }

   /**
    * Increase the score for Team B by 1 points.
    */
   public void addOneForTeamB(View view) {
       scoreTeamB = scoreTeamB + 1;
       displayForTeamB(scoreTeamB);
   }

   /**
    * Increase the score for Team B by 1 points.
    */
   public void resetScore(View view) {
       scoreTeamA = 0;
       scoreTeamB = 0;
       displayForTeamA(0);
       displayForTeamB(0);
   }

   /**
    * Displays the given score for Team A.
    */
   public void displayForTeamA(int score) {
       TextView scoreView = (TextView) findViewById(R.id.team_a_score);
       scoreView.setText(String.valueOf(score));
   }

   /**
    * Displays the given score for Team B.
    */
   public void displayForTeamB(int score) {
       TextView scoreView = (TextView) findViewById(R.id.team_b_score);
       scoreView.setText(String.valueOf(score));
   }
}
  1. 用全局变量保存两队的总分。
  2. 打开 "Add unambiguous imports on the fly" 功能,在发现未知量时立即添加清晰的 imports,通常导入 Java 的库或包。
  3. 在 Android Studio 中变量名默认为紫色。
  4. 养成写注释的习惯。

课程至此,我做了第二个实战项目——计分器应用。这同样是个简单练习,在 Court Counter App 的基础上稍作修改,达到如下图的效果。

这次项目提交上去由导师审阅后,给出了关于 App 资源文件相关的优化建议,如

  1. 将 sp & dp 等尺寸相关的声明放入 dimens.xml 内,而不是将 Views 的高度和宽度,以及字体的大小硬编码 (hard coding);
  2. 将字符串变量放入了 strings.xml 内,方便应用内多处引用,在翻译应用时也可以大大减少工作量。
  3. 将 color 颜色值放入 colors.xml 内,统一管理颜色资源,而不是对颜色硬编码;
  4. 将样式重复的 TextView、Button、LinearLayout 在 styles.xml 封装调用,使 XML 代码简洁易读。

对 Android App 资源文件的介绍会在以后的课程中出现,在此先有个概念,不多作解释。

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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,139评论 25 707
  • afinalAfinal是一个android的ioc,orm框架 https://github.com/yangf...
    passiontim阅读 15,366评论 2 44
  • 你总说毕业遥遥无期,转眼就各奔东西。我的那些年就像这句老狼的歌词。有些人,一旦错过了,就变成了陌路。匆匆那年里,陈...
    可忆阅读 305评论 0 0
  • 年初,沫沫的额头长出了小痘痘,起初以为是吃什么过敏,后来越来越多,发现是青春痘;某天,带沫沫去洗澡,发现乳...
    苏日娜_ab97阅读 1,314评论 10 12
  • 春寒料峭中,我走进河北从南至北一个又一个村庄。到处尘土飞扬,满地枯枝败叶。丝毫不见春意盎然。越往北,到沽源,越荒凉...
    白龙珠阅读 290评论 0 1