設計資料庫表格
建立資料庫表格使用SQL的「CREATE TABLE」指令,這個指令需要指定表格的名稱,還有這個表格用來儲存每一筆資料的欄位(Column)。
這些需要的表格欄位可以對應到主要類別中的欄位變數,
不過SQLite資料庫的資料型態只有下面這幾種,使用它們來決定表格欄位可以儲存的資料型態:
- INTEGER – 整數,對應Java 的byte、short、int 和long。
- REAL – 小數,對應Java 的float 和double。
- TEXT – 字串,對應Java 的String。
後面加上「PRIMARY KEY AUTOINCREMENT」的設定,就可以讓SQLite自動為每一筆資料編號。
建立SQLiteOpenHelper類別
Android 提供許多方便與簡單的資料庫API,可以簡化應用程式處理資料庫的工作。這些API都在「android.database.sqlite」套件,它們可以用來執行資料庫的管理和查詢的工作。
這個套件中的「SQLiteOpenHelper」類別,可以在應用程式中執行建立資料庫與表格的工作,應用程式第一次在裝置執行的時候,由它負責建立應用程式需要的資料庫與表格,
後續執行的時候開啟已經建立好的資料庫讓應用程式使用。
還有應用程式在運作一段時間以後,如果增加或修改功能,資料庫的表格也增加或修改了
,它也可以為應用程式執行資料庫的修改工作,讓新的應用程式可以正常的運作。
Create New Java Class
public class MyDBHelper extends SQLiteOpenHelper{
//資料庫名稱
public static final String DATABASE_NAME = "mydata.db";
//資料庫版本,資料結構改變的時後要更改這個數字,通常是加一
public static final int VERSION = 1;
//資料庫物件,固定的欄位變數
private static SQLiteDatabase database;
public MyDBHelper(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 MyDBHelper(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建立新版的表格
onCreate(db);
}
}
資料庫功能類別
在Android應用程式中使用資料庫功能通常會有一種狀況,就是Activity或其它元件的程式碼,會因為加入處理資料庫的工作,程式碼變得又多、又複雜。所以這裡說明的作法,會採用在一般應用程式中執行資料庫工作的設計方式,把執行資料庫工作的部份寫在一個獨立的Java類別中。
接下來設計應用程式需要的資料庫功能類別,提供應用程式與資料庫相關功能
資料存取物件(Data Access Object,DAO)
public class ItemDAO {
//表格名稱
public static final String TABLE_NAME = "item";
//編號表格欄位名稱,固定不變
public static final String KEY_ID = "_id";
//其它表格名稱(參考來自 Item.class 的物件)
public static final String DATETIME_COLUMN = "datetime";
public static final String TITLE_COLUMN = "title";
public static final String CONTENT_COLUMN = "content";
//使用上面宣告的變數來建立表格的SQL指令 (注意字串前後的空白)
public static final String CREATE_TABLE =
"CREATE TABLE " + TABLE_NAME + " (" +
KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
DATETIME_COLUMN + " INTEGER NOT NULL, " +
TITLE_COLUMN + " TEXT NOT NULL, " +
CONTENT_COLUMN + " TEXT NOT NULL " + ")";
//資料庫物件
private SQLiteDatabase db;
//建構子
public ItemDAO(Context context){
db = MyDBHelper.getDatabase(context);
}
//關閉資料庫
public void closeDB(){
db.close();
}
//新增參數指定的物件
public Item insert(Item item){
//建立新增資料的 ContentValues 物件
ContentValues cv = new ContentValues();
//加入 ContentValues 物件包裝的新增資料
//cv.put(欄位名稱, 欄位資料)
cv.put(DATETIME_COLUMN, item.getDatetime());
cv.put(TITLE_COLUMN, item.getTitle());
cv.put(CONTENT_COLUMN, item.getContent());
//新增一筆資料並取的編號 db.insert(表格名稱, 沒有指定欄位值的預設值, 包裝新增資料的 cv 物件
long id = db.insert(TABLE_NAME, null, cv);
//設定編號
item.setId(id);
//回傳結果
return item;
}
//修改參數指定的物件
public boolean update(Item item){
//建立修改資料的 ContentValues 物件
ContentValues cv = new ContentValues();
//加入 contentValues 物件的包裝的修改資料
cv.put(DATETIME_COLUMN, item.getDatetime());
cv.put(TITLE_COLUMN, item.getTitle());
cv.put(CONTENT_COLUMN, item.getContent());
//設定修改資料的條件編號 格式為「欄位名稱 = 資料」
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;
}
//讀取所有記事資料
//Cursor 是一個 JAVA 介面,代表一個查詢後的結果,透過它的方法,可指向結果中的其中一筆記錄
// KEY_ID + " DESC" 改ORDER BY條件可以做排序 大-->小
public List<Item> getAll(){
List<Item> result = new ArrayList<>();
Cursor cursor = db.query(TABLE_NAME,
null,
null,
null,
null,
null,
KEY_ID + " DESC ");
while (cursor.moveToNext()){
result.add(getRecord(cursor));
}
cursor.close();
return result;
}
//取得指定編號的資料物件
public Item get(long id){
//準備回傳結果用的物件
Item item = null;
//使用編號為查詢條件
String where = KEY_ID + "=" + id;
//執行查詢
Cursor result = db.query(TABLE_NAME,
null,
null,
null,
null,
null,
null);
//如果有查詢結果
if (result.moveToFirst()){
//讀取包裝一筆資料物件
item = getRecord(result);
}
result.close();
return item;
}
//把 Cursor 目前的資料包裝為物件
private Item getRecord(Cursor cursor) {
//準備回傳結果用的物件
Item result = new Item();
result.setId(cursor.getLong(0));
result.setDatetime(cursor.getLong(1));
result.setTitle(cursor.getString(2));
result.setContent(cursor.getString(3));
//回傳結果
return result;
}
//取得資料數量
public int getCount(){
int result = 0;
//rawQuery是SQLite的查詢語法。使用rawQuery會以Cursor型別傳回執行結果或查詢資料
Cursor cursor = db.rawQuery("SELECT COUNT(*) FROM " + TABLE_NAME, null);
if (cursor.moveToNext()){
result = cursor.getInt(0);
}
return result;
}
//建立範例資料
public void sample(){
Item item = new Item(0, new Date().getTime(), "範例資料一", "記事資料一");
Item item2 = new Item(0, new Date().getTime(), "範例資料二", "記事資料二");
Item item3 = new Item(0, new Date().getTime(), "範例資料三", "記事資料三");
Item item4 = new Item(0, new Date().getTime(), "範例資料四", "記事資料四");
Item item5 = new Item(0, new Date().getTime(), "範例資料五", "記事資料五");
Item item6 = new Item(0, new Date().getTime(), "範例資料六", "記事資料六");
insert(item);
insert(item2);
insert(item3);
insert(item4);
insert(item5);
insert(item6);
}
}
public class Item {
private long id;
private long datetime;
private String title;
private String content;
public Item(){
}
public Item(long id, long datetime, String title, String content) {
this.id = id;
this.datetime = datetime;
this.title = title;
this.content = content;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public long getDatetime() {
return datetime;
}
//裝置區域的日期時間
public String getLocationDatetime(){
return String.format(Locale.getDefault(), "%tF %<tR", new Date(datetime));
}
public void setDatetime(long datetime) {
this.datetime = datetime;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
以上完成與資料庫相關的類別
參考 http://www.codedata.com.tw/mobile/android-tutorial-the-3rd-class-3-sqlite
RecyclerView
RecyclerView 小工具是比較進階和彈性的 ListView 版本。這個小工具是一個用來顯示大型資料集的容器,只要維護少數幾個視圖,就可極有效率地捲動資料集。您的資料集元素在執行階段會根據使用者操作動作或網路事件而變更。
RecyclerView 類別會提供下列項目,簡化大型資料集的顯示和處理方式:
- 版面配置管理員,用來將項目定位 (將它設為橫向或直向,或者以網格形式顯示)
- 常見項目操作 (例如移除或新增項目) 的預設動畫
- CoordinatorLayout 也只支援 RecyclerView 而不支援 LisView
- RecyclerView 架構,比 ListView多了一個LayoutManager
如果要使用 RecyclerView 小工具,您必須指定配接器和版面配置管理員。
如要建立配接器,請延伸 RecyclerView.Adapter 類別。
RecyclerView 提供下列內建的版面配置管理員:
- LinearLayoutManager 在垂直或水平捲動清單中顯示項目。
- GridLayoutManager 會在網格中顯示項目。
- StaggeredGridLayoutManager 會在交錯網格中顯示項目。
使用 RecyclerView,先在 gradle 裡面加入 v7 support libraries
dependencies {
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support:recyclerview-v7:25.3.1'
}
從繼承關係得知,RecyclerView 僅是一個 ViewGroup,而非是 AdapterView。
RecyclerView 也有自己的 RecyclerView.Adapter
但它的 Adapter 用法不同於 ListView 的 Adapter。
- Adapter - 負責把 Dataset 裡面的資料,轉成 view 給 RecyclerView 顯示
- Position - 在 Adapter 裡面的 data item 的位置
- Index - 已掛上 RecyclerView 裡面的 child-view 的位置,用在 getChildAt(int)
- Binding - 把資料變成可視的 view 的過程
- Recycle(view) - 之前曾經被拿來顯示過的 view,現在放在 cache 裡面留待下次使用
- Dirty(view) - 需要重新自資料更新外觀的 view
- Scrap(view) - 在 Layout 過程被拿下來但是尚未完全 detached。(從原始碼來看,mAttachScrap 放在 class Recycler 底下。
建立資料來源類別 – RecyclerView.Adapter
ViewHolder
Adapter 需要一個 ViewHolder,原本就有一個把所有基本東西都做完的抽象類別 RecyclerView.ViewHolder,我們只要實作它的 constructor 就好public class MyViewHolder extends RecyclerView.ViewHolder {
public TextView title_text, datetime_text;
public ImageView image_edit, image_delete;
public MyViewHolder(View itemView) {
super(itemView);
title_text = itemView.findViewById(R.id.title_Text);
datetime_text = itemView.findViewById(R.id.datetime_Text);
image_edit = itemView.findViewById(R.id.image_edit);
image_delete = itemView.findViewById(R.id.image_delete);
}
}
Adapter
RecyclerView.Adapter 負責提供 child-view 給 RecyclerView 使用,同時也負責把資料跟 child-view 綁在一起。實際上並不是直接對上 View,而是對上 ViewHolder。從設計上來看就是強迫使用 ViewHolder pattern 來增加效率。
此抽象類別需要實作這三個 method
- getItemCount() - Adapter 自己才知道如何儲存資料,所以它自己也才知道該怎麼數有多少資料
- onCreateViewHolder(ViewGroup, int) - 現有的 ViewHolder 不夠用,要求 Adapter 產生一個新的
- onBindViewHolder(ViewHolder, int) - 重用之前產生的 ViewHolder,把特定位置的資料連結上去準備顯示
public class MyRecyclerViewAdapter extends RecyclerView.Adapter<MyViewHolder>{
private Context mContext;
private List<Item> items;
private List<Item> filterList;
private ItemDAO itemDAO;
public MyRecyclerViewAdapter (Context mContext, List<Item> items){
this.mContext = mContext;
this.items = items;
//將原始列表複製到過濾器列表中
this.filterList = new ArrayList<Item>();
this.filterList.addAll(this.items);
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_row, parent, false);
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
final Item singleItem = filterList.get(position); //filter
holder.title_text.setText(singleItem.getTitle());
holder.datetime_text.setText(singleItem.getLocationDatetime());
//edit
holder.image_edit.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
editTaskDialog(singleItem);
}
});
//delete
holder.image_delete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final AlertDialog.Builder dialog = new AlertDialog.Builder(mContext);
dialog.setTitle("刪除資料\n" + singleItem.getTitle());
dialog.setPositiveButton("確認", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
filterList.remove(singleItem);
itemDAO = new ItemDAO(mContext);
itemDAO.delete(singleItem.getId());
notifyDataSetChanged();
}
});
dialog.setNegativeButton("取消", null);
dialog.show();
}
});
}
private void editTaskDialog(final Item singleItem) {
LayoutInflater inflater = LayoutInflater.from(mContext);
View subView = inflater.inflate(R.layout.add_item, null);
final EditText editTitle = subView.findViewById(R.id.editTitle);
final EditText editContent = subView.findViewById(R.id.editContent);
final TextView modifyDateTime = subView.findViewById(R.id.add_dateTime);
if (singleItem != null){
//如果資料不是空的,先取得目前項目資料
editTitle.setText(singleItem.getTitle());
editContent.setText(singleItem.getContent());
modifyDateTime.setText(singleItem.getLocationDatetime());
AlertDialog.Builder dialog = new AlertDialog.Builder(mContext);
dialog.setTitle("修改");
dialog.setView(subView);
dialog.create();
dialog.setPositiveButton("修改", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String titleStr = editTitle.getText().toString();
singleItem.setTitle(titleStr);
String contentStr = editContent.getText().toString();
singleItem.setContent(contentStr);
singleItem.setDatetime(new Date().getTime());
long modifyTime = new Date().getTime();
singleItem.setDatetime(modifyTime);
modifyDateTime.setText("modify:" + singleItem.getLocationDatetime());
if (TextUtils.isEmpty(titleStr)){
Snackbar.make(editTitle, "沒有資料", Snackbar.LENGTH_SHORT).show();
}else {
//要先初始化
itemDAO = new ItemDAO(mContext);
itemDAO.update(singleItem);
//set on UI Thread
((Activity)mContext).runOnUiThread(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
});
}
}
});
dialog.setNegativeButton("取消", null);
dialog.show();
}
}
@Override
public int getItemCount() {
return (null != filterList ? filterList.size() : items.size());
}
//SearView
public void filter(final String text){
filterList.clear();
if (TextUtils.isEmpty(text)){
filterList.addAll(items);
} else {
for (Item item : items){
if (item.getTitle().contains(text) ||
item.getLocationDatetime().contains(text)){
filterList.add(item);
}
}
}
notifyDataSetChanged();
}
}
LayoutManager
import android.support.v7.widget.SearchView;
最後回到 MainActivitypublic class MainActivity extends AppCompatActivity
implements SearchView.OnQueryTextListener{
private Toolbar toolbar;
private FloatingActionButton fab;
private RecyclerView recyclerView;
private MyRecyclerViewAdapter adapter;
private LinearLayoutManager layoutManager;
private Item item = new Item();
private ItemDAO itemDAO;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initHandler();
// 建立資料庫物件
itemDAO = new ItemDAO(getApplicationContext());
// 如果資料庫是空的,就建立一些範例資料
// 這是為了方便測試用的,完成應用程式以後可以拿掉
if (itemDAO.getCount() == 0) {
itemDAO.sample();
}
}
private void initHandler() {
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
addNoteDialog();
}
});
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setHasFixedSize(true);
itemDAO = new ItemDAO(this);
List<Item> allItems = itemDAO.getAll();
if (allItems.size() > 0) {
adapter = new MyRecyclerViewAdapter(this, allItems);
recyclerView.setAdapter(adapter);
} else {
Snackbar.make(fab, "目前沒有資料", Snackbar.LENGTH_SHORT).show();
}
}
private void addNoteDialog() {
LayoutInflater inflater = LayoutInflater.from(this);
View subView = inflater.inflate(R.layout.add_item, null);
final EditText title = subView.findViewById(R.id.editTitle);
final EditText content = subView.findViewById(R.id.editContent);
final TextView dataTime = subView.findViewById(R.id.add_dateTime);
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle("新增記事資料:");
dialog.setView(subView);
dialog.create();
dialog.setPositiveButton("新增", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
item.setDatetime(new Date().getTime());
dataTime.setText(item.getLocationDatetime());
String titleStr = title.getText().toString();
String contentStr = content.getText().toString();
item.setTitle(titleStr);
item.setContent(contentStr);
itemDAO.insert(item);
runOnUiThread(new Runnable() {
@Override
public void run() {
initHandler();
}
});
}
});
dialog.setNegativeButton("取消", null);
dialog.show();
}
private void initView() {
toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
fab = findViewById(R.id.fab);
recyclerView = findViewById(R.id.recycler);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.search_menu, menu );
final MenuItem searchItem = menu.findItem(R.id.action_search);
final SearchView searchView = (SearchView)searchItem.getActionView();
searchView.setOnQueryTextListener(this);
searchView.setIconifiedByDefault(false);
searchView.setSubmitButtonEnabled(true);
searchView.setQueryHint("Search Here");
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
return super.onOptionsItemSelected(item);
}
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
List<Item> allItem = itemDAO.getAll();
adapter = new MyRecyclerViewAdapter(this, allItem);
adapter.filter(newText);
recyclerView.setAdapter(adapter);
return true;
}
}
Layout
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.claire.sqliteandrecyclerviewandsearchviewfilter.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:theme="@style/AppTheme.AppBarOverlay"/>
</android.support.design.widget.AppBarLayout>
<LinearLayout
android:id="@+id/linear"
android:layout_marginTop="?attr/actionBarSize"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
app:layout_anchor="@id/linear"
app:layout_anchorGravity="bottom|right"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:clickable="true"
app:srcCompat="@android:drawable/ic_menu_add"
android:focusable="true"/>
</android.support.design.widget.CoordinatorLayout>
add_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#5b92df"
android:padding="16dp"
android:orientation="vertical">
<EditText
android:id="@+id/editTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPersonName"
android:hint="@string/title"/>
<EditText
android:id="@+id/editContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPersonName"
android:hint="@string/content"/>
<TextView
android:id="@+id/add_dateTime"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
item_row.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_margin="3dp"
app:cardCornerRadius="6dp"
app:cardElevation="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#a8faf6f7"
android:orientation="vertical">
<TextView
android:id="@+id/title_Text"
android:padding="3dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""
android:textColor="#000000"
android:textSize="24sp"
android:textStyle="bold"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="3dp"
android:background="#3e56ddec"
android:orientation="horizontal">
<TextView
android:id="@+id/datetime_Text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
android:text=""
android:textColor="#000000"
android:textSize="16sp"
android:textStyle="italic"/>
<ImageView
android:id="@+id/image_edit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:contentDescription="@string/app_name"
app:srcCompat="@android:drawable/ic_menu_edit"/>
<ImageView
android:id="@+id/image_delete"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:contentDescription="@string/app_name"
app:srcCompat="@android:drawable/ic_menu_delete"/>
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
GitHub |
留言
張貼留言