课程 2: 数据,列表,循环和自定义类

这节课是 Android 开发(入门)课程 的第二部分《多屏幕应用》的第二节课,导师依然是 Katherine Kuan 和 Jessica Lin,这节课完成了 Miwok App 的以下几点内容:

  • Learn about how to store a list of words in the app.(Data Structure: Array, ArrayList)
  • Display a list of words.
  • Display a list of English/Miwok word pairs.
  • Add the words from all the remaining categories.

关键词:数组 (Array),列表 (ArrayList),while & for 循环,ListView 与 ArrayAdapter 实现视图回收,自定义类 (ArrayAdapter)

Array(数组)

数组 (Array) 可以保存一系列变量,并使之保持一定的顺序,就像有七个格子的药盒,数组可以理解成长度固定的容器,每一格存储一个值,所有值必须是相同类型的 (Java is a strongly typed language)。
整个数组有一个名字,数组中的每个单元称为其元素 (element),通过其数值位置 (numerical position,即 indices(索引)) 来访问元素。

// 创建数组:数据类型[] 数组名 = new 数据类型[数组长度];
int[] shoeSizeAvailable = new int[3];
// 数组赋值:数组名[索引号] = 值;
// 注意要输入正确的数据类型
shoeSizeAvailable[0] = 5;
// 数组取值:数组名[索引号];
shoeSizeAvailable[0];
// 获取数组的长度
shoeSizeAvailable.length;

在 Android Studio 中,日志 (Log) 按重要/紧急程度分为 verbose(Log.v) → debug(Log.d) → information(Log.i) → warning(Log.w) → error(Log.e),可以通过不同的 Log 语句打印对应等级的日志信息。

ArrayList(列表)

相比长度固定的 Array,ArrayList(列表)可通过添加和移除元素的指令动态调整大小。与数组不同,ArrayList 是一个类,其元素是对象,所以 ArrayList 只能通过 method 来存取对象(若要存储原始类型数据 (Primitive) 要用到对象封装类 (Object Grabbers))以及其他操作。

// 创建 ArrayList:ArrayList<对象数据类型> 名称 = new ArrayList<对象数据类型>();
ArrayList<String> musicLibrary = new ArrayList<String>();
// 添加和移除 ArrayList 的元素:使用 add 和 remove method 实现
musicLibrary.add(“Thriller”);
// ArrayList 名称.add(索引号, 添加的字符串);
musicLibrary.add(0, “Blue Suede Shoes”);
// 移除索引号为 2 的元素后,索引号为 3 及以上的元素补上,ArrrayList 的大小减一
musicLibrary.remove(2);
// ArrayList 取值:使用 get method 实现
musicLibrary.get(0); 
// 获取 ArrayList 的大小:使用 size method 实现
musicLibrary.size(); 

查看 Android 文档,可以知道 ArrayList 可溯源至 List 接口,关系链为 ArrayList ← AbstractList ← List,如下图所示。

因此,ArrayList 是 List 的一个具象类(List 的其它子类有 LinkedList、Stack、Vector 等),ArrayList 可以使用 List 的 method,例如 add(E e)abstract E remove(int index),留意到 add 的输入数据类型是 E 以及 remove 的返回值类型也是 E,这是 Java 的泛型类型 (Generic Type) 参数,常见的有以下几种。

  • E - element
  • K - key
  • N - number
  • T - type
  • V - value
  • S, U, V, etc - 2nd, 3rd, 4th types.

泛型类型参数与抽象类和接口的概念类似,它是参数化的数据类型,在具体实现时需要指定数据类型,例如 add(E e) 表示处理的是数据集合的元素 (element),它可以是任何非原始数据类型 (如 String)。
因此,ArrayList 也是一种泛型类,其元素可以是自定义对象。也就是说,下面 ArrayList 的元素数据类型 String 可以换成任何自定义对象,在 Miwok App 就是 Word 自定义类。

