Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 中級開發 >> 使用 Android 和 XML 構建動態用戶界面(二)

使用 Android 和 XML 構建動態用戶界面(二)

編輯:中級開發

收集用戶數據

您已經創建了 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 注冊表單
運行中的 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 的汽車維修調查表
帶有三個 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 詢問油量變化
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
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 可以使用的應用程序值

注釋 0 Obj 包含一個將向用戶顯示的文本字符串 1 成功完成傳輸 2 出現一個錯誤。告知用戶出了問題,不要丟棄數據

圖 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";
?>

 

如果腳本返回字符串 SUCCESSRunForm 類將重置。其他任何值將導致向用戶顯示一條錯誤消息,並允許他們更正他們的輸入,或者獲取提交表單方面的幫助。

結束語

本教程演示了一個框架,它基於一個利用 Android SDK 的原生應用程序服務針對 android 用戶的動態問題。您看到了動態表單呈現、驗證和處理技術、以及從應用程序到 web 服務器的通信。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved