第十四章 SQLite

      在〈第十四章 SQLite〉中尚無留言

 資料庫系統

要處理這些大量的資料時, 通常會使用資料庫系統來儲存及分析.  若將資料存於Android裝置本機上, 可使用內建的SQLite資料庫系統存在SDCard上. 另外可將資料透過網路傳送到伺服器(雲端), Android支援的伺服器種類相當多, 最常用的有MySQL及MSSQL.

SQLite是開放式的小型資料庫, 跟一般商業用的資料庫有類似的架構, 所以同樣都是使用SQL語言進行操作, 但SQLite不支援網路直接連線. 通常由Android API進行查詢取得資料後, 再透過API傳送到遠端資料庫伺服器

每一個資料庫, 都會有好幾張的資料表, 資料表就像一個二維陣列一樣, 每一列都是一筆資料, 每一筆資料又是由好幾個欄位所構成

ID FirstName LastName TEL EMAIL ADDRESS BIRTHDAY
1 Thomas Wu 0989123456 mahaljsp@gmail.com 彰化市中正路一段100號 1980/05/07
2 John Hunag 0987123548 john@yahoo.com.tw 台北市忠孝東路三段23巷5號6F 1982/1/15
3 Venus Lin 0952852741 venus@gmail.com
4 Tracy Lin 0912963258 tracy@gmail.com 桃園市無尾巷10號 1970/2/24
5 Lisa Chen 0987456123 屏東市香蕉路1號

上述表格, 看似很像Excel的表格, 是這麼說沒錯. 但其實我很不喜歡用這種說法, 因為這樣就太抬舉了Excel了. 資料庫系統所能作的事情, 遠比Excel高明太多了.

多工

SQLite經由getDatabase取得database物件後,可以使用同一個database物件在多個執行緒中進行查詢。但不可以使用同一個database物件在不同的執行緒中寫入,因為在寫入時,第一個寫入的執行緒會進行lock,所以第二個執行緒就會出現資料庫被lock的例外。

所以建議當要寫入資料庫時,都用handler交由UI主執行緒進行寫入的動作。

另外,離開主程式前,因為要進行SQLite close的動作,所以如果別的執行緒還在查詢就被主執行緒關閉,一樣會出現例外,所以還是建議查詢時也是在UI主執行緒。

DAO Design Pattern

資料庫的程式設計相當複雜, 所以為了簡化其程序, 都必需遵循DAO的設計模式. 這就是為什麼在OCP認証考試中, 這麼重視DAO的原因.

但在移動式裝置中, 隨時都可能會因為移動位置而無網路訊號並與資料庫中斷連線, 所以DAO的設計方式, 反而變成了無用之地.

本章節所討論的為SQLite, 是存於裝置內的資料庫, 不用考慮網路問題, 所以還是可以遵循DAO的規範. 但請記得, 如果是使用MSSQL 或是MySQL, 則不能採用DAO.

設計資料庫程式的首要任務, 就是要決定到底要儲存什麼資料, 然後再設計到底要什麼樣的欄位. 所以本章節就以上述的連絡人資料表為說明

在連絡人裏, 目前設計有七個欄位, ID, FirstName, LastName, Tel, EMail, Address, Birthday. 當然如果有需要, 可再加入其他欄位, 如性別, 公司行號等等

撰寫資料庫程式時, 請有個心理準備, 所有的資料庫程式都要以如下的模式寫成

封裝所有的欄位成自訂資料型態

 在DAO模式裏, 首先要把每筆資料物件化, 也就是將每列中的所有欄位封裝成一個類別, 形成一個自訂的資料型態. 在此例中, 我們以Item這個類別來描述所有的欄位. 每個欄位都是private, 然後再加上getXXX()及setXXX()方法進行存取.

Item.java

public class Item {
    private long id;
    private String firstName;
    private String lastName;
    private String tel;
    private String email;
    private String address;
    private long birthday;
    public Item(){}
    public Item(long id, String firstName, String lastName, String tel,
                String email, String address, long birthday){
        this.id=id;
        this.firstname=firstName;
        this.lastname=lastName;
        this.tel=tel;
        this.email=email;
        this.address=address;
        this.birthday=birthday;
    }
    public long getId(){return id;}
    public void setId(long id){this.id=id;}
    public String getFirstName(){return firstName;}
    public void setFirstName(String firstName){this.firstName=firstName;}
    public String getLastName(){return lastName;}
    public void setLastName(String lastName){this.lastName=lastName;}
    public String getTel(){return tel;}
    public void setTel(String tel){this.tel=tel;}
    public String getEmail(){return email;}
    public void setEmail(String email){this.email=email;}
    public String getAddress(){return address;}
    public void setAddress(String address){this.address=address;}
    public long getBirthday(){return birthday;}
    public void setBirthday(long birthday){this.birthday=birthday;}
}

