iT邦幫忙

DAY 27
1

以「寶寶聯絡簿」為例,適合 Android 初學者的學習筆記系列 第 27

寶寶生活記錄 App (Day27 寶寶列表的其它實作細節)

討論完 ListView 和客製化的 List Item 後,今天來討論「寶寶列表 (BabyListActivity)」的其它實作細節,剩下的實作細節應該比較不太需要講解了,對於 Listener, Callback Method, AlertDialog, LayoutInflater, Activity Lifecycle 已經熟悉的讀者,建議直接跳過今天的內容。

剩下的實作細節可分成四項說明:

  1. 相關 ImageView 的點擊 (Click) 處理
  2. ListView 的點擊 (Click) 和長按 (Long Click) 處理
  3. AlertDialog 的使用方法
  4. 為何讀取寶寶列表內容的動作是寫在 onResume

首先來看 BabyListActivity 的原始碼:

package lincyu.babylog.babymanager;

import android.app.ActionBar;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;

import java.util.ArrayList;
import java.util.Locale;

import lincyu.babylog.Constants;
import lincyu.babylog.R;
import lincyu.babylog.db.Baby;
import lincyu.babylog.db.BabyDB;


public class BabyListActivity extends ActionBarActivity {

    private ListView lv_babylist;
    private LinearLayout ll_emptylist, ll_loading, ll_notempty;
    private ImageView iv_addbaby, iv_close;
    private RelativeLayout rl_usage;

    private ArrayList<Baby> babylist;
    private BabyArrayAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_babylist);
        viewInitialization();

    }

    private void viewInitialization() {
        ActionBar actionbar = getActionBar();
        actionbar.setDisplayHomeAsUpEnabled(true);
        actionbar.setDisplayShowHomeEnabled(false);

        ll_loading = (LinearLayout)findViewById(R.id.ll_loading);
        ll_emptylist = (LinearLayout)findViewById(R.id.ll_emptylist);
        ll_notempty = (LinearLayout)findViewById(R.id.ll_notempty);

        rl_usage = (RelativeLayout)findViewById(R.id.rl_usage);

        iv_addbaby = (ImageView)findViewById(R.id.iv_addbaby);
        iv_addbaby.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startBabyEditor(-1);
            }
        });
        iv_close = (ImageView)findViewById(R.id.iv_close);
        iv_close.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                rl_usage.setVisibility(View.GONE);
            }
        });

        lv_babylist = (ListView)findViewById(R.id.lv_babylist);
        lv_babylist.setOnItemClickListener(lv_clicklistener);
        lv_babylist.setOnItemLongClickListener(lv_longclicklistener);
    }

    AdapterView.OnItemClickListener lv_clicklistener = new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
            Baby baby = babylist.get(position);
            startBabyEditor(baby.babyid);
        }
    };

    AdapterView.OnItemLongClickListener lv_longclicklistener = new AdapterView.OnItemLongClickListener() {
        @Override
        public boolean onItemLongClick(AdapterView<?> adapterView, View view, int position, long id) {
            showRemoveConfirmDialog(babylist.get(position));
            return true;
        }
    };

    private void showRemoveConfirmDialog(final Baby baby) {

        LayoutInflater inflater = LayoutInflater.from(this);
        View view = inflater.inflate(R.layout.dialog_singletextview, null);

        String space = " ";
        String lang = Locale.getDefault().getLanguage();
        if (lang.equalsIgnoreCase("zh") == true) {
            space = "";
        }

        String confirmstr = getString(R.string.removebabyconfirm1) + space +
                baby.toString() + space + getString(R.string.removebabyconfirm2);

        TextView tv = (TextView)view.findViewById(R.id.tv_dialogmsg);
        tv.setText(confirmstr);

        AlertDialog.Builder adb = new AlertDialog.Builder(this);
        adb.setView(view);
        adb.setPositiveButton(R.string.remove, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                BabyDB.removeBaby(BabyListActivity.this, baby.babyid);
                // 未來若有其他資料 (生活資料), 必須一並刪除
                new BackgroundLoadBabyList().execute();
            }
        });
        adb.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
            }
        });
        adb.show();
    }

    private void startBabyEditor(int babyid) {
        Intent intent = new Intent();
        intent.setClass(this, BabyEditorActivity.class);
        intent.putExtra(Constants.EXTRA_BABYID, babyid);
        startActivity(intent);
    }

    @Override
    public void onResume() {
        super.onResume();
        new BackgroundLoadBabyList().execute();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                finish();
                break;
        }
        return super.onOptionsItemSelected(item);
    }

    protected class BackgroundLoadBabyList extends AsyncTask<Void, Void, Void> {

        @Override
        protected void onPostExecute(Void result) {
            ll_loading.setVisibility(View.GONE);

            if (babylist.size() == 0) {
                ll_emptylist.setVisibility(View.VISIBLE);
                ll_notempty.setVisibility(View.GONE);
            } else {
                ll_notempty.setVisibility(View.VISIBLE);
                ll_emptylist.setVisibility(View.GONE);
            }
            lv_babylist.setAdapter(adapter);
        }

        @Override
        protected void onPreExecute() {
            ll_loading.setVisibility(View.VISIBLE);
            ll_emptylist.setVisibility(View.GONE);
            ll_notempty.setVisibility(View.GONE);
        }

        @Override
        protected Void doInBackground(Void... params) {
            babylist = BabyDB.getAllBabies(BabyListActivity.this);
            adapter = new BabyArrayAdapter(BabyListActivity.this, babylist);
            return null;
        }
    }
}