ArrayList<String> musicLibrary = new ArrayList<String>();
ArrayList<Word> words = new ArrayList<Word>();

使用 Java 添加和设置 Views 。

  1. 从 API 26 开始,findViewById 返回值类型为 T (A view with given ID if found, or null otherwise),所以不再需要 cast findViewById 的返回值类型;以前 findViewById 返回值类型直接为 View。

     LinearLayout rootView = (LinearLayout) findViewById(R.id.rootView);
    
  2. 在 XML 定义的 View 无需在 Java 中定义。
    TextView method 的输入参数为 Context,包括应用主题和其他环境信息。
    在从 Context 延伸出 (extends) 的类 (Application, Activity, Service, IntentService classes) ,可以使用 getApplicationContext()getContext()getBaseContext()this 来获取 context。
    若在不含 class extends from Context 的自定义类中,需要传入 Context context 才行

     TextView wordView = new TextView(this);
    
  3. 注意 setText 的输入数据类型

     wordView.setText(“some texts”);
    
  4. 使用 addView method 向 rootView 添加一个 View

     rootView.addView(wordView);
    
while & for 循环语句
  1. while 循环语句
Setup counter variable;
while(Condition) {
    Instruction;
    Update counter variable;
}

对于 while 循环语句,在设置计数器变量后,进入 while 循环;首先判断 Condition 是否为真,若真则进入循环执行 Instruction,记得更新计时器变量;执行完后再次判断 Condition,若假则跳出循环。Update counter variable 的简写语句有

index++;        // index = index + 1;
index--;        // index = index - 1;
index += 3;     // index = index + 3;
  1. for 循环语句
for(Setup counter variable; Condition; Update counter variable;) {Instruction;}

对于 for 循环语句,工作流程与 while 循环相同,不过它将三处代码集合到一个小括号内。
for(String variable: arrays) 专用于遍历数据的所有元素。

ListView 与 ArrayAdapter 实现视图回收

由于内存是非常宝贵的资源,所以 App 要有有效的内存策略:视图回收,即重复使用屏幕上不在可见的视图(以单行为单位,包括 ViewGroups,例如一个 Horizontal 的 LinearLayout),即无需重新创建视图,直接改变 Views 的内容,如 TextView 的 Text,ImageView 的 Image。
这里有一个 Scrap Pile(不可见的视图的存放区)的概念,放入 Scrap Pile 的视图称为 Scrap View,这些视图在修改数据后,会作为新出现的视图显示在屏幕上。

ListView、GridView、RecycleView 等视图都可以与 ArrayAdapter 实现视图回收,这里介绍 ListView 与 ArrayAdapter 的例子。
ListView 由 ArrayAdapter 提供支持 (powered by),没有 ArrayAdapter 的话 ListView 只是一个空容器,ArrayAdapter 会决定在屏幕上显示的数据集。

具体的工作流程如下。

  1. ListView 向 ArrayAdapter 询问 Array 有几个元素,ArrayAdapter 会查询 (getView);

  2. ListView 对 ArrayAdapter 发送当前 Array 的索引位置,ArrayAdapter 查看 Array 的数据,并向 ListView 说明如何显示列表;
    当屏幕上显示完全后,ListView 停止向 ArrayAdapter 寻求更多的列表项,此时显示在屏幕上的视图才会创建;

  3. 用户划动屏幕,一些视图将不再出现,这些 Scrap Views 会放到 Scrap Pile 中,需要显示新的列表项时 Scrap Views 会返回到 ArrayAdapter 中,此时 ListView 会请求要显示位置的视图以及之前显示过的视图(在 Scrap Pile 中的 Reusable View),ArrayAdapter 就把数据放入显示过的视图中,并把重新使用的视图放到新显示的视图中。

这就实现了整个视图回收的过程。

