Android 日期與文字互轉範例

在手機中輸入日期並解析日期進行相關處理是常見的活動,但在傳統的程式語言中,字串與日期卻是不太容易處理的資料型態,而且當輸入的日期時間資料格式不相同時,就必須要先進行日期格式 (pattern) 的設定才不會解析誤,再加上以Android Virtual Device Manager的擬虛機器模擬手機的操作時,由於誤用(或誤認)錯誤輸入資料方式將導致android程式執行一直發生錯誤,但是就是找不到程式在那裏出錯的情況。本文將詳細的進行Android 日期時間資料與EditText物件類別之間如何進行互相轉換的範例說明。

 

1 問題

假設現在想設計一個Android程式,使用者先輸入自己的生日,例如,1987/07/15,然後在按了一個按鈕物件後,程式就分別顯示:生日年份是1987,生日月份是7,生日日數是15。圖1.1 是一開始程式執行時的初始畫面:

01-start.jpg  

圖1.1. 程式執行後的初始畫面。

當使用者輸入 1987/07/15並點按「查詢生日的年月份」按鈕後,正確產生的結果如 圖1.2 所示。

02-ok-result.jpg  

圖1.2. 使用者輸入生日後程式解析的結果。

以下各節將說明如何進行相關的程式設計與可能遭遇的困難與相對應的解決方式。

 

2 Eclipse中Android程式設計環境

一般要設計一個Android App時,有許多種開發的環境,目前較簡單程式的環境已有愈來愈多人使用 MIT 的AppInventor,然而較不昜進行複雜的程式設計。而最多人使用的是採用Eclipse整合性開發環境,並在Eclipse中安裝Android SDK的外掛程式來設計開發Android程式。安裝Eclipse與Android SDK的方式,請詳見「安裝Eclipse與Android SDK」一文的說明。

當安裝完成 Eclipse與Android SDK後,使用者可以在Eclipse整合性開發環境中,建立一個Android的專案後,如 圖2.1 所示的畫面。另外讀者要注意的是,由於不同版本的 Android SDK 所提供的資源與工具有所不同,所以在開發與邁立專案時,所可以使用的視窗編輯元件與畫面可能就不太一樣。因此讀者自行用自己的 Eclipse 中建立一個新的 Android 專案時,可能會與本文的畫面不太相同,但是設計專案的流程與概念卻是大同小異的,因此讀者應該要能夠自行進行不同 Android SDK 版本專案開發畫面的瞭解與映對。

 

09-eclipse-date01

圖2.1. 於Eclipse中開啟Android程式專案的畫面-全部。

以下將 圖2.1 分別切割成圖2.2、圖2.3、圖2.4 以較能清楚的閱讀內容:

09-eclipse-date01-01

圖2.2. 於Eclipse中開啟Android程式專案的畫面-左邊。


09-eclipse-date01-02

圖2.3. 於Eclipse中開啟Android程式專案的畫面-中間。


09-eclipse-date01-03  

圖2.4. 於Eclipse中開啟Android程式專案的畫面-右邊。

在進行Android App程式設計時,使用者必須要設計三大部份(三大區),分別是res (resources) 資源區,src (sources) 程式區,以及 Android系統架構區(就是在AndroidManifest.xml該檔案中進行設定)。

在Project Exploer的管理視窗中,點選該建立的Android專案後,就會出現許多的子目錄與檔案,其中使用者可以進行直接編輯的就是上述三大區塊子目錄或檔案的內容。

2.1 res (resources) 資源區

res 目錄代表的是 resources資源的意思,亦即在Android執行程式時將會或可以存取資源的地方,如圖2.2中紅色數字標示之第1區。所謂Android的資源可以包括:

2.1.1 畫面的配置(layout)

