美文网首页
课程 2: 数据,列表,循环和自定义类

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

作者: HsuJin | 来源:发表于2017-12-25 16:36 被阅读0次

    这节课是 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. 良好的注释是必备的习惯,能让代码更加容易理解和以后的使用。

    相关文章

      网友评论

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

          本文链接:https://www.haomeiwen.com/subject/bbrlgxtx.html