目前为止,可以把 ListView 和 ArrayAdapter 分成 User Interface 和 Data Model 两部分来看,所以存在同一个 ArrayAdapter 关联不同的 ListView 或 GridView 或 Spinner 仍可工作的情况,这就是适配器模式。

下面来看 ListView 与 ArrayAdapter 的代码实例。

ListView

<ListView xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/list"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="vertical" />
  1. 列表方向由 android:orientation 设置,下划线样式由 android:dividerandroid:dividerHeight 设置。注意如果设置了 android:divider(颜色),那也要同时设置 android:dividerHeight(宽度),否则下划线消失。
  2. 把 ListView 添加到 XML 时,Android Studio 预览会出现列表内容,但实际上 App 中不存在内容。

ArrayAdapter

ArrayAdapter<String> itemsAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, words);
  1. 创建 ArrayAdapter,String 为元素的数据类型;
  2. ArrayAdapter 的构造函数有三个输入参数,Context、Resource(Layout)、List<T>(对象列表);
  • this ← Context
  • android.R.layout.simple_list_item_1 是 Android 预定义的一个 XML,是一个 TextView;如果要显示更多内容,要将 Resource 指定到自定义的一个 Layout
  • List<T> object 需要输入列表对象,它是 ArrayAdapter 的数据来源
  1. ArrayAdapter<T> 也是泛型类,其元素不仅可以是 String,也可以是自定义数据类型对象
ListView listView = (ListView) findViewById(R.id.list);
  1. 找到 ListView 的视图层级。
listView.setAdapter(itemsAdapter);
  1. 连接 ListView 和 ArrayAdapter;
  2. setAdapter 是 ListAdapter 的 method,通过 Android 文档查得关系链,ArrayAdapter(Concrete Class) ← BaseAdapter(Abstract Class) ← ListAdapter(Interface)
自定义对象(Word)和自定义类(WordAdapter)

正如前面说到的,ArrayAdapter<T> 是泛型类,其元素可以是自定义数据类型对象,所以针对 Miwok App 要显示一组两个单词的需求,我们要自定义一个对象输入 ArrayAdapter。自定义对象有 state 和 method,所有这些结合在一起叫作封装 (Encapsulation),外部可以调用内部 method,但不关心内部的工作原理。

在包名 (com.example.android.miwok)右键选择 new → Java Class,输入类名,点击完成即可新建一个 Java Class 文件。自定义类 Word 的代码如下。

public class Word {
   // 变量要声明为 private
   private String mDefaultTranslation;
   private String mMiwokTranslation;

   // 构造函数:名称必须与类名完全一致(包括大小写),无返回值(但需要标 void)
   // 访问修饰符为 public 说明外部类可访问
   public Word(String defaultTranslation, String miwokTranslation) {
       mDefaultTranslation = defaultTranslation;
       mMiwokTranslation = miwokTranslation;
   }

   // getter methods,声明为 public
   public String getDefaultTranslation() {
       return mDefaultTranslation;
   }
   public String getMiwokTranslation() {
       return mMiwokTranslation;
   }

   // 一般要有 setter methods
}

在完成自定义对象 Word 后,先输入到 ArrayList 中,代码如下。

ArrayList<Word> words = new ArrayList<>();
words.add(new Word("one", "lutti"));
words.add(new Word("two", "otiiko"));
words.add(new Word("three", "tolookosu"));

完成这个步骤,还不能直接将 words 传入 ArrayAdapter,因为前面说到,ArrayAdapter 的构造函数有三个输入参数,第二个参数为资源,默认为一个 TextView (simple_list_item_1.xml 就是一个 TextView),如果要显示多个 Views 就要 override gerView(),所以要创建一个 ArrayAdapter 的子类 WordAdapter,代码如下。

// 类名添加 extends ArrayAdapter<Word> 表示 WordAdapter 继承 ArrayAdapter 的行为
public class WordAdapter extends ArrayAdapter<Word> {

