ID #58068

使用 Android 和 XML 构建动态用户界面(一)

简介:  有几个网站从事一些非盈利服务,提供一些可轻松设置和使用的表单来进行民意测验和数据收集。本教程介绍一个简单的架构来为 Android 设计类似的应用程序 — 允许非编程员从移动用户收集数据的动态用户界面。在本教程中,您将创建一个拥有服务器端和移动端的示例表单引擎。

开始之前

为更好地利用本文,您应该使用 Android SDK 来构造 Android 应用程序。完成本文之后,您将了解如何使用 HTTP(S) 执行从应用程序到 web 服务器的通信,以及如何使用 DOM 解析器解析 XML。在此过程中,您将创建自定义和动态的用户界面布局、多线程通信、消息处理程序、以及进程对话框。至少,您将了解 AndroidManifest.xml 和服务器端脚本编写。

关于本教程

常用缩略词

  • API:应用程序编程接口
  • DOM:文档对象模型
  • HTML:超文本标记语言
  • HTTP(S):超文本传输协议安全
  • IDE:集成开发环境
  • SAX:XML 的简单 API
  • SDK:软件开发工具包
  • UI:用户界面
  • URL:统一资源定位符
  • XML:可扩展标记语言

本教程介绍一个用于在 Android 设备上进行移动数据收集的动态表单的架构。我将首先介绍一个高级架构,讨论这样一个应用程序在数据收集的大环境中的应用。快速浏览这个已完成的项目,包含每个源文件,对本教程将要介绍的内容有一个大概了解。就像 “厨艺展示” 一样,您使用一些 Java 类从头开始构建这个应用程序,每个 Java 类都被小心引入并关联到应用程序的其他方面,其中最显著是一个数据模型,这个表单引擎就是基于它构造的。最后,您将表单数据保存在服务器上并简单地查看应用程序的服务器端。

数据收集

我们首先简要讨论数据收集,以及如何在使用 Android 移动设备时轻松实现数据收集。

一个 Android 数据收集框架

收集数据是计算机时代之前的任务。计算机已经变成一个日常 “订书钉”,使我们思考、查找和使用信息的方式发生了革命性的变化。市值数百亿美元的公司要归功于他们在存储、检索和管理海量信息中的高效。今天使用的数据库由各种各样架构的系统供给,包括大型机、客户服务器、web 应用程序、以及现在的移动应用程序。

物理存货和周期计数应用程序是移动计算的早期实用应用程序。这些应用程序通常是批量数据收集,其中硬件需要一个扩展插口(docking station)来上传收集的信息。

从出现到现在,移动应用程序市场已经走过了很长一段路程,无线连通性和设备几乎遍布各种文化和市场,渗透到日常生活的每个方面。

尽管数据收集方式可能已经变得更具流动性,但数据收集的核心并没有发生太大变化。用户必须看到一些问题,并且要有一种简单的响应方式。本教程演示如何为 Android 驱动的移动电话构造一个简单的数据收集框架,其中要利用一个 XML 支持的动态元数据结构。

应用程序架构

在深入代码之前,我们先在一个非常高的层面上检查应用程序设置。

Forms Engine 一瞥

我们首先浏览一下这个 Forms Engine 应用程序的各个方面。图 1 描绘这个应用程序与提供各种内容的数据录入表单的一个或多个服务器之间的关系。


图 1. 应用程序架构
应用程序架构图表
 

在 图 1 中,Form 1 提供 Robotics Competition 的注册,Form 2 要求用户提供关于他们的汽车维修习惯的信息。这些表单和 Android 应用程序通信使用 HTTP(S)来:

  • 下载表单数据。
  • 向用户显示表单数据并(可选)收集特定于设备的数据,比如相机图像、声音录制、GPS 定位、或者罗盘读数。
  • 收集用户提供的数据。
  • 将数据提交给适当服务器。

本教程的服务器端实现为一对文件:一个描述表单的 XML 文档和一个负责记录表单提交的 PHP 文档。这个 Android 应用程序是一个用 Java 代码编写的原生应用程序,使用 Android SDK,在 Eclipse 中编码。

表 2 显示完整应用程序的源文件。您可以下载包含所有这些源文件的压缩文件(参见 下载)。本教程将详细介绍每个源文件。


表 2. 必要的应用程序源文件
文件名 注释 XmlGui.java Android Activity 的入口点 XmlGuiForm.java 一个表单的高级数据模型和方法 XmlGuiFormField.java 表示一个表单域,持有一个表单的每个字段的元数据 XmlGuiEditBox.java 实现一个文本框类型用户界面元素 XmlGuiPickOne.java 实现一个下拉列表类型用户界面元素 RunForm.java 使用上述类处理一个表单 main.xml 应用程序用户界面主页 AndroidManifest.xml 此 Android 应用程序的部署描述符 xmlgui1.xml 收集 Robotics 比赛注册信息的样例表单 xmlgui1-post.php 处理表单提交的 PHP 脚本 xmlgui2.xml 汽车维修习惯调查的样例表单

图 2 展示本教程末尾完成的应用程序在 Eclipse 中的项目结构。(查看 图 2 的文本版本。)


图 2. Eclipse 中的项目
Eclipse 中的项目结构的屏幕截图
 

如果您没有一个正在运行的 Android 开发环境,那么现在是安装 Android 工具的绝好时机。要了解如何设置一个 Android 开发环境,请参阅 参考资料 中必要工具的链接,以及一些关于如何为 Android 开发应用程序的入门文章和教程。熟悉 Android 有助于理解本教程。

您已经对架构和应用程序有一个大致了解,现在就开始吧!

项目和数据模型

我们现在已准备好在 Eclipse 中启动这个 Android 项目,创建允许您为这个 Forms Engine 应用程序存储和管理元数据的数据模型和类。

创建项目

创建 Android 应用程序从我们熟悉的地方开始:打开 Eclipse 并选择 File > New,如 图 3 所示。


图 3. 新建一个 Android 应用程序
新建一个 Android 应用程序的屏幕截图
 

这个步骤将启动 Eclipse New project 向导。选择 Android Project(针对 Android 的专业 Java 环境)。确保向项目提供一个有效的标识符(我使用的是 XMLGUI)。为匹配本教程中介绍的解决方案,在 Properties 下面,指定应用程序名为 XML GUI,包名为 com.msi.ibm。选择 Create Activity 复选框并将 Activity name 指定为 XmlGui,如 图 4 所示。


图 4. 设置一个新项目
设置一个新项目的屏幕截图
 

项目创建后,它应该非常类似 图 5 中的图像。(查看 图 5 的文本版本。)


图 5. New project 向导刚刚完成时的 Android 项目
New project 向导刚刚完成时的 Android 项目
 

现在,项目已创建,确保应用程序干净地构建并在 Android Emulator 中运行是一个最佳实践。注意,有时应用程序直到您编辑并保存 Java 源文件时才会构建。这导致 Android SDK 工具自动在 gen 文件夹中生成一些文件。如果 Eclipse 环境中的 Problems 选项卡没有显示任何条目,那么您可以测试这个应用程序。

要测试应用程序,创建一个 Run 配置,如 图 6 所示。在 Android Application 列表中,选择 XmlGui。确保以下值出现:XmlGui 位于 Name 字段中,XMLGUI 位于 Project 字段中,com.msi.ibm.XmlGui 位于 Launch 字段中。单击 Run


图 6. Run 配置设置
Run 配置设置
 

此项目已在 Android 模拟器中创建、配置并正确启动,现在可以为 Android 创建 XML 驱动的数据收集工具了。


数据模型

这个应用程序的具体细节要求它向一个用户显示输入元素,验证数据,然后将数据提交到一个指定服务器。如果这个应用程序只针对新记录设置,那么它什么用处也没有,因为它不支持查询现有记录以便编辑或删除。

要向应用程序提供足够的关于如何显示数据录入表单的指示,需要一个信息集(通常称为元数据)。元数据是关于数据的数据。简言之,这个应用程序必须理解几个数据元素,包括:

  • Form Name — 人们可读的表单名称
  • Form Identifier — 这个元数据收集的惟一标识符
  • Submission URL — 收集到的数据的发送地址
  • 一个或多个字段 — 这些字段可能是文本、数字,或者 “从列表中选择” 类型的字段

几乎所有种类的问题都映射到这三种类型的用户界面元素中的一种。例如,可以实现一个复选框作为一个 Yes 或 No 选择字段。可以实现多重选择(multi-select)作为多选字段。当然,您可以随意扩展本教程中展示的代码。

对于您的应用程序,使用场景如下:您在参加一个活动,其中您可以注册一个或多个项目。您可以填写一张注册表,也可以等到回家后登录该组织的网站进行注册。在本例中,您将假定一位用户将通过在一个 Android 设备上下载一个动态表单,以便在现场从他的手机填写一张简单表单,提供申请者的姓名、性别和年龄。

清单 1 显示了 xmlgui1.xml 的内容,该文件表示一个 Robotics 俱乐部活动的注册表单。


清单 1. xmlgui1.xml

					
<?xml version="1.0" encoding="utf-8"?>
<xmlgui>
<form id="1" name="Robotics Club Registration" 
   submitTo="http://serverurl/xmlgui1-post.php" >
<field name="fname" label="First Name" type="text" required="Y" options=""/>
<field name="lname" label="Last Name" type="text" required="Y" options=""/>
<field name="gender" label="Gender" type="choice" required="Y" options="Male|Female"/>
<field name="age" label="Age on 15 Oct. 2010" type="numeric" required="N" options=""/>
</form>
</xmlgui>

 