亦即進行畫面各個元件的設計與配置,如Eclipse所顯示的 layout 子目錄,圖2.2中紅色數字標示之第1.1區。在Andriod中,一個視窗(程式)被稱為 Activity,因為該視窗可以進行許多的活動,所以被稱為Activity。由於每一個視窗是由許多不同功能的可視元件與非可視元件所組成,所以就會針會該視窗建立一個「xml型態的視窗配置檔」來集中記錄該視窗包括了那些元件,以及這些元件的顯示位置關係。例如若在建立一個Android專案時所建立的預設視窗名稱為SampleDate, 那麼Eclipse整合開發環境就會自動解析該視窗的配置檔名為 activity_sample_date.xml,當然在建立的過程中使用者可以自行修改該視窗的配置檔名。由圖1.2中可以明顯的看出此Android的預設視窗共由10個元件所組成,而且每一各元件間有其相對應的顯示位置。


2.1.2 各種視窗專用變數與常數設定區(values)

例如Eclipse所顯示的 values 子目錄圖2.2中紅色數字標示之第1.1區該values子目錄中包括許多的檔案,其中最常被使用的是strings.xml檔案。每一個Android視窗中可以建立各種系統所提供的元件,例如,TextView, EditText, Button等元件,而每一個元件大都在存許多的欄位,而每一個欄位皆可設定一個值,此一欄位值可以在該視窗所屬的「xml型態的視窗配置檔」中直接設定,例如:

<TextView
        android:id="@+id/tv_lb_keyin_birthday"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/tv_lb_hello"
        android:layout_below="@+id/tv_lb_hello"
        android:layout_marginTop="19dp"
        android:text="請輸入您的生日:" />

 

亦即將一個名稱為 tv_lb_keying_birthday的TextView元件的 text 欄位屬性的值設為字串"請輸入您的生日:"。

也可以將該欄位值填入一個變數,該變數就是存在於 values子目錄中的某一個檔案。例如:

<TextView
        android:id="@+id/tv_lb_hello"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />

 

亦即將一個名稱為 tv_lb_hello 的TextView元件的 text 欄位屬性的值設為去存取名為hello_world的變數之值,而且該變數是儲存在strings.xml這個檔案中, 上述設定句「android:text="@string/hello_world"」中的@string指的就是要去存取strings.xml檔案。若查閱該strings.xml檔案,則應該也要出現對應的hello_world變數的設定,否則程式執行時就會不正確。strings.xml的範例如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">Date01</string>
    <string name="action_settings">Settings</string>
    <string name="hello_world">Android日期處理範例01!</string>

</resources>

 

其中「<string name="hello_world">Android日期處理範例01!</string>」即是建立一個名為hello_world的變數,且變數的值設為「Android日期處理範例01!」。

2.1.3 圖片區(drawable)

亦即存放圖片影音檔案的目錄區。圖2.2中紅色數字標示之第1.1區。使用者可以利用檔案總管,在該Android專案中的res子目錄中,自行建立一個名為「drawable」的子目錄,並複製相關的圖片與影音檔案,以供視窗中的元件來取用。當然使用者也可以將同一個圖片以不同的大小來儲存成不同的檔名,並分別存放至不同的「drawable-xxxx」子目錄中,以供不同的情境使用。

2.1.4 menu區

亦即用來存放每一個視窗中顯示與可供點按操作的選單資料,其檔案為預設為視窗名稱的xml檔。例如若視窗名稱為 SampleDate,則menu的設定檔名將自動預設為「sampel_date.xml」。

 

2.2 src 原始程式區 (source code)

亦即存放原程式碼的地方。Android將每一個應用程式以URL網址的方式來命名應用程式(package),亦即以階層的方式來命名,例如,網址為 com.example.date01,其中date01即為專案的名稱(而不是Activity視窗的名稱)。com與example在建立專案時皆可以更改。若以檔案總管去查看scr子目錄,會發現com.example.date01是分別建立3個子目錄,而且是形成父子階層的關係。亦即com之下有一子目錄example,example目錄下又建立一子目錄date01。