   /**
    * This is our own custom constructor (it doesn't mirror a superclass constructor).
    * The context is used to inflate the layout file, and the list is the data we want
    * to populate into the lists.
    *
    * @param context The current context. Used to inflate the layout file.
    * @param words   A List of Word objects to display in a list
    */
   public WordAdapter(Context context, ArrayList<Word> words) {
       // Here, we initialize the ArrayAdapter's internal storage for the context and the list.
       // the second argument is used when the ArrayAdapter is populating a single TextView.
       // Because this is a custom adapter for two TextViews, the adapter is not
       // going to use this second argument, so it can be any value. Here, we used 0.
       super(context, 0, words);
   }

   // 选择菜单 Code → Override Methods 或快捷键 cmd+O 来快速生成一个override method
   @Override
   public View getView(int position, View convertView, ViewGroup parent) {
       // Check if the existing view is being reused, otherwise inflate the view
       View listItemView = convertView;
       if (listItemView == null) {
           listItemView = LayoutInflater.from(getContext()).inflate(
                   R.layout.list_item, parent, false);
       }

       // Get the {@link Word} object located at this position in the list
       Word currentWord = getItem(position);

       // Find the TextView in the list_item.xml layout with the ID version_name
       TextView miwokTextView = listItemView.findViewById(R.id.miwok_text_view);
       // Get the version name from the current Word object and
       // set this text on the name TextView
       miwokTextView.setText(currentWord.getMiwokTranslation());

       // Find the TextView in the list_item.xml layout with the ID version_number
       TextView defaultTextView = listItemView.findViewById(R.id.default_text_view);
       // Get the version number from the current Word object and
       // set this text on the number TextView
       defaultTextView.setText(currentWord.getDefaultTranslation());

       // Return the whole list item layout (containing 2 TextViews)
       // so that it can be shown in the ListView
       return listItemView;
   }
}

Tips
1. 对于 Android 的命名空间,除了 AndroidNS 外,还有 toolsNS 提供了 Designtime Layout Attributes ,即在设计时辅助显示,但在实际运行 (Runtime) 时忽略的属性。
2. 在 GitHub 上按 T 键可以激活 file finder 功能,直接输入关键字即可查找文件。
3. 留意 GitHub README.md 里面的 Licenses 内容,查看该项目是否允许修改和再发布。


完成第二节课后,我做了第五个实战项目:ReportCard 成绩单,项目托管在我的 GitHub 上,主要应用了这节课学习的自定义 Java Class,详细介绍我写在 GitHub 的 README 上。App 的效果如下:

这只是 Demo App,没有提供输入成绩的接口,但总成绩是自动计算的。主要知识点在于自定义了一个 Java 类 ReportCard,有几个点可分享。

  1. 将 ReportCard 自定义类的域设置为 public,使其可外部访问;
  2. 内部变量常以 m 开头,如 mCategorymGrade,method 名及其形参没必要在名字前加 m
  3. override toString method 来自定义 return 值;同时将数据以可读的字符串形式显示出来,方便检查和调试;
  4. 在设置分数前先用 if/else 语句检查,是一个很好的编程习惯;
  5. 良好的注释是必备的习惯,能让代码更加容易理解和以后的使用。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,454评论 25 707
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,357评论 0 17
  • 本人初学Android,最近做了一个实现安卓简单音乐播放功能的播放器,收获不少,于是便记录下来自己的思路与知识总结...
    落日柳风阅读 19,078评论 2 41
  • 最近国家标准化管理委员会出台的《首饰贵金属纯度的规定及命名方法》在2016年5月4日正式开始实施,是对《首饰贵金属...
    第四波阅读 415评论 0 1
  • 5月17日,利用休假日参加了户外组织的白鹿仓景区一日游。 白鹿仓景区全称白鹿原·白鹿仓文化旅游景区,是...
    心心向荣xr阅读 2,471评论 0 1