前面提到慕的,筆記本列表頁(yè)面具有以下功能:
- 展示全部筆記本
- 創(chuàng)建新的筆記本
- 選擇筆記本虐沥,并向調(diào)用NotebooksActivity的上一級(jí)activity返回對(duì)應(yīng)的筆記本ID
可以做如下設(shè)計(jì):
- 用RecyclerView來(lái)顯示筆記本列表,其中最上面一項(xiàng)是“全部筆記”扬跋,意思是脆栋,不選中任何筆記本時(shí),則令筆記列表顯示所有筆記鸠匀,否則僅顯示選中的筆記本中的內(nèi)容
- 添加一個(gè)漂浮按鈕來(lái)觸發(fā)新建筆記本操作蕉斜。點(diǎn)擊漂浮按鈕后,彈出一個(gè)對(duì)話。對(duì)話框包含一個(gè)編輯框宅此,用戶手動(dòng)輸入筆記本名字后確認(rèn)机错,則向數(shù)據(jù)庫(kù)中新插入一條記錄。插入操作完成后刷新筆記本列表
- 點(diǎn)擊列表中的某一條目之后父腕,關(guān)閉頁(yè)面弱匪,并向上一級(jí)頁(yè)面返回:
a. 選擇“全部筆記”則返回0
b. 選擇其它筆記本條目則返回筆記本id
1. 創(chuàng)建Notebook類
之前我們用Note類來(lái)表示一條筆記。同樣的道理璧亮,我們創(chuàng)建一個(gè)Notebook類來(lái)表示筆記本萧诫。
在“model”包下新建Notebook類,并添加如下的屬性:
- id:筆記本的唯一id
- name:筆記本名稱
打開(kāi)新建的Notebook類枝嘶,編寫代碼如下:
public class Notebook {
private long id;
private String name;
public Notebook(long id, String name) {
this.id = id;
this.name = name;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2. 向NoteDAO類增加筆記本數(shù)據(jù)訪問(wèn)函數(shù)
打開(kāi)NoteDAO類财搁,添加以下方法
- insertNotebook():向數(shù)據(jù)庫(kù)插入一個(gè)筆記本對(duì)象
- queryAllNotebooks():查詢?nèi)抗P記本
- queryNotebookById():根據(jù)id查找筆記本對(duì)象
insertNotebook()
/**
* 向數(shù)據(jù)庫(kù)中插入一個(gè)筆記本
* @param noteBook 被添加到數(shù)據(jù)庫(kù)的筆記本對(duì)象
* @return 插入成功后更新noteBook的id并將note對(duì)象返回;插入失敗則返回null
*/
public Notebook insertNotebook(Notebook noteBook) {
if (noteBook == null) {
return noteBook;
}
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COL_NOTEBOOK_NAME, noteBook.getName());
long id = db.insert(TABLE_NOTEBOOK, null, values);
if (id <= 0) {
return null;
}
noteBook.setId(id);
return noteBook;
}
queryAllNotebooks()
/**
* 獲取全部筆記本
* @return 全部筆記本的列表躬络。如果沒(méi)有任何筆記本尖奔,則返回的列表長(zhǎng)度為0
*/
public ArrayList<Notebook> queryAllNotebooks() {
ArrayList<Notebook> nbs = new ArrayList<>();
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursor cursor = db.query(TABLE_NOTEBOOK, null, null, null, null, null, null);
if (cursor != null) {
try {
while (cursor.moveToNext()) {
long id = cursor.getLong(0);
String name = cursor.getString(1);
Notebook nb = new Notebook(id, name);
nbs.add(nb);
}
} finally {
cursor.close();
}
}
return nbs;
}
queryNotebookById
/**
* 根據(jù)id獲取對(duì)應(yīng)的筆記本
* @param id 筆記本id
* @return 如果存在id對(duì)應(yīng)的筆記本,則創(chuàng)建對(duì)象并返回穷当,否則返回null
*/
public Notebook queryNotebookById(long id) {
if (id <= 0) {
return null;
}
SQLiteDatabase db = dbHelper.getReadableDatabase();
String selection = COL_ID + "=" + id;
Cursor cursor = db.query(TABLE_NOTEBOOK, null, selection, null, null, null, null, null);
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
String name = cursor.getString(1);
Notebook nb = new Notebook(id, name);
return nb;
}
} finally {
cursor.close();
}
}
return null;
}
這樣提茁,對(duì)筆記本數(shù)據(jù)進(jìn)行操作的幾個(gè)方法就完成了。
2. 展示筆記本列表
展示筆記本列表與原來(lái)的全部筆記頁(yè)面是大體相似的馁菜。唯一的不同茴扁,是我們?cè)诹斜淼淖钋懊姹A粢粋€(gè)“全部筆記”固定項(xiàng)。
在NotebooksActivity的布局文件activity_notebooks.xml中增加RecyclerView:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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.jing.app.sn.NotebooksActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/notebook_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
接下來(lái)汪疮,為筆記本列表項(xiàng)創(chuàng)建布局峭火,新建布局文件notebook_list_item.xml:
在這里,簡(jiǎn)單的讓它只顯示筆記本的名字智嚷,因此卖丸,只包含一個(gè)TextView,以及一條分隔線:
<?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="72dp"
android:orientation="vertical"
android:clickable="true"
android:background="@drawable/note_list_item_bg">
<!--筆記本名字-->
<TextView
android:id="@+id/tv_nb_name"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:gravity="center_vertical"
android:text="這里是筆記本名字"
android:maxLines="2"
android:textColor="@color/black"
android:textSize="16sp" />
<!--分隔線-->
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="#999999"/>
</LinearLayout>
從布局文件中盏道,我們可以看到稍浆,顯示筆記本名字的TextView的id叫做“tv_nb_name”。
現(xiàn)在猜嘱,我們?cè)偃otebooksActivity中實(shí)現(xiàn)這個(gè)列表衅枫。
先創(chuàng)建內(nèi)部類NotebookViewHolder以實(shí)現(xiàn)我們自己的ViewHolder:
private class NotebookViewHolder extends RecyclerView.ViewHolder {
// 只有一個(gè)文本視圖用以顯示名字
private TextView notebookName;
public NotebookViewHolder(View itemView) {
super(itemView);
notebookName = itemView.findViewById(R.id.tv_nb_name);
}
}
接下來(lái),創(chuàng)建適配器類NotebookAdapter:
private class NotebookAdapter extends RecyclerView.Adapter<NotebookViewHolder> {
// 筆記本列表
private ArrayList<Notebook> noteBooks = new ArrayList<>();
// 設(shè)置筆記本列表內(nèi)容
public void setNotes(ArrayList<Notebook> nbs) {
// 現(xiàn)將原列表清空朗伶,再將傳入的列表元素全部加入
this.noteBooks.clear();
if (nbs != null) {
this.noteBooks.addAll(nbs);
}
}
@Override
public NotebookViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// 根據(jù)列表項(xiàng)布局文件創(chuàng)建視圖對(duì)象
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(R.layout.notebook_list_item, parent, false);
// 基于上面的視圖對(duì)象創(chuàng)建ViewHolder對(duì)象并返回
NotebookViewHolder vh = new NotebookViewHolder(view);
return vh;
}
@Override
public void onBindViewHolder(NotebookViewHolder holder, int position) {
// 取對(duì)應(yīng)位置的筆記對(duì)象
final Notebook nb = noteBooks.get(position);
// 設(shè)置對(duì)應(yīng)ViewHolder對(duì)象中各視覺(jué)元素的值
holder.notebookName.setText(nb.getName());
// 響應(yīng)點(diǎn)擊事件
// 處理列表項(xiàng)點(diǎn)擊事件
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onSelectNotebook(nb);
}
});
}
@Override
public int getItemCount() {
return noteBooks.size();
}
}
可以注意到弦撩,在處理列表項(xiàng)點(diǎn)擊事件時(shí),調(diào)用了onSelectNotebook()方法论皆,但是目前并沒(méi)有創(chuàng)建它益楼,因此會(huì)出現(xiàn)報(bào)錯(cuò)歧斟。
在NotebooksActivity中(適配器類之外)創(chuàng)建onSelectNotebook()方法,它的參數(shù)就是當(dāng)前點(diǎn)擊的筆記本對(duì)象偏形,它執(zhí)行的操作就是關(guān)閉筆記本列表頁(yè)面静袖,并向上一級(jí)activity返回這個(gè)對(duì)象的id和名字。編寫代碼如下:
private void onSelectNotebook(Notebook notebook) {
// 創(chuàng)建一個(gè)Intent對(duì)象俊扭,用來(lái)攜帶要返回的參數(shù)
Intent intent = new Intent();
intent.putExtra("notebookId", notebook.getId());
intent.putExtra("notebookName", notebook.getName());
// 設(shè)置返回結(jié)果队橙,返回碼為1
setResult(1, intent);
//關(guān)閉頁(yè)面
finish();
}
接下來(lái),我們查詢數(shù)據(jù)庫(kù)萨惑,獲取筆記本列表捐康,并使其顯示出來(lái)。
首先庸蔼,為NotebooksActivity添加RecyclerView及其適配器類型的成員:
public class NotebooksActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private NotebookAdapter mAdapter;
...
}
然后找到onCreate()方法解总,添加代碼以對(duì)RecyclerView進(jìn)行初始化:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_notebooks);
// 添加代碼:初始化并設(shè)置RecyclerView
mRecyclerView = findViewById(R.id.notebook_list);
// 設(shè)定為垂直列表
LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mRecyclerView.setLayoutManager(layoutManager);
mAdapter = new NotebookAdapter();
mRecyclerView.setAdapter(mAdapter);
}
這里僅僅是初始化了RecyclerView,還沒(méi)有加載數(shù)據(jù)姐仅。重寫NotebooksActivity類的onResume()方法花枫,并通過(guò)AsyncTask對(duì)筆記本數(shù)據(jù)進(jìn)行異步加載:
@Override
protected void onResume() {
super.onResume();
asyncLoadNotebooks();
}
private void asyncLoadNotebooks() {
AsyncTask<Void, Void, ArrayList<Notebook>> task = new AsyncTask<Void, Void, ArrayList<Notebook>>() {
@Override
protected ArrayList<Notebook> doInBackground(Void... voids) {
ArrayList<Notebook> nbs = NoteDAO.getInstance(NotebooksActivity.this.getApplicationContext()).queryAllNotebooks();
// 在列表最前添加"全部筆記"項(xiàng),id設(shè)為0
Notebook allNotes = new Notebook(0, getString(R.string.all_notes));
nbs.add(0, allNotes);
return nbs;
}
@Override
protected void onPostExecute(ArrayList<Notebook> notebooks) {
mAdapter.setNotes(notebooks);
mAdapter.notifyDataSetChanged();
}
};
task.execute();
}
在這里掏膏,我們通過(guò)創(chuàng)建一個(gè)匿名的AsyncTask子類來(lái)實(shí)現(xiàn)筆記本數(shù)據(jù)的異步加載:
- 在doInBackground()方法中劳翰,首先從數(shù)據(jù)庫(kù)中查詢?nèi)康墓P記本,得到一個(gè)列表馒疹。然后創(chuàng)建一個(gè)特殊的Notebook對(duì)象佳簸,將其id設(shè)置為0(從數(shù)據(jù)庫(kù)中來(lái)的筆記本id不可能為0),名字設(shè)置為“全部筆記”颖变,并插入到列表最前面生均。
- 在onPostExecute()方法中,更新適配器類持有的數(shù)據(jù)腥刹,并通知數(shù)據(jù)集變化马胧。
3. 處理返回結(jié)果
之前,我們講到肛走,從筆記列表頁(yè)面調(diào)用筆記本列表頁(yè)面漓雅,選擇筆記本后將會(huì)把它的id返回過(guò)來(lái)录别。下面來(lái)處理這個(gè)返回值朽色。處理邏輯如下:
分析返回值,將頁(yè)面標(biāo)題設(shè)置為返回的筆記本名字组题,判斷返回的id:
- 如果為0葫男,則加載全部筆記,并設(shè)置頁(yè)面標(biāo)題為“全部筆記”
- 如果大于0崔列,則讀取該筆記本中所有的筆記梢褐,顯示在列表中
經(jīng)過(guò)這個(gè)處理旺遮,原來(lái)僅僅顯示全部筆記的主頁(yè)面,現(xiàn)在可以根據(jù)我們所選擇的筆記本盈咳,動(dòng)態(tài)的變換內(nèi)容了耿眉。
進(jìn)行以下修改:
為NoteListActivity類添加notebookId成員:
notebookId成員缺省為0,表明加載全部筆記鱼响。當(dāng)我們從筆記本列表中返回時(shí)鸣剪,用返回的新id值將其重新設(shè)置,以切換筆記本:
private long notebookId;
修改NoteListActivity類的異步加載類:
找到內(nèi)部類LoadAllNotesTask丈积,修改如下:
private class LoadAllNotesTask extends AsyncTask<Void, Void, ArrayList<Note>> {
private long notebookId;
public LoadAllNotesTask(long notebookId) {
this.notebookId = notebookId;
}
public LoadAllNotesTask() {
this(0);
}
@Override
protected void onPreExecute() {
mLoadingView.setVisibility(View.VISIBLE);
}
@Override
protected void onPostExecute(ArrayList<Note> notes) {
// 為適配器設(shè)置新的筆記列表
adapter.setNotes(notes);
// 通知RecyclerView刷新
adapter.notifyDataSetChanged();
// 關(guān)閉加載等待視圖
mLoadingView.setVisibility(View.GONE);
}
@Override
protected ArrayList<Note> doInBackground(Void... voids) {
ArrayList<Note> notes = noteRepository.getAllNotes();
// 從列表中去掉筆記本id不匹配的筆記對(duì)象
if (notebookId > 0) {
for (Iterator<Note> iterator = notes.iterator(); iterator.hasNext();) {
if (iterator.next().getNotebookId() != notebookId) {
iterator.remove();
}
}
}
return notes;
}
}
在原來(lái)的基礎(chǔ)上進(jìn)行了如下改動(dòng):
- 添加了私有成員notebookId筐骇,表示當(dāng)前頁(yè)面針對(duì)哪個(gè)筆記本
- 添加了兩個(gè)構(gòu)造方法,一個(gè)接收指定的筆記本id來(lái)初始化notebookId成員江滨;另一個(gè)缺省的將notebookId設(shè)置為0铛纬,即全部筆記
- 在doInBackground()中,當(dāng)notebookId>0唬滑,也就是指定了當(dāng)前頁(yè)面對(duì)應(yīng)的筆記本時(shí)告唆,進(jìn)行一次循環(huán)處理,去掉從數(shù)據(jù)庫(kù)中取得的筆記列表中不屬于此筆記本的項(xiàng)目
修改onResume()方法中對(duì)LoadAllNotesTask的創(chuàng)建和調(diào)用
@Override
protected void onResume() {
super.onResume();
// 修改:創(chuàng)建對(duì)象時(shí)加上筆記本id參數(shù)
LoadAllNotesTask task = new LoadAllNotesTask(notebookId);
task.execute();
}
重寫NoteListActivity類的onActivityResult()方法
onActivityResult()方法專門用于處理被調(diào)用activity的返回值晶密。
打開(kāi)NoteListActivity類悔详,重寫onActivityResult()方法:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1 && resultCode == 1) {
// 取得返回參數(shù)
notebookId = data.getLongExtra("notebookId", 0);
String notebookName = data.getStringExtra("notebookName");
// 設(shè)置頁(yè)面標(biāo)題
setTitle(notebookName);
}
}
4. 實(shí)現(xiàn)新建筆記本
回到NotebooksActivity,先在它的布局文件中添加漂浮按鈕惹挟∏洋Γ可以簡(jiǎn)單的把原來(lái)在筆記列表頁(yè)面中添加的漂浮按鈕拷貝過(guò)來(lái)。完畢后布局內(nèi)容如下:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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.jing.app.sn.NotebooksActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/notebook_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab_add_notebook"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:clickable="true"
android:onClick="onNewNotebook"
app:srcCompat="@drawable/ic_add" />
</FrameLayout>
這里指定按鈕點(diǎn)擊處理函數(shù)為onNewNotebook()连锯,則在NotebooksActivity類中添加此方法:
public void onNewNotebook(View view) {
}
在這個(gè)方法里面归苍,我們彈出一個(gè)對(duì)話框,里面包含一個(gè)編輯框运怖,用來(lái)填寫新建的筆記本的名字拼弃,點(diǎn)擊確認(rèn)后將其保存到數(shù)據(jù)庫(kù)。
為onNewNotebook()方法添加代碼如下:
public void onNewNotebook(View view) {
// 創(chuàng)建編輯框?qū)ο? final EditText notebookNameEdit = new EditText(this);
AlertDialog.Builder builder = new AlertDialog.Builder(this)
.setTitle(R.string.new_notebook)
.setView(notebookNameEdit) // 將編輯框?qū)ο蠹尤雽?duì)話框
.setNegativeButton("取消", null)
.setPositiveButton("確認(rèn)", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String name = notebookNameEdit.getEditableText().toString();
if (!TextUtils.isEmpty(name)) {
Notebook nb = new Notebook(0, name);
nb = NoteDAO.getInstance(NotebooksActivity.this).insertNotebook(nb);
if (nb != null) {
// 插入成功摇展,刷新筆記本列表
asyncLoadNotebooks();
} else {
// 插入失敗吻氧,提示用戶
Toast.makeText(NotebooksActivity.this, "保存筆記本失敗", Toast.LENGTH_SHORT).show();
}
}
}
});
builder.show();
}
這樣,到目前階段咏连,可以添加新的筆記本了盯孙。選擇新的筆記本之后,UI切回筆記列表頁(yè)面祟滴,且標(biāo)題改為當(dāng)前選中的筆記本標(biāo)題:
然而振惰,新的筆記本里面并沒(méi)有任何筆記,這一點(diǎn)要通過(guò)修改新建筆記頁(yè)面來(lái)實(shí)現(xiàn)垄懂。