SQLiteOpenHelper

繼承SQLiteOpenHelper類別, 用來建立資料庫程及資料表.
先指定DATABASE_NAME資料庫名稱, 然後用static getDatabase()取得database資料庫物件.
onCreate()的任務是建立資料表, onUpgrade()則是在VERSION有變化時, 會砍掉原本的資料表, 再重新建立新的資料表.

請注意為什麼要有onUpgrade這個機制. 因App更新版本時若有更改到欄位, 裝置裏的資料表並不會跟著變更, 還是保留原本的欄位與資料. 所以為了讓App更新後也順便更新資料表, 就需更改VERSION版號, 此時就會觸發onUpgrade()刪除舊資料並重新建立新資料表.

public class DBHelper extends SQLiteOpenHelper {
    public static final String DATABASE_NAME = "mydata.db";
    public static final int VERSION = 1;
    private static SQLiteDatabase database;
    public DBHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }
    public static SQLiteDatabase getDatabase(Context context){
        if (database == null || !database.isOpen()) {
            database = new DBHelper(context, DATABASE_NAME,
                    null, VERSION).getWritableDatabase();
        }
        return database;
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(ItemDAO.CREATE_TABLE);
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS "+ItemDAO.TABLE_NAME);
        onCreate(db);
    }
}

DAO類別

實作DAO類別為資料庫程式的重頭戲, 需要明確指定新增, 修改, 刪除, 查詢四個方法

如下的程式碼中, 以ItemDAO為類別名稱. 物件變數有String TABLE_NAME 資料表名稱, 並使用String 記載每個欄位的名稱. 最後再使用 CREATE_TABLE 字串建立資料表. 在建構子中使用DBHelper.getDatabase(context) 啟動new DBHelper, 再由DBHelper的onCreate()取得這裏的TABLE_NAME而建立資料表

新增修改刪除查詢資料時, 使用 ContentValues cv=new ContentValues() 建立ContentValues物件, 然後把要修改的資料都放進這個物件裏. 再啟動db, insert(), db.update(), db.delete(), db.query()

ItemDAO.java