而最終真正的程式預設是與視窗名稱同名的java 程式,例如若視窗名稱當初為為SampleDate,則程式的檔名就變成「SampleDate.java」。

 

2.3 Android系統架構區 (AndroidManifest.xml)

亦即在AndroidManifest.xml該檔案設定整個Android 應用程式包括了幾個視窗、誰是預設起始視窗、與網路伺服器連結方式的設定等等。AndroidManifest.xml的範例如下所示:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.date01"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="10"
        android:targetSdkVersion="10" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.date01.SampleDate"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

 

上 述<activity> ... </activity>的區塊就是在定義一個視窗的基本屬性,若是一個 Android應用程式 (package) 包括了 (設計了) 兩個視窗,則必定來要此 AndroidManifest.xml中,另外再新建一組<activity> ... </activity>的區塊來定義另一個視窗的相關屬性。 

 

3 日期與字串資料互轉程式設計

3.1 建立專案

 21-newProject.jpg

圖3.1.1. 開始要建立新專案的功能選項。

 

22-newAndroidProject.jpg

圖3.1.2. 選擇要建立Android應用程式專案

 

23-newAndroidProject-define.jpg

圖3.1.3. 建立與設定Android應用程式的名稱

 

24-configureProject.jpg

圖3.1.4. 進行簡單Android專案的設定

 

25-configureIconSet.jpg

圖3.1.5. 決定不同大小尺寸的圖示圖片

上述中所設定的 Icon Set 圖片就是存放在 圖2.2 中紅色數字標示 第1.3區 的子目錄中。

 

26-decideCreateActivty.jpg

圖3.1.6. 決定是否建立Activity視窗以及視窗的樣式類別

 

27-defineActivty.jpg

圖3.1.7. 設定Activity視窗的名稱與所屬的layout視窗元件配置檔名

 

28-eclipse-android-project-default.jpg  

圖3.1.8. 建立一個新Android 應用程式專案後的Eclipse畫面

 

29-max-layout.jpg

圖3.1.9. 初始Activity視窗layout的GUI畫面

 

30-layout-xml-helloworld.jpg

圖3.1.10. 初始Activity視窗layout的xml檔案畫面

 上圖中的TextView是系統自動產生的視窗元件,目的是做為顯示歡迎詞字串之用,此時此一TextView元件並無設定ID識別名稱。

 

31-add-textview.jpg

圖3.1.11. 新建一個TextView視窗元件的GUI畫面

上圖中乃從 Form Widgets區塊中拖曳一個TextView元件到視窗顯示區塊中。

 

32-add-textview-layout-xml.jpg

圖3.1.12. 新建一個TextView視窗元件後的layout 之xml檔案的畫面

上圖乃顯示當新建一個視窗元件後,layout之xml檔案會自動調整的內容。其中新建的TextView元件在「android:id="@+id/textView1"」那一列代表該新TextView元件的ID識別名稱被設為「textView1」,而原來用來做為顯示歡迎詞的TextView元件,新增了自己的ID識別名為且設定為「textView2」。

33-change-textview-layout-xml.jpg

圖3.1.13. 修改layout 之xml檔案完成後的畫面

上圖中顯示使用者為了閱讀的容易性,所以將兩個TextView元件的ID識別碼進行更改,同時也一併更新兩個元件間進行相對位置關聯時的元件ID識別名稱的修正。強烈建議每新增一個視窗元件,就要到layout 的xml檔案中,將新增元件的ID識別名稱進行修改,如此以後再次新增新的視窗元件時,系統就會依照之前已建立好的視窗元件的ID識別名稱,來設定與新增視窗元件之間的位置關係。否則,若一次就建立所有的視窗元件後,想再來改變各視窗元件的ID識別名稱時,就會造成各元件位置關係名稱的混亂。

 

34-changed-textview-layout-gui.jpg

圖3.1.14. 修改layout 之xml檔案完成後的GUI畫面

 

35-strings-xml-default.jpg

圖3.1.15. strings.xml檔案未修改前的畫面

 

36-strings-xml-changed-helloworld.jpg

圖3.16. strings.xml檔案修改後的畫面

37-strings-xml-changed-helloworld-gui.jpg

圖3.1.17. Activity視窗第一階段修改完成後的GUI畫面

 

39-TextFields.jpg

圖3.1.18.TextFields 視窗元件選擇區-一般的文字類型

 

40-TextFields-date.jpg  

圖3.1.19.TextFields 視窗元件選擇區-日期與數值類型

於上圖中,將TextFields視窗元件選擇區中,拖曳紅色標記為第2區的日期類型 EditText元件至GUI的畫面設計區。接著點選 activity_sample_date.xml的頁簽,將出現如下新增的EditText元件的設定:

<EditText
        android:id="@+id/editText1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/tv_lb_keyin_birthday"
        android:layout_alignBottom="@+id/tv_lb_keyin_birthday"
        android:layout_marginLeft="24dp"
        android:layout_toRightOf="@+id/tv_lb_keyin_birthday"
        android:ems="10"
        android:inputType="date" >

        <requestFocus />
    </EditText>

 

其中「android:inputType="date"」就是指該 EditText 元件設成只可輸入為日期格式的資料。而「android:id="@+id/editText1"」則是該 EditText元件的ID識別名稱,必須要加以更改為適合的名稱。

 

3.2 視窗 layout設計

本範例程式是希望建立如 圖1.2 的畫面,因此在Eclipse建立專案後,請依序在視窗畫面中每建立一個視窗元件後,就立即轉入layout的xml檔案中修改新增視窗元件的ID識別名稱,然後再回到視窗畫面繼續新增所需的視窗元件,直到完成所有需要建立的視窗元件。下圖就是最後完成建立所有所需視窗元件的設計畫面。

38-layout-gui-final.jpg  

 圖3.2.1. Activity視窗最後完整修改後的GUI畫面

 

最終的視窗layout之 sample_sample_date.xml的內容如下所示:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".SampleDate" >

    <TextView
        android:id="@+id/tv_lb_hello"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />

    <TextView
        android:id="@+id/tv_lb_keyin_birthday"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/tv_lb_hello"
        android:layout_below="@+id/tv_lb_hello"
        android:layout_marginTop="19dp"
        android:text="請輸入您的生日:" />

    <EditText
        android:id="@+id/ed_birthday"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/tv_lb_keyin_birthday"
        android:layout_alignBottom="@+id/tv_lb_keyin_birthday"
        android:layout_marginLeft="21dp"
        android:layout_toRightOf="@+id/tv_lb_keyin_birthday"
        android:ems="10"
        android:inputType="date" >

        <requestFocus />
    </EditText>

    <TextView
        android:id="@+id/tv_lb_msg_year"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/tv_lb_keyin_birthday"
        android:layout_below="@+id/ed_birthday"
        android:layout_marginTop="20dp"
        android:text="生日年份是:" />

    <TextView
        android:id="@+id/tv_lb_out_year"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/tv_lb_msg_year"
        android:layout_alignBottom="@+id/tv_lb_msg_year"
        android:layout_toRightOf="@+id/tv_lb_keyin_birthday"
        android:text="" />

    <TextView
        android:id="@+id/tv_lb_msg_month"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/tv_lb_msg_year"
        android:layout_below="@+id/tv_lb_out_year"
        android:text="生日月份是:" />

    <TextView
        android:id="@+id/tv_lb_out_month"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/tv_lb_msg_month"
        android:layout_alignBottom="@+id/tv_lb_msg_month"
        android:layout_centerHorizontal="true"
        android:text="" />

    <TextView
        android:id="@+id/tv_lb_msg_day"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/tv_lb_msg_month"
        android:layout_below="@+id/tv_lb_out_month"
        android:text="生日日數是:" />

    <TextView
        android:id="@+id/tv_lb_out_day"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/tv_lb_msg_day"
        android:layout_alignBottom="@+id/tv_lb_msg_day"
        android:layout_toRightOf="@+id/tv_lb_keyin_birthday"
        android:text="" />

    <Button
        android:id="@+id/btn_inquery"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv_lb_out_day"
        android:layout_marginTop="16dp"
        android:layout_toRightOf="@+id/tv_lb_msg_day"
        android:text="查詢生日的年月份" />

