前面提到,筆記本列表頁(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:筆記本名稱
打開新建的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ù)
打開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的返回值。
打開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)。