首先來看 ImageView 的點擊處理,共有兩個 ImageView 需要處理,一個是當寶寶列表是空的時候,會出現一個「+」按鈕,讓使用者可新增寶寶,程式使用了匿名類別的寫法,onClick 裡的內容相當單純,就是啟動 BabyEditorActivity,必且將 EXTRA_BABYID 設成 -1 (新寶寶)。第二個要處理的 ImageView 是當列表非空時,列表上方會有一個「使用說明」,按下「x」按鈕可關閉「使用說明」,onClick 裡的內容也相當簡單,就是呼叫 View 的 setVisibility 讓「使用說明」這個 View 消失 (GONE),這邊做個小說明,GONE 不僅只有讓 View 消失,所佔用的空間也會讓出,若使用 INVISIBLE,則只是讓 View 消失,空間還是佔有著。

接著說明 ListView 的點擊 (Click) 和長按 (Long Click) 處理,我們分別利用 AdapterView 類別 [1] 的 setOnItemClickListener 和 setOnItemLongClickListener 來設定點擊 (Click) 和長按 (Long Click) 的處理。在 79~85 行,程式宣告了一個OnItemClickListener物件,並覆寫onItemClick這個 Method,Method 裡要做的事是要讓使用者能夠編輯寶寶資料,程式碼內容則相當簡單,就是啟動 BabyEditorActivity,必且將 EXTRA_BABYID 設成要編輯的寶寶的 ID,比較需要提及的是:我們可利用 onItemClick 的第三個參數 position 來判斷是哪個寶寶被按下了。而 87 ~ 93 行,程式宣告了一個OnItemLongClickListener物件,並覆寫onItemLongClick這個 Method,Method 裡要做的事是要讓使用者能夠刪除寶寶資料,我們同樣可利用 onItemLongClick 的第三個參數得知是哪一個寶寶的記錄需要刪除,由於刪除的動作是無法還原的,因此利用 AlertDialog 顯示一個對話框,確認是否真的要刪除,接下來就說明一下 AlertDialog 的使用 [2]。

首先程式建立一個 AlertDialog.Builder 型別的物件 adb,建構子需要一個 Context 物件,把BabyListActivity物件實體 (即this) 填入即可。adb 這個物件有許多Methods可呼叫,首先程式先利用 LayoutInflater 將一個版面設計 XML 檔轉成一個 View 物件,這個 XML 檔的內容如下所示:

<LinearLayout 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="6dp"
    android:paddingLeft="7dp"
    android:paddingRight="7dp"
    android:paddingTop="5dp"
    android:orientation="horizontal"
    android:weightSum="2">
    <TextView android:id="@+id/tv_dialogmsg"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:textSize="20sp"
        android:layout_gravity="center"
        />
</LinearLayout>

檔案內容很簡單,就只是一個 LinearLayout 包著 TextView,因此程式利用 View 類別的 findViewById 取出這個 TextView,並將「刪除確認」的訊息寫到這個 TextView,最後呼叫 adb 的 setView 就完成了這個對話框的內容主體 (一個 TextView),接著呼叫 AlertDialog.Builder [3] 的 setPositveButton 和 setNegativeButton 設定「刪除資料」和「取消」按鈕,關於這兩個 Methods 的用法,留給讀者自行閱讀 AlertDialog.Builder 類別的說明文件 [3],只是要說明的是,「取消」按鈕的 Callback Method 內什麼都不用做,按下「取消」按鈕後,對話框會消失,而「刪除資料」按鈕內,我們刪除了資料庫裡寶寶的資料,要注意如果未來還有更多寶寶的資料(例如用別的 Table 記錄寶寶的飲食、睡眠等),那些資料也必須一並刪除,資料庫處理完畢後,我們執行 new BackgroundLoadBabyList().execute(); 重新讀取寶寶列表資料,以便確保 ListView 的內容有被更新,對話框的執行結果如下圖所示:

最後針對 BabyListActivity 只有一點需要說明,就是為何讀取寶寶列表內容的動作 (new BackgroundLoadBabyList().execute();) 是寫在 onResume 而不是 onCreate,這是因為程式中會啟動 BabyEditorActivity,編輯或新增寶寶執行完畢後,BabyEditorActivity 會結束,BabyListActivity 會重新從背景移到前景,此時 ListView 的內容應隨剛才的編輯結果更改,而移動到前景的過程中,onRestart, onStart, onResume 會分別被呼叫,可是 onCreate 並不會被呼叫,因此讀取寶寶列表內容的動作應該寫在 onResume,更多 Activity Lifecycle 的討論,請參考筆者的書籍 [4]。

參考資料

[1] AdapterView | Android Developers, http://developer.android.com/reference/android/widget/AdapterView.html

[2] AlertDialog | Android Developers, http://developer.android.com/reference/android/app/AlertDialog.html

[3] AlertDialog.Builder | Android Developers, http://developer.android.com/reference/android/app/AlertDialog.Builder.html

[4] 林致宇, Android程式設計入門與應用(附範例光碟), 全華出版社, ISBN: 9789572194126, http://www.opentech.com.tw/search/bookinfo.asp?isbn=9789572194126&companyID=04383129


上一篇
寶寶生活記錄 App (Day26 Customized ArrayAdapter)
下一篇
寶寶生活記錄 App (Day28 從內建的相片集程式中選取照片)
系列文
以「寶寶聯絡簿」為例,適合 Android 初學者的學習筆記30

尚未有邦友留言

立即登入留言