public class ItemDAO {
    public static final String TABLE_NAME = "contact";
    public static final String KEY_ID="_id";
    public static final String FIRSTNAME_COLUMN="firstname";
    public static final String LASTNAME_COLUMN="lastname";
    public static final String TEL_COLUMN="tel";
    public static final String EMAIL_COLUMN="email";
    public static final String ADDRESS_COLUMN="address";
    public static final String BIRTHDAY_COLUMN="birthday";
    public static final String CREATE_TABLE =
            "CREATE TABLE " + TABLE_NAME + " (" +
                    KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
                    FIRSTNAME_COLUMN + " TEXT NOT NULL, " +
                    LASTNAME_COLUMN + " TEXT NOT NULL, " +
                    TEL_COLUMN + " TEXT NOT NULL, " +
                    EMAIL_COLUMN + " TEXT, " +
                    ADDRESS_COLUMN + " TEXT, " +
                    BIRTHDAY_COLUMN + " INTEGER)";
    private SQLiteDatabase db;
    public ItemDAO(Context context){
        db=DBHelper.getDatabase(context);
    }
    public Item insert(Item item){
        ContentValues cv=new ContentValues();
        cv.put(FIRSTNAME_COLUMN, item.getFirstName());
        cv.put(LASTNAME_COLUMN, item.getLastName());
        cv.put(TEL_COLUMN, item.getTel());
        cv.put(EMAIL_COLUMN, item.getEmail());
        cv.put(ADDRESS_COLUMN, item.getAddress());
        cv.put(BIRTHDAY_COLUMN, item.getBirthday());
        long id=db.insert(TABLE_NAME, null, cv);
        item.setId(id);
        return item;
    }
    public boolean update(Item item){
        ContentValues cv=new ContentValues();
        cv.put(FIRSTNAME_COLUMN, item.getFirstName());
        cv.put(LASTNAME_COLUMN, item.getLastName());
        cv.put(TEL_COLUMN, item.getTel());
        cv.put(EMAIL_COLUMN, item.getEmail());
        cv.put(ADDRESS_COLUMN, item.getAddress());
        cv.put(BIRTHDAY_COLUMN, item.getBirthday());
        String where=KEY_ID+"="+item.getId();
        return db.update(TABLE_NAME, cv, where, null)>0;
    }
    public boolean delete(long id){
        String where =KEY_ID+"="+id;
        return db.delete(TABLE_NAME, where, null)>0;
    }
    public Item get(long id) {
        Item item = null;
        String where = KEY_ID + "=" + id;
        Cursor result = db.query(TABLE_NAME, null, where,
                null, null, null, null, null);
        if (result.moveToFirst()) {
            item = getRecord(result);
        }
        result.close();
        return item;
    }
    public Item getRecord(Cursor cursor){
        Item item=new Item();
        item.setId(cursor.getLong(0));
        item.setFirstName(cursor.getString(1));
        item.setLastName(cursor.getString(2));
        item.setTel(cursor.getString(3));
        item.setEmail(cursor.getString(4));
        item.setAddress(cursor.getString(5));
        item.setBirthday(cursor.getLong(6));
        return item;
    }
    public List<Item> getAll(){
        List<Item>list=new ArrayList<>();
        Cursor cursor = db.query(
                TABLE_NAME, null, null, null, null, null, null, null);
        while (cursor.moveToNext()) {
            list.add(getRecord(cursor));
        }
        cursor.close();
        return list;
    }
    public int getCount() {
        int count = 0;
        Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM " + TABLE_NAME, null);
        if (cursor.moveToNext()) {
            count = cursor.getInt(0);
        }
        return count;
    }
    public void sample() {
        Item item1 = new Item(0, "Thomas", "Wu", "0989123456", "mahaljsp@gmail.com", "彰化市中正路100號", new Date().getTime());
        Item item2 = new Item(0, "John", "Hung", "0912123456", "john@gmail.com", "台北市忠孝東路一段23巷5號6F", new Date().getTime());
        insert(item1);
        insert(item2);
    }
}

MainActivity

在MainActivity裏就簡單了, 先建立一個ListView, 使用new ItemDAO(this)取得itemDAO物件.
再用itemDAO.getAll()取得所有的資料並傳回List<Item>. 最後就可以用此list建立Adapter了

MainActivity.java

public class MainActivity extends AppCompatActivity {
    ListView listView;
    List<Item> list;
    ItemDAO itemDAO;
    ItemAdapter adapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        processViews();
        processControllers();
    }
    public void processViews(){
        listView=(ListView)findViewById(R.id.listView);
    }
    public void processControllers(){
        itemDAO=new ItemDAO(this);
        if (itemDAO.getCount()==0)itemDAO.sample();
        list=itemDAO.getAll();
        adapter=new ItemAdapter(this, list);
        listView.setAdapter(adapter);
        registerForContextMenu(listView);
    }
    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
        super.onCreateContextMenu(menu, v, menuInfo);
        MenuInflater inflater=getMenuInflater();
        inflater.inflate(R.menu.modify, menu);
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(resultCode== Activity.RESULT_OK){
            refresh();
        }
    }
    @Override
    public boolean onContextItemSelected(MenuItem item) {
        AdapterView.AdapterContextMenuInfo info=(AdapterView.AdapterContextMenuInfo) item.getMenuInfo();
        Intent intent=new Intent(this, UpdateActivity.class);
        final long id=((Item)(list.get(info.position))).getId();
        switch(item.getItemId()){
            case R.id.insert:
                startActivityForResult(intent, 100);
                break;
            case R.id.update:
                intent.putExtra("ID", id);
                startActivityForResult(intent, 101);
                break;
            case R.id.delete:
                AlertDialog.Builder dialog = new AlertDialog.Builder(this);
                dialog.setTitle("刪除")
                        .setMessage("確定要刪除?");
                dialog.setPositiveButton(getString(android.R.string.ok),
                        new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                itemDAO.delete(id);
                                refresh();
                            }
                        });
                dialog.setNegativeButton(getString(android.R.string.cancel), null);
                dialog.show();
                break;
        }
        return super.onContextItemSelected(item);
    }
    public void refresh(){
        list=itemDAO.getAll();
        adapter.setList(list);
        adapter.notifyDataSetChanged();
    }
}

