編輯:中級開發
收集用戶數據
您已經創建了 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列表控件的快速滾動滑塊是如何啟用的,這裡android開發網告訴大家,輔助滾動滑塊只需要一行代碼就可以搞定,如果你使用XML布局只需要在
簡介: Java™ 語言是 Android 開發人員所選的工具。android 運行時使用自己的虛擬機 Dalvik,這並不是多數程序開發人員使用
簡介: HTML 5 針對移動 Web 應用程序引入了大量新特性,其中包括一些可視化特性,它們通常會帶來強烈的視覺沖擊。Canvas 是最引人注目的新 UI
一個android視圖有很多控件,那麼怎麼來控制它們的位置排列呢?我們需要容器來存放這些控件並控制它們的位置排列,就像Html中div, table一樣,android