注意关于这个 XML 文档的以下几点:

  • 这个 XML 非常容易解析,这要归功于元素属性的广泛使用。之所以使用这种方法,原因是与多个子元素和标记相比,它使提取数据更容易。以这种方式使用属性还能保持较小的下载大小,并有助于保持较低的解析时间。
  • submitTo 属性告知应用程序,数据收集完成后将要发送到的位置。
  • 每个 field 元素同时提供一个字段名和一个标签的属性。尽管这些值是相关的,但您希望每个 name 属性的值都保持惟一,以便接收应用程序能够正确解析并处理它们。您还应该向用户提供一个提示性 label 值,提示用户什么种类的数据将进入一个特定字段。
  • 您可以轻松扩展这个方法以包含每个字段的默认值,一个用于验证的 regex 表达式,或者一个获取关于特殊字段的更多信息的链接。
  • options 字段用作 choice 字段的一个受限列表。

基本了解数据模型后,现在看看负责将这个 XML 数据转化为一个有用应用程序的代码。


表示数据

解析数据是一个非常机械的练习,将在本教程后面展示。在检查解析流程之前,应用程序需要某个位置来在内存中存储和管理这个元数据。出于这个目的,您拥有两个 Java 类,一个用于表单,一个用于表示表单字段。我们首先查看 清单 2 中的 XmlGuiForm.java


清单 2. XmlGuiForm.java

					
package com.msi.ibm;
import java.util.Vector;
import java.util.ListIterator;
import java.net.URLEncoder;

public class XmlGuiForm {

   private String formNumber;
   private String formName;
   private String submitTo;
   public Vector<XmlGuiFormField> fields;


   public XmlGuiForm()
   {
      this.fields = new Vector<XmlGuiFormField>();
      formNumber = "";
      formName = "";
      submitTo = "loopback"; // do nothing but display the results
   }
   // getters & setters
   public String getFormNumber() {
      return formNumber;
   }

   public void setFormNumber(String formNumber) {
      this.formNumber = formNumber;
   }


   public String getFormName() {
      return formName;
   }
   public void setFormName(String formName) {
      this.formName = formName;
   }

   public String getSubmitTo() {
      return submitTo;
   }

   public void setSubmitTo(String submitTo) {
      this.submitTo = submitTo;
   }

   public Vector<XmlGuiFormField> getFields() {
      return fields;
   }

   public void setFields(Vector<XmlGuiFormField> fields) {
      this.fields = fields;
   }

   public String toString()
   {
      StringBuilder sb = new StringBuilder();
      sb.append("XmlGuiForm:\n");
      sb.append("Form Number: " + this.formNumber + "\n");
      sb.append("Form Name: " + this.formName + "\n");
      sb.append("Submit To: " + this.submitTo + "\n");
      if (this.fields == null) return sb.toString();
      ListIterator<XmlGuiFormField> li = this.fields.listIterator();
      while (li.hasNext()) {
         sb.append(li.next().toString());
      }
   
      return sb.toString();
   }

   public String getFormattedResults()
   {
      StringBuilder sb = new StringBuilder();
      sb.append("Results:\n");
      if (this.fields == null) return sb.toString();
      ListIterator<XmlGuiFormField> li = this.fields.listIterator();
      while (li.hasNext()) {
         sb.append(li.next().getFormattedResult() + "\n");
      }

      return sb.toString();
   }

   public String getFormEncodedData()
   {
      try {
      int i = 0;
      StringBuilder sb = new StringBuilder();
      sb.append("Results:\n");
      if (this.fields == null) return sb.toString();
      ListIterator<XmlGuiFormField> li = this.fields.listIterator();
      while (li.hasNext()) {
         if (i != 0) sb.append("&");
         XmlGuiFormField thisField = li.next();
         sb.append(thisField.name + "=");
         String encstring = new String();
         URLEncoder.encode((String) thisField.getData(),encstring);
         sb.append(encstring);
      }

      return sb.toString();
      }
      catch (Exception e) {
         return "ErrorEncoding";
      }
   }
}

 

关于 XmlGuiForm 类,有几个重要项目需要注意:

  1. 这里有 4 个成员变量:
    • formNumber:这是服务器端表单分发机制的惟一标识符。
    • formName:这是表单标题,向用户提供上下文和确认。
    • submitTo:这是应用程序提交输入的数据(经过验证)的 URL。这个值也可以是一个 loopback。在 loopback 场景中,数据显示给用户,而不是提交到服务器。这对测试比较有用。
    • fields:这是被模板化以持有表单的字段数据的 Vector 类。清单 3 展示了 XmlGuiFormField.java 的细节。
  2. 一系列针对这些变量的 getterssetters
  3. toString()getFormattedResults() 方法负责生成 XmlGuiForm 类的可读摘要。
  4. getFormEncodedData() 方法在准备将数据提交到 submitTo 属性中指定的 URL 时使用。
  5. 这个代码不使用严格连接的 java.lang.String 类,而是采用一个 StringBuilder 作为构建理想数据字符串的一个更内存高效的方法。
  6. URLEncoder 类用于准备将数据提交到服务器。这使得数据实际上看起来就像一个传统 HTML 表单创建的一样。
  7. 这个应用程序的一些潜在扩展包括:
    • 元数据本地存储或缓存使重复任务更快运行。
    • 本地存储在提交前一段时间内记录数据。
    • GPS 记录 — 使用位置数据标记每条记录。

现在看看 清单 3 中 XmlGuiFormField 类的构造情况。


清单 3. XmlGuiFormField.java

					
package com.msi.ibm;
// class to handle each individual form
public class XmlGuiFormField {
   String name;
   String label;
   String type;
   boolean required;
   String options;
   Object obj;   // holds the ui implementation
                   // or the EditText for example


   // getters & setters
   public String getName() {
      return name;
   }
   public void setName(String name) {
      this.name = name;
   }
   public String getLabel() {
      return label;
   }
   public void setLabel(String label) {
      this.label = label;
   }
   public String getType() {
      return type;
   }
   public void setType(String type) {
      this.type = type;
   }
   public boolean isRequired() {
      return required;
   }
   public void setRequired(boolean required) {
      this.required = required;
   }
   public String getOptions() {
      return options;
   }
   public void setOptions(String options) {
      this.options = options;
   }

   public String toString()
   {
      StringBuilder sb = new StringBuilder();
      sb.append("Field Name: " + this.name + "\n");
      sb.append("Field Label: " + this.label + "\n");
      sb.append("Field Type: " + this.type + "\n");
      sb.append("Required? : " + this.required + "\n");
      sb.append("Options : " + this.options + "\n");
      sb.append("Value : " + (String) this.getData() + "\n");

      return sb.toString();
   }
   public String getFormattedResult()
   {
      return this.name + "= [" + (String) this.getData() + "]";

   }

   public Object getData()
   {
      if (type.equals("text") || type.equals("numeric"))
      {
         if (obj != null) {
            XmlGuiEditBox b = (XmlGuiEditBox) obj;
            return b.getValue();
         }
      }
      if (type.equals("choice")) {
         if (obj != null) {
            XmlGuiPickOne po = (XmlGuiPickOne) obj;
            return po.getValue();
         }
      }

      // You could add logic for other UI elements here
      return null;
   }

}

 

现在更详细地查看 XmlGuiFormField 类。

  • 有 6 个类级别成员:
    1. name 持有字段名称 — 这是数据值的字段名称,类似于 HTML 值的表单域名称或数据库列名。
    2. label 持有字段说明或显示给用户的值。
    3. type 表明要创建的用户界面字段的风格。
      • text 表明这个字段是用一个 EditText 字段实现的,用于输入字母数字字符。这是最常用的值。
      • numeric 也是一个 EditText,但它局限于数字值。
      • choice 使这个字段成为一个下拉列表。
    4. required 是一个 Boolean 值,标志字段是否为必要字段。如果字段必要且没有填充,则当用户试图提交表单时,将向用户显示一条错误消息。
    5. options 是一个字符串值,用于为一个选择字段传送一个可用选项列表。这个字段对其他字段可用,可能用作一个 regex 表达式以进行验证,也可以被覆盖以指定一个默认值。
    6. obj 表示用户界面小部件。例如,这个变量持有一个 EditText,用于文本或数字字段。对于一个选择字段,obj 成员包含一个 spinner 小部件。这个方法将在本教程后面进一步解释。
  • 不出所料,这些变量拥有几个 getterssetters
  • toString()getFormattedResult() 方法都利用 getData() 方法,我们将稍后解释。
  • XmlGuiFormField 类中,您需要管理多种类型数据,因此代码需要明确表示数据如何存储和访问。getData() 方法检查这个类型字段并在 obj 字段上执行一个类型设置,以便与存储的对象正确交互。如果您想向这个框架添加新字段类型,可以扩展 getData() 方法来支持这种新字段类型(参见 清单 3 末尾附近的注释)。

您现在有一种方法来存储和管理元数据。现在可以检查应用程序的运行情况,然后将各部分组合起来。


2011-08-18 00:39
阅读:
I'm VC , Just U know Y
本站部分文章来源于互联网,版权归原作者所有。

延伸阅读:

animation-list旋转的地球以及Timer的使用

Android JNI实例代码(二)

Android应用闪屏(Splash)

用Android Matrix类实现镜像方法

Android 加密解密