自訂Adapter

在此以ItemAdapter為類別名稱並繼承BaseAdapter. 首先將所有的畫面元件用ViewHolder類別定義出來, 然後在getView()中將畫面元件資源物件化, 再定義畫面元件與顯示資料的相對應關係

public class ItemAdapter extends BaseAdapter {
    private LayoutInflater mInflater;
    List<Item>list;
    public ItemAdapter(Context context, List<Item>list){
        this.mInflater = LayoutInflater.from(context);
        this.list=list;
    }
    public void setList(List<Item> list){
        this.list=list;
    }
    @Override
    public int getCount() {
        return list.size();
    }
    @Override
    public Object getItem(int position) {
        return list.get(position);
    }
    @Override
    public long getItemId(int i) {
        return 0;
    }
    @Override
    public View getView(int position, View convertView, ViewGroup viewGroup) {
        ViewHolder holder;
        if (convertView == null) {
            holder=new ViewHolder();
            convertView = mInflater.inflate(R.layout.item_view, null);
            holder.txtId = (TextView)convertView.findViewById(R.id.txtId);
            holder.txtFirstName = (TextView)convertView.findViewById(R.id.txtFirstName);
            holder.txtLastName = (TextView)convertView.findViewById(R.id.txtLastName);
            holder.txtTel=(TextView)convertView.findViewById(R.id.txtTel);
            holder.txtEmail=(TextView)convertView.findViewById(R.id.txtEmail);
            holder.txtBirthday=(TextView)convertView.findViewById(R.id.txtBirthday);
            holder.txtAddress=(TextView)convertView.findViewById(R.id.txtAddress);
            convertView.setTag(holder);
        }else {
            holder = (ViewHolder)convertView.getTag();
        }
        holder.txtId.setText(String.valueOf(list.get(position).getId()));
        holder.txtFirstName.setText(list.get(position).getFirstname());
        holder.txtLastName.setText(list.get(position).getLastname());
        holder.txtTel.setText(list.get(position).getTel());
        holder.txtEmail.setText(list.get(position).getEmail());
        Date d=new Date(list.get(position).getBirthday());
        holder.txtBirthday.setText(DateFormat.getDateInstance(DateFormat.SHORT, Locale.TAIWAN).format(d));
        holder.txtAddress.setText(list.get(position).getAddress());
        if(position%2==0)
            convertView.setBackgroundColor(Color.rgb(0xaa,0xaa,0xaa));
        else
            convertView.setBackgroundColor(Color.rgb(0xcc, 0xcc, 0xcc));
        return convertView;
    }
    public final class ViewHolder {
        public TextView txtId, txtFirstName, txtLastName, txtTel, txtEmail, txtAddress, txtBirthday;
    }
}

自訂顯示Layout

因為要顯示的資料有七個欄位, 所以在Layout的安排上就要費心一點了

item_view.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal" android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <TextView
        android:layout_gravity="center"
        android:layout_height="wrap_content"
        android:id="@+id/txtId"
        android:layout_width="30sp" />
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <TextView
                android:layout_width="100sp"
                android:layout_height="wrap_content"
                android:id="@+id/txtFirstName"/>
            <TextView
                android:layout_width="50sp"
                android:layout_height="wrap_content"
                android:id="@+id/txtLastName"/>
            <TextView
            android:gravity="right"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/txtTel"/>
        </LinearLayout>
        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <TextView
                android:layout_height="wrap_content"
                android:id="@+id/txtEmail"
                android:layout_width="200sp" />
            <TextView
                android:gravity="right"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/txtBirthday"/>
        </LinearLayout>
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/txtAddress" />
    </LinearLayout>
</LinearLayout>

android_sqlite

新增修改程式

