編輯:中級開發
收集用戶數據
您已經創建了 Activity 主屏幕布局,現在可以創建用戶界面表單來收集數據了。在本例中,您將創建一個 Robotics Club Registration 表單和一個 Auto Maintenance 調查表單。
使用元數據
這個應用程序取決於 android 程序員動態操作用戶界面的能力。在本教程前面,您檢查了 main.XML 文件,該文件定義 XMLGui 類(主Activity)的屏幕布局。如果您總是必須在設計或編譯時定義用戶界面元素,那麼應用程序幾乎不可能是它的當前形式。
幸運的是,您並不局限於那種方式。DisplayForm() 方法負責將這個元數據轉換為用戶界面元素,以便搜集數據。其代碼分為兩個主要功能區域:用戶界面元素的布局和提交按鈕的處理。
首先,檢查布局邏輯。這段代碼負責將 XMLGuiForm 對象轉換為一個真正的屏幕表單。清單 8 展示了這段代碼。
清單 8. 布局邏輯
private boolean DisplayForm()
{
try
{
ScrollView sv = new ScrollView(this);
final LinearLayout ll = new LinearLayout(this);
sv.addView(ll);
ll.setOrIEntation(android.widget.LinearLayout.VERTICAL);
// walk through the form elements and dynamically create them,
// leveraging the mini library of tools.
int i;
for (i=0;i<theForm.fields.size();i++) {
if (theForm.fields.elementAt(i).getType().equals("text")) {
theForm.fields.elementAt(i).obj = new
XmlGuiEditBox(this,(theForm.fields.elementAt(i).isRequired()
? "*" : "") + theForm.fields.elementAt(i).getLabel(),"");
ll.addView((View) theForm.fields.elementAt(i).obj);
}
if (theForm.fields.elementAt(i).getType().equals("numeric")) {
theForm.fields.elementAt(i).obj = new
XmlGuiEditBox(this,(theForm.fields.elementAt(i).isRequired()
? "*" : "") + theForm.fields.elementAt(i).getLabel(),"");
((XmlGuiEditBox)theForm.fields.elementAt(i).obj).makeNumeric();
ll.addView((View) theForm.fields.elementAt(i).obj);
}
if (theForm.fields.elementAt(i).getType().equals("choice")) {
theForm.fIElds.elementAt(i).obj = new
XMLGuiPickOne(this,(theForm.fields.elementAt(i).isRequired()
? "*" : "") + theForm.fields.elementAt(i).getLabel(),
theForm.fields.elementAt(i).getOptions());
ll.addView((View) theForm.fields.elementAt(i).obj);
}
}
Button btn = new Button(this);
btn.setLayoutParams(new LayoutParams
(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.
WRAP_CONTENT));
ll.addView(btn);
btn.setText("Submit");
btn.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
// check if this form is Valid
if (!CheckForm())
{
AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
AlertDialog ad = bd.create();
ad.setTitle("Error");
ad.setMessage("Please enter all required (*) fields");
ad.show();
return;
}
if (theForm.getSubmitTo().equals("loopback")) {
// just display the results to the screen
String formResults = theForm.getFormattedResults();
Log.i(tag,formResults);
AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
AlertDialog ad = bd.create();
ad.setTitle("Results");
ad.setMessage(formResults);
ad.show();
return;
} else {
if (!SubmitForm()) {
AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
AlertDialog ad = bd.create();
ad.setTitle("Error");
ad.setMessage("Error submitting form");
ad.show();
return;
}
}
}
} );
setContentVIEw(sv);
setTitle(theForm.getFormName());
return true;
} catch (Exception e) {
Log.e(tag,"Error Displaying Form");
return false;
}
}
您必須預期超出單個屏幕可以容納的內容的更多字段的可用性,因此,使用一個 ScrollVIEw 作為父視圖或容器。在該 ScrollVIEw中,您使用一個垂直 LinearLayout 來將各種用戶界面小部件組織為一個垂直列。
這種方法非常簡單:
XMLGuiForm 實例的 fIElds 成員中包含的 XMLGuiFormFIEld對象來列舉。LinearLayout。您將快速檢查不同的 UI 小部件。當 UI 元素被創建並添加到這個線性布局中後,您將整個ScrollVIEw 實例添加到這個屏幕的內容並將表單名指定為屏幕標題。圖 9 展示了為用戶輸入准備就緒的一個 Robotics 俱樂部注冊屏幕。這個表單是 清單 1 中的 XML 數據的處理結果。
圖 9. 運行中的 Robotics 注冊表單
我們來看看為這個應用程序創建的不同的自定義用戶界面小部件。
回想一下,我們為這個應用程序定義了三種類型的數據輸入字段:文本、數值和選項。這三種類型通過兩個不同的自定義小部件實現:XMLGuIEditBox 和 XMLGuiPickOne。
文本和數值非常相似,因此可以利用相同的 EditVIEw 方法,但使用不同的輸入過濾器來在字母數字和僅允許數字之間切換。清單 9 展示了 XMLGuIEditBox 類的代碼。
清單 9.XMLGuIEditBox 類
package com.msi.ibm;
import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextVIEw;
import android.widget.EditText;
import android.text.method.DigitsKeyListener;
public class XmlGuiEditBox extends LinearLayout {
TextView label;
EditText txtBox;
public XmlGuiEditBox(Context context,String labelText,String initialText) {
super(context);
label = new TextView(context);
label.setText(labelText);
txtBox = new EditText(context);
txtBox.setText(initialText);
txtBox.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams
.FILL_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT));
this.addView(label);
this.addVIEw(txtBox);
}
public XMLGuIEditBox(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public void makeNumeric()
{
DigitsKeyListener dkl = new DigitsKeyListener(true,true);
txtBox.setKeyListener(dkl);
}
public String getValue()
{
return txtBox.getText().toString();
}
public void setValue(String v)
{
txtBox.setText(v);
}
}
XMLGuIEditBox 類擴展 LinearLayout 類並包含一個文本標簽來描述要求的輸入,以及一個 EditText 來實際收集輸入的數據。所有對象初始化都在構造函數中完成。這也許被視為糟糕的表單,但如果您對這種方法不滿意,那麼這只是留給您的一個練習。
我們還將討論三種方法。getValue() 和 setValue() 方法正是您想要的。它們是用於與 EditText 字段交互的 getter 和 setter。
第三種方法 makeNumeric() 僅在設置數值表單類型時才調用。DigitsKeyListener 的實例用於過濾任何非數值鍵。您免費獲取的另一個好處是,適當的鍵盤將根據正在使用的 XMLGuIEditBox 類型而顯示 — 帶有或沒有數值設置。
圖 10 展示了運行中的表單,顯示了一個字母鍵盤,原因是 Last Name 字段設置為輸入字母,即 text。
圖 10. 字母數值鍵輸入
圖 11 展示了正在使用數值鍵盤,原因是 age 字段的數據類型設置為 numeric。
圖 11. 數值鍵盤
選擇字段通過 XMLGuiPickOne 類在用戶界面中實現,與上述輸入字段有點不同。這個選擇字段被實現為一個 android Spinner 小部件。這個用戶界面元素類似於其他編程環境中的下拉列表框,在那裡,用戶必須從一些現成選項中選擇一個選項。圖 12 展示三個實例XMLGuiPickOne 小部件。
圖 12. 帶有三個 XMLGuiPickOne 的汽車維修調查表
在本例中,正在收集的數據用於統計目的,與處理自由文本輸入字段相比,規范化可能的輸入使得數據處理更整潔。當然,如果您想將這次調查限制到一個特殊的地理區域的話,可以將 State 字段定義為一個選擇字段。
清單 10 展示了 XMLGuiPickOne 類的代碼。
清單 10. XMLGuiPickOne 類
package com.msi.ibm;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Spinner;
import android.widget.ArrayAdapter;
public class XmlGuiPickOne extends LinearLayout {
String tag = XmlGuiPickOne.class.getName();
TextView label;
ArrayAdapter<String> aa;
Spinner spinner;
public XmlGuiPickOne(Context context,String labelText,String options) {
super(context);
label = new TextVIEw(context);
label.setText(labelText);
spinner = new Spinner(context);
String []opts = options.split("\\|");
aa = new ArrayAdapter<String>( context,
android.R.layout.simple_spinner_item,opts);
spinner.setAdapter(aa);
this.addView(label);
this.addVIEw(spinner);
}
public XMLGuiPickOne(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public String getValue()
{
return (String) spinner.getSelectedItem().toString();
}
}
這個類看起來非常類似於 XMLGuIEditBox 類,主要區別在於它采用了一個 Spinner 控件而不是一個 EditText 控件。還要注意,這個類只實現了 getValue() 方法。這個類的一個明顯增強是允許用戶指定一個默認值。
注意,我們使用了 options 成員來填充選項列表。在這段代碼中,我們使用一個 regex 表達式將包含可用選項的 String 分割為一個數組,然後將其傳遞給 ArrayAdapter 的一個實例。常量android.R.layout.simple_spinner_item 被內置到 android 中,而不是在本教程的應用程序代碼中提供。這個適配器設置好後,就被分配給這個 Spinner。圖 13 展示了屏幕上顯示的選項列表提示用戶,典型的是油量變化與英裡數。
圖 13. XMLGuiPickOne 詢問油量變化
既然用戶可以在表單中輸入數據了,現在可以驗證並提交數據了。
保存並提交數據
現在必須創建一種方法,允許用戶通過驗證數據並將數據提交到一個服務器來保存數據。
保存
現在應該回顧一下 RunForm 類的 DisplayForm() 方法。回想一下,該方法的第一個選項負責繪制表單。接下來,您將檢查提交按鈕的onClick() 處理程序,如 清單 11 所示。
清單 11. onClick() 處理程序
btn.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
// check if this form is Valid
if (!CheckForm())
{
AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
AlertDialog ad = bd.create();
ad.setTitle("Error");
ad.setMessage("Please enter all required (*) fIElds");
ad.show();
return;
}
if (theForm.getSubmitTo().equals("loopback")) {
// just display the results to the screen
String formResults = theForm.getFormattedResults();
Log.i(tag,formResults);
AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
AlertDialog ad = bd.create();
ad.setTitle("Results");
ad.setMessage(formResults);
ad.show();
return;
} else {
if (!SubmitForm()) {
AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
AlertDialog ad = bd.create();
ad.setTitle("Error");
ad.setMessage("Error submitting form");
ad.show();
return;
}
}
}
} );
當用戶選擇提交按鈕時,表單條目將被檢查,以確保所有必要字段都已填充。否則,將顯示一個 AlertDialog 來提醒用戶填充所有字段。假定數據輸入令人滿意,現在可以提交數據了。.
數據提交過程由這個教程應用程序的兩個部分完成。如果表單的submitTo 字段已經設置回送值,則這些值只需回顯到屏幕上。這有利於實現測試目的。如果您對表單工具對數據的收集效果感到滿意,現在可以將其指向一個服務器頁面,該頁面負責記錄表單條目。
清單 12 展示了 CheckForm() 方法。這段代碼非常直觀。每個字段都被檢查,看看它們是否必要。如果字段必要但用戶沒有提供相應信息,將設置一個標志。您可以增強這種標志,向用戶提供更具體的反饋。
清單 12. CheckForm() 方法
private boolean CheckForm()
{
try {
int i;
boolean good = true;
for (i=0;i<theForm.fields.size();i++) {
String fieldValue = (String)
theForm.fields.elementAt(i).getData();
Log.i(tag,theForm.fields.elementAt(i)
.getName() + " is [" + fieldValue + "]");
if (theForm.fields.elementAt(i).isRequired()) {
if (fieldValue == null) {
good = false;
} else {
if (fIEldValue.trim().length() == 0) {
good = false;
}
}
}
}
return good;
} catch(Exception e) {
Log.e(tag,"Error in CheckForm()::" + e.getMessage());
e.printStackTrace();
return false;
}
}
現在可以將收集的數據提交到服務器了。檢查 清單 13 中的SubmitForm()。
清單 13. SubmitForm() 方法
private boolean SubmitForm()
{
try {
boolean ok = true;
this.progressDialog = ProgressDialog.show(this,
theForm.getFormName(), "Saving Form Data", true,false);
this.progressHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// process incoming messages here
switch (msg.what) {
case 0:
// update progress bar
progressDialog.setMessage("" + (String) msg.obj);
break;
case 1:
progressDialog.cancel();
finish();
break;
case 2:
progressDialog.cancel();
break;
}
super.handleMessage(msg);
}
};
Thread workthread = new Thread(new TransmitFormData(theForm));
workthread.start();
return ok;
} catch (Exception e) {
Log.e(tag,"Error in SubmitForm()::" + e.getMessage());
e.printStackTrace();
// tell user that the submission failed....
Message msg = new Message();
msg.what = 1;
this.progressHandler.sendMessage(msg);
return false;
}
}
首先,您設置 android.os.Handler 類的一個實例。當應用程序需要與不同的線程共享數據時,Handler 類可以派上用場。SubmitForm()方法中需要注意的另一個重要項目是 ProgressDialog。注意,ProgressDialog 和 Handler 被定義為類級變量,如 清單 14所示。
清單 14. ProgressDialog 和 Handler
public class RunForm extends Activity {
/** Called when the activity is first created. */
String tag = RunForm.class.getName();
XMLGuiForm theForm;
ProgressDialog progressDialog;
Handler progressHandler;
...
}
您肯定不想在與服務器通信時不必要地阻塞應用程序,因此您部署了一個後台 Thread 來通信,但您依賴這個 Handler 來從通信線程接收通知,以便向用戶提供反饋。注意,只有主線程被視為與用戶界面交互。這個獨立線程方法的一個替代方法是 android.os 包中的AsyncTask 類。
當應用程序連接到服務器以傳輸數據時,它有機會通知用戶操作的狀態,這當然是一種好實踐。圖 14 展示了運行中的ProgressDialog。
圖 14.ProgressDialog
實際服務器通信代碼位於 清單 15 中的 TransmitFormData() 類中,該類實現 Runnable 界面。
清單 15. TransmitFormData 類
private class TransmitFormData implements Runnable
{
XmlGuiForm _form;
Message msg;
TransmitFormData(XMLGuiForm form) {
this._form = form;
}
public void run() {
try {
msg = new Message();
msg.what = 0;
msg.obj = ("Connecting to Server");
progressHandler.sendMessage(msg);
URL url = new URL(_form.getSubmitTo());
URLConnection conn = url.openConnection();
conn.setDoOutput(true);
BufferedOutputStream wr = new BufferedOutputStream
(conn.getOutputStream());
String data = _form.getFormEncodedData();
wr.write(data.getBytes());
wr.flush();
wr.close();
msg = new Message();
msg.what = 0;
msg.obj = ("Data Sent");
progressHandler.sendMessage(msg);
// Get the response
BufferedReader rd = new BufferedReader(new
InputStreamReader(conn.getInputStream()));
String line = "";
Boolean bSuccess = false;
while ((line = rd.readLine()) != null) {
if (line.indexOf("SUCCESS") != -1) {
bSuccess = true;
}
// Process line...
Log.v(tag, line);
}
wr.close();
rd.close();
if (bSuccess) {
msg = new Message();
msg.what = 0;
msg.obj = ("Form Submitted Successfully");
progressHandler.sendMessage(msg);
msg = new Message();
msg.what = 1;
progressHandler.sendMessage(msg);
return;
}
} catch (Exception e) {
Log.d(tag, "Failed to send form data: " + e.getMessage());
msg = new Message();
msg.what = 0;
msg.obj = ("Error Sending Form Data");
progressHandler.sendMessage(msg);
}
msg = new Message();
msg.what = 2;
progressHandler.sendMessage(msg);
}
}
TransmitFormData 類負責連接到 XMLGuiForm 實例(來自元數據)的 submitTo 成員中列示的服務器。它通過 sendMessage() 方法,發送 Message 類的一個實例來定期更新主應用程序線程。以下兩個成員在 Message 類上填充:
what 值充當一個高級開關,通過消息通知 Handler 應該如何操作。obj 值指定一個可選 Java.lang.Object。在本例中,一個Java.lang.String 實例被傳遞並用於在 Progress Dialog 中顯示。任意給定應用程序使用的架構是任意的。這個應用程序使用 表 3 中的值。
表 3. what 可以使用的應用程序值
圖 15 展示 Form Data 傳輸成功時 ProgressDialog 中的最終消息。
圖 15. 表單提交
表單成功提交後,應用程序返回主頁面。對於生產就緒的應用程序,下一步發生的操作主要取決於數據收集組織的目的。這個屏幕可以只是重置以便接受另一個輸入,就像在物理存貨應用程序中一樣。或者,您也可以將用戶引導到其他屏幕。
應用程序要正確運行,androidManifest.XML 文件必須包含對所有使用的 Activity 類的引用,且必須包括用於 Internet 訪問的用戶權限。清單 16 展示了這個教程應用程序的 androidManifest.XML 文件。
清單 16. androidManifest.XML 文件
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.msi.ibm"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".XMLGui"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".RunForm">
</activity>
</application>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
</manifest>
結束之前,我們簡單看看服務器端腳本。
提供一個服務器端腳本
基於本教程的目的,您將使用一個 PHP 腳本來收集必要數據,並將數據附加到一個文本文件。
服務器上
服務器上發生的事情的確切情況取決於數據收集組織的需求。數據收集的一個常用方法是將表單數據存儲在一個關系數據庫中,比如 DB2®、MySQL、SQL Server、Oracle 等。數據存儲在數據庫中後,就可以被分割和分析。
對於本教程,數據通過一個 PHP 腳本收集並附加到一個文本文件上。清單 17 展示了與這個 Robotics 注冊表單關聯的 PHP 表單。
清單 17. Robotic 的 PHP 表單
<?PHP // XMLgui form # 1 // this page is expecting // fname // lname // gender // age $filename = "/pathtowritablefile/datafile.txt"; $f = fopen($filename,"a"); fprintf($f,"Data received @ ".date(DATE_RFC822)); fprintf($f,"\n"); fprintf($f,'First Name:['.$_POST['fname'].']'); fprintf($f,"\n"); fprintf($f,'Last Name:['.$_POST['lname'].']'); fprintf($f,"\n"); fprintf($f,'Gender:['.$_POST['gender'].']'); fprintf($f,"\n"); fprintf($f,'Age:['.$_POST['age'].']'); fprintf($f,"\n"); fclose($f); print "SUCCESS"; ?>
如果腳本返回字符串 SUCCESS,RunForm 類將重置。其他任何值將導致向用戶顯示一條錯誤消息,並允許他們更正他們的輸入,或者獲取提交表單方面的幫助。
結束語
本教程演示了一個框架,它基於一個利用 Android SDK 的原生應用程序服務針對 android 用戶的動態問題。您看到了動態表單呈現、驗證和處理技術、以及從應用程序到 web 服務器的通信。
ListView的右邊滾動滑塊啟用方法
很多開發者不知道ListVIEw列表控件的快速滾動滑塊是如何啟用的,這裡android開發網告訴大家,輔助滾動滑塊只需要一行代碼就可以搞定,如果你使用XML布局只需要在
Android 中實現並發性、聯網和數據訪問
簡介: Java™ 語言是 Android 開發人員所選的工具。android 運行時使用自己的虛擬機 Dalvik,這並不是多數程序開發人員使用
Android學習之使用 HTML 5 開發新的可視化 UI 特性
簡介: HTML 5 針對移動 Web 應用程序引入了大量新特性,其中包括一些可視化特性,它們通常會帶來強烈的視覺沖擊。Canvas 是最引人注目的新 UI
Android視圖控件布局之Layout 布局(1)
一個android視圖有很多控件,那麼怎麼來控制它們的位置排列呢?我們需要容器來存放這些控件並控制它們的位置排列,就像Html中div, table一樣,android