</RelativeLayout>

 

 3.3 res 視窗存取之變數常數之設定

 最終strings.xml檔案的內容如下所示:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">Date01</string>
    <string name="action_settings">Settings</string>
    <string name="hello_world">Android日期處理範例01!</string>

</resources>

 

3.4 java應用程式之設計

3.4.1最終 SampleDate.java 程式檔案的內容

如下所示:

package com.example.date01;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class SampleDate extends Activity {

    EditText ed_birthday;
    TextView tb_lb_out_year,tb_lb_out_month,tb_lb_out_day;
    Button btn_inquery;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sample_date);
        doWorks();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.sample_date, menu);
        return true;
    }

    public void doWorks(){
        
        ed_birthday = (EditText) findViewById(R.id.ed_birthday);
        tb_lb_out_year = (TextView) findViewById(R.id.tv_lb_out_year);
        tb_lb_out_month = (TextView) findViewById(R.id.tv_lb_out_month);
        tb_lb_out_day = (TextView) findViewById(R.id.tv_lb_out_day);
        btn_inquery = (Button) findViewById(R.id.btn_inquery);
        
        btn_inquery.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                // TODO Auto-generated method stub
                Calendar c = Calendar.getInstance();
                
                try{
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd",Locale.US);
                    String strDate = ed_birthday.getText().toString();
                    Date birthday = sdf.parse(strDate);
                    c.setTime(birthday);
                    int year = c.get(Calendar.YEAR);
                    int month = c.get(Calendar.MONTH)+1;
                    int day = c.get(Calendar.DAY_OF_MONTH);
                    
                    tb_lb_out_year.setText(String.valueOf(year));
                    tb_lb_out_month.setText(String.valueOf(month));
                    tb_lb_out_day.setText(String.valueOf(day));
                    
                }catch(java.text.ParseException e){ e.printStackTrace();};
                
            }
        });  
    }
}

3.4.2 Android Java 程式架構

Android Java程式檔中可以分為幾大區塊:

  1. Android 應用程式 (package)名稱區塊。
  2. 外部資源匯入宣告區塊。
  3. 物件類別設計區塊。

 

3.4.2.1 Android 應用程式 (package)名稱區塊

此區塊只有一行以 package 開頭的指令,以此 SampleDate.java 程式為例,此 package 指令的內容為:「package com.example.date01;」

 

3.4.2.2 外部資源匯入宣告區塊

亦即宣告於 java 程式中將會引用的外部函數庫,此區塊中會使用到的是所有由 import 指令開頭的指令。

 

3.4.2.3 物件類別設計區塊

由 class 指令開頭來定義每一個物件類別的指令。例如「public class SampleDate extends Activity {...} 」。其中,public 代表此定義的類別是否可以完全公開被其他程式或物件所引用;SampleDate 則是使用者自行定義的類別名稱;extends 後面接得是父類別的名稱,所以 extends Activity 代表延伸繼承視窗 Activity 父類別的所有屬性與方法;{...} 則是使用者自定類別的詳細內容,可以定義屬性與方法。

一個 java 程式中可以定義多個物件類別,若是設計一個 Android 視窗程式,則一定至少包括一個繼承 (extends) 自 Activity 的視窗父類別。自然若一個Android 應用程式包括兩個視窗,自然就要設計兩個使用者自定的類別,而且是繼承 (extends) 自 Activity 的視窗父類別

 

一個 java 物件類別程式是用來處理資料的,所以自然必須要先指出要處理的資料是誰,然後再利用一些指令來處理這些資料。

在一個 Android視窗 java 物件類別程式中,資料主要可分為外部資料與內部資料。其中內部資料目前使用者可將之視為自行使用的內部變數或常數即可。

java 類別中外部資料可以是與 layout 視窗元件進行連結,也可以是外部的資料庫或其他資料來源。本 SampleDate.java 程式因為是要去讀取 layout 視窗中的元件,所以就必須要同時建立一個內部資料變數,並將這些內部資料變數與外部資料來源變數進行連結,當連結完成後,處理內部資料變數,就等同於處理外部資料變數。

例如,「 EditText ed_birthday;」是定義一個名為 ed_birthday 的內部資料變數,其資料型態為 EditText。「ed_birthday = (EditText) findViewById(R.id.ed_birthday);」指令中,R.id.ed_birthday 是指自 R檔案中,從所建立的視窗元件ID識別名稱中,取出一個ID識別名稱為 ed_bithday 的視窗元件。此例的視窗元件就是在 activity_sample_date.xml 中所定義的。而指令 findViewById(...) 就是到 R 檔案中,從所建立的視窗元件ID識別名稱中,取出一個ID識別名稱為 ed_bithday 的視窗元件。其中 R 檔案就是 Android 系統自動將 activity_sample_date.xml 進行翻譯後的結果,當然 R 檔案也包括其他的 res 定義的資料。因此,等號左邊的 ed_birthday 是內部資料變數,而 R.id.ed_birthday 是外部資料變數,以後要處理的是內部的資料變數 ed_birthday,而且處理內部資料變數,就等同於處理外部資料變數

本 SampleDate 物件類別中的主要內部資料物件包括「EditText ed_birthday; TextView tb_lb_out_year,tb_lb_out_month,tb_lb_out_day; Button btn_inquery;」,分別與外部資料物件進行如下的連結:「ed_birthday = (EditText) findViewById(R.id.ed_birthday); tb_lb_out_year = (TextView) findViewById(R.id.tv_lb_out_year); tb_lb_out_month = (TextView) findViewById(R.id.tv_lb_out_month); tb_lb_out_day = (TextView) findViewById(R.id.tv_lb_out_day); btn_inquery = (Button) findViewById(R.id.btn_inquery);」

 

3.4.3 處理物件類別 click 等事件的方式

物件導向程式設計的概念一般是事件導向的,也就是說,當發生某一事件後,與該事件相應的 (亦即有關的) 物件才進行回應處理。因此包括兩階段的作業,第一階段是要隨時偵測是否已發生某一事件,第二階段就是針對已發生的事件進行相應的處理。而每一作業都必須要有人或個體負責處理,第一階段要偵測事件是否已發生的個體,稱為 Listener 物件類別,監聽偵測不同事件的 Listener 其物件類別名稱會不同,例如監聽是否按下滑鼠按左鍵事件的 Listener 稱為 OnClickListener 物件類別。所以在 java 程式中,就要先建立一個 Listener 物件,例如「new View.OnClickListener() {...}」就是建立一個監聽是否按下滑鼠左鍵的 Listner。然後進入第二階段,在該相應的事件函數區塊中撰寫回應該事件的處理程式,例如,在「public void onClick(View v) {...}」區塊中撰寫回應按下滑鼠按左鍵事件的處理程式

然而一個用來監聽某一事件是否發生的 Listener 物件,通常是與某一個已建立的物件 (通常是指一個 java 的內部資料變數) 是直接相關的,若是想建立的 Listener 物件不會與別的 java 內部資料物件變數來共用時,則可以直接指定一個新建的 Listener 物件給該 java 內部資料物件變數,此例中,「btn_inquery.setOnClickListener(new View.OnClickListener() {...}」就是將 java 內部資料物件 btn_inquery 以 setOnClickListener() 方法來設定一個 Listener 物件,而此一 Listener 物件則是直接用 「new View.OnClickListener()」的指令來產生一沒有特別命名的 Listener 物件。而 {...} 則是對該 Listener 物件的內容進行設定撰寫相應的函數程式,例如使用者必須自行撰寫「public void onClick(View v) {...}」當按下滑鼠左鍵後該如何處理的程式。

 

3.4.4 日期資料與文字欄位資料的轉換程式

在 java 程式中最早的日期資料物件類別為 Date,但是因為某些原因,Date 中的某些方法已不被鼓勵使用,反而是建議採用 Calendar 物件類別來進行日期資料的儲存與處理。然而通常使用者會先將日期的資料以字串的型式處理,所以就必須將字串形式的日期資料,轉成 Date 物件,然後再將 Date 物件指定給 Calendar 物件。

然而在將字串日期資料轉成 Date 物件之前,必須要先知道字串日期資料的表達方式後,才能進行正確的字串日期資料的解析,否則就會解析錯誤。因此就必須借助 DateFormat 此一物件類別來設定字串日期資料解析的格式,然而通常不會直接使用 DateFormat 物件類別,而是使用 DateFaormat 的延伸子類別 SimpleDateFormat 來設定日期解析的格式 (pattern)。

上述完整程式中的「SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd",Locale.US);」,就是建立一名為 sdf 的 SimpleDateFormat 物件,而設定的日期解析格式為 "yyyy/MM/dd" (請參考此處 android 官方對日期符號格式的說明),而且是採用 Locale.US 美國的日期資料格式。

String strDate = ed_birthday.getText().toString();」此一指令是將 ed_birthday 此一 EditText物件,取出其Text屬性 (使用 getText() 的方法),並將 Text 屬性的內容轉成字串資料,並存入一個名為 strDate 的字串變數中.

Date birthday = sdf.parse(strDate);」此一指令中,利用 sdf 此一 SimpleDateFormat 物件中的 parse() 方法來將 strDate 字串變數轉成 Date 物件資料之後,再將此一 Date 物件資料存入 birthday 此一 Date 物件。

Calendar c = Calendar.getInstance();」指令是產生一個名為 c 的 Calendar 物件。

c.setTime(birthday);」指令是將 birthday 的 Date 物件指定做為 c 此一 Calendar 物件的日期資料。

int year = c.get(Calendar.YEAR);」是取出 Calendar 物件中的年份資料,存入 year 此一整數變數中。

int month = c.get(Calendar.MONTH)+1;是取出 Calendar 物件中的月份資料,存入 month 此一整數變數中,由於 Calendar.MONTH的值由 0 開始,所以要再加上 1 才是正確的月份。

int day = c.get(Calendar.DAY_OF_MONTH);是取出 Calendar 物件中的月份之日數資料,存入 day 此一整數變數中。

 tb_lb_out_year.setText(String.valueOf(year));」指令中是以 Sting.valueOf() 函數將整數變數 year 轉成字串後,再以 setText() 方法存入 tb_lb_out_year 此一 TextView 物件的 Test 屬性中。其餘的 setText() 指令類推之。

由於 SimpleDateFomrt  的 parse 方法常常有可能在解析日期時會發生錯誤,所以就必須以 try {...} catch (){...}的程式架構來處理。其中,try {...}是主要程式區,而catch (){...}是指當發生錯誤時的錯誤處理區。此例中的「catch(java.text.ParseException e){ e.printStackTrace();}」是指當 SimpleDateFormat 物件解析日期資料發生錯誤事件「java.text.ParseException e」(其中 e 為 java.text.ParseException 型態的物件)時,其處理錯誤的方式就是利用 「e.printStackTrace()」將異常型態物件 e 的內容顯示出來。

 

3.5 執行 Android 應用程式

執行一個已經寫好的應用程式,請右點按該應用程式專案名稱,例如本範例,請點選視窗左邊「Date01」專案的名稱,再點選 「Run As/ Android Application」。此時,若已經先透過 「Window/Aandroid AVD Manager」來啟動一個虛擬手機,則系統會自動上傳該 Android 應用軟體至該虛擬手機後並啟動執行之。若尚未先啟動一個虛擬手機,則系統會自動選擇,或出現另一個視窗來讓使用者自行選擇要啟動那一個虛擬手機或直實的手機。

 

4 日期與文字欄位互轉程式設計時可能發生的錯誤情形

如前所述,本問題是希望使用者將生日資料輸入到 EditText 視窗元件中,然後在程式中讀取該 EditText中 Text屬性的值,並解析成獨立的年份數值,月份數值,與日數數值。在程式設計上是先建立一個Calendar類別物件,然後在程式中讀取該 EditText中 Text屬性的值,並將之轉為 Date 類別物件,然後再將此一 Date 物件設定成為所建立之 Calendar物件的時間值。接著再從 Calendar物件中分別讀取年份,月份,與日數後,直接寫入所對應的三個用來顯示年份、月份、日數的TextView物件。

然而在模擬手機中輸入生日資料時,由於往往使用者誤認手機上的特殊符號例如倒斜線是半形字(佔1個byte),但實際上卻是一個全形字 (佔2個byte),所以就會導致eclipse產生錯誤的訊息,而且查看程式似乎都沒有錯,但是就是不知問題在那裏,最終才發現原來是輸入特殊符號時產生上述的錯誤,其解決的方式其實很簡單,就是直接用所使用的電腦鍵盤輸入日期,而不要使用手機上的虛擬鍵盤。

以下各圖將呈現上述發生錯誤的操作過程:

03-keying-by-sim-keyboard-start.jpg

 

圖4.1. 模擬手機中輸入生日時出現虛擬鍵盤

 

04-keying-by-sim-keyboard-symbol-up-arrow-btn.jpg

圖4.2. 模擬手機中輸入生日年份後,想點按「記號」來輸入特殊符號-倒斜線。

 

05-keying-by-sim-keyboard-symbol-backslash.jpg

 

圖4.3. 模擬手機中輸入特殊符號-半形的倒斜線

上圖中,沒有出現一大片的特殊符號時,該特殊符號應該有可能是半形的特殊符號,這是因為在建立EditText物件時設為「android:inputType="date"」的原故,所以最終程式就可以順利的解析日期資料,不會發生錯誤。

 

06-keying-by-sim-keyboard-symbol-backslash-2bytes.jpg

圖4.4. 模擬手機中輸入特殊符號-全形倒斜線

上圖中的某些特殊符號是屬於全形(2個byte),這是因為在建立EditText物件時沒有設為「android:inputType="date"」的原故,所以最終程式就無法順利的解析日期資料,將會發生錯誤。

 

07-keying-by-sim-keyboard-symbol-backslash-2bytes-out.jpg

圖4.5.用虛擬鍵盤所輸入的倒斜線是屬於全形字型

 

08-keying-by-sim-keyboard-symbol-backslash-2bytes-logchat-error-msg.jpg  

圖4.6. 圖4.5.用虛擬鍵盤所輸入的倒斜線是屬於全形字型,將會造成「Unparseable date: ....」的錯誤

因此要解決上述 Unparseable date 錯誤的方式如下:

  1. 輸入日期的資料時要與 SimpleDateFormat中所設定的日期資料格式相一致,例如日期格式為「yyyy/MM/dd」輸入時就一定要輸入類似「1987/07/15」的日期,若是輸入「1987-07-15」就會產生錯誤。
  2. 設定EditText視窗元件的屬性為日期資料型態,亦即多了一行android:inputType="date"的設定。
  3. 在模擬測試時,不要使用虛擬鍵盤來輸入生日日期資料,直接採用電腦的實體鍵盤來輸入日期資料。

 

文章標籤
創作者介紹
創作者 xx3d2ybnf 的頭像
xx3d2ybnf

xx3d2ybnf-不圖3日但2年精進勇者不懼的部落格

xx3d2ybnf 發表在 痞客邦 留言(0) 人氣()