public class UpdateActivity extends Activity {
    TextView txtId, txtBirthday;
    ItemDAO itemDAO;
    EditText editFirstName, editLastName, editTel, editEmail, editAddress;
    Intent intent;
    long _id;
    int mYear=1980, mMonth=0, mDay=1;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_update);
        processViews();
        processControllers();
    }
    public void processViews(){
        txtId=(TextView)findViewById(R.id.txtId);
        editFirstName=(EditText)findViewById(R.id.editFirstName);
        editLastName=(EditText)findViewById(R.id.editLastName);
        editTel=(EditText)findViewById(R.id.editTel);
        editEmail=(EditText)findViewById(R.id.editEmail);
        editAddress=(EditText)findViewById(R.id.editAddress);
        txtBirthday=(TextView)findViewById(R.id.txtBirthday);
    }
    public void processControllers(){
        itemDAO=new ItemDAO(this);
        intent= getIntent();
        _id=intent.getLongExtra("ID", -1);
        if (_id!=-1) {
            Item item = itemDAO.get(_id);
            txtId.setText(String.valueOf(item.getId()));
            editFirstName.setText(item.getFirstname());
            editLastName.setText(item.getLastname());
            editTel.setText(item.getTel());
            editEmail.setText(item.getEmail());
            editAddress.setText(item.getAddress());
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(new Date(item.getBirthday()));
            mYear = calendar.get(Calendar.YEAR);
            mMonth = calendar.get(Calendar.MONTH);
            mDay = calendar.get(Calendar.DATE);
            txtBirthday.setText(mYear + "/" + (mMonth + 1) + "/" + mDay);
        }
    }
    public void btOk_click(View view){
        Item item=new Item();
        item.setFirstname(editFirstName.getText().toString());
        item.setLastname(editLastName.getText().toString());
        item.setTel(editTel.getText().toString());
        item.setEmail(editEmail.getText().toString());
        item.setAddress(editAddress.getText().toString());
        Calendar calendar=Calendar.getInstance();
        calendar.set(mYear, mMonth, mDay);
        item.setBirthday(calendar.getTimeInMillis());
        if(_id==-1)
            itemDAO.insert(item);
        else{
            item.setId(_id);
            itemDAO.update(item);
        }
        setResult(Activity.RESULT_OK, intent);
        this.finish();
    }
    public void btCancel_click(View view){
        this.finish();
    }
    public void btDate_click(View view){
        DatePickerDialog dialogDate=new DatePickerDialog(UpdateActivity.this,
                new DatePickerDialog.OnDateSetListener() {
                    @Override
                    public void onDateSet(DatePicker datePicker, int year, int month, int day) {
                        mYear = year;
                        mMonth = month;//month為年的第幾月, 第一個月為0
                        mDay = day;
                        txtBirthday.setText(mYear+"/"+mMonth+"/"+mDay);
                    }
                }, mYear, mMonth, mDay);
        dialogDate.show();
    }
}

新增修改畫面

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_update"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.asuscomm.mahaljsp.ch13_01_sqlite.UpdateActivity"
    android:orientation="vertical">
    <TableLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
            <TextView
                android:text="ID"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/textView0" />

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/txtId"
                android:textSize="18sp" />
        </TableRow>
        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
            <TextView
                android:text="FirstName"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/textView1" />
            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="textPersonName"
                android:ems="10"
                android:id="@+id/editFirstName" />
        </TableRow>
        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
            <TextView
                android:text="LastName"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/textView2" />
            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="textPersonName"
                android:ems="10"
                android:id="@+id/editLastName" />
        </TableRow>
        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
            <TextView
                android:text="Tel"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/textView3" />
            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="textPersonName"
                android:ems="10"
                android:id="@+id/editTel" />
        </TableRow>
        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
            <TextView
                android:text="eMail"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/textView4" />
            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="textPersonName"
                android:ems="10"
                android:id="@+id/editEmail" />
        </TableRow>
        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
            <TextView
                android:text="Address"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/textView5" />
            <EditText
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:inputType="textPersonName"
                android:ems="10"
                android:id="@+id/editAddress" />
        </TableRow>
        <TableRow
            android:layout_width="match_parent"
            android:layout_height="match_parent" >
            <TextView
                android:text="Birthday"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:id="@+id/textView6" />

            <LinearLayout
                android:orientation="horizontal"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:id="@+id/txtBirthday"
                    android:textSize="18sp"
                    android:layout_weight="1" />

                <ImageButton
                    android:onClick="btDate_click"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    app:srcCompat="@android:drawable/ic_menu_month"
                    android:id="@+id/btDate"/>

            </LinearLayout>

        </TableRow>
    </TableLayout>
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <Button
            android:text="取消"
            android:onClick="btCancel_click"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/btCancel"
            android:layout_weight="1" />

        <Button
            android:text="確定"
            android:onClick="btOk_click"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/btOk"
            android:layout_weight="1" />
    </LinearLayout>
</LinearLayout>

本章完整程式碼下載

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *