任务6.1:实现笔记本(2)

前面提到,笔记本列表页面具有以下功能:

  • 展示全部笔记本
  • 创建新的笔记本
  • 选择笔记本,并向调用NotebooksActivity的上一级activity返回对应的笔记本ID

可以做如下设计:

  • 用RecyclerView来显示笔记本列表,其中最上面一项是“全部笔记”,意思是,不选中任何笔记本时,则令笔记列表显示所有笔记,否则仅显示选中的笔记本中的内容
  • 添加一个漂浮按钮来触发新建笔记本操作。点击漂浮按钮后,弹出一个对话。对话框包含一个编辑框,用户手动输入笔记本名字后确认,则向数据库中新插入一条记录。插入操作完成后刷新笔记本列表
  • 点击列表中的某一条目之后,关闭页面,并向上一级页面返回:
    a. 选择“全部笔记”则返回0
    b. 选择其它笔记本条目则返回笔记本id

1. 创建Notebook类

之前我们用Note类来表示一条笔记。同样的道理,我们创建一个Notebook类来表示笔记本。
在“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类增加笔记本数据访问函数

打开NoteDAO类,添加以下方法

  • insertNotebook():向数据库插入一个笔记本对象
  • queryAllNotebooks():查询全部笔记本
  • queryNotebookById():根据id查找笔记本对象

insertNotebook()

    /**
     * 向数据库中插入一个笔记本
     * @param noteBook 被添加到数据库的笔记本对象
     * @return 插入成功后更新noteBook的id并将note对象返回;插入失败则返回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 全部笔记本的列表。如果没有任何笔记本,则返回的列表长度为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

    /**
     * 根据id获取对应的笔记本
     * @param id 笔记本id
     * @return 如果存在id对应的笔记本,则创建对象并返回,否则返回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;
    }

这样,对笔记本数据进行操作的几个方法就完成了。

2. 展示笔记本列表

展示笔记本列表与原来的全部笔记页面是大体相似的。唯一的不同,是我们在列表的最前面保留一个“全部笔记”固定项。

在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>

接下来,为笔记本列表项创建布局,新建布局文件notebook_list_item.xml:

在这里,简单的让它只显示笔记本的名字,因此,只包含一个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”。

现在,我们再去NotebooksActivity中实现这个列表。
先创建内部类NotebookViewHolder以实现我们自己的ViewHolder:

    private class NotebookViewHolder extends RecyclerView.ViewHolder {
        // 只有一个文本视图用以显示名字
        private TextView notebookName;

        public NotebookViewHolder(View itemView) {
            super(itemView);
            notebookName = itemView.findViewById(R.id.tv_nb_name);
        }
    }

接下来,创建适配器类NotebookAdapter:

    private class NotebookAdapter extends RecyclerView.Adapter<NotebookViewHolder> {

        // 笔记本列表
        private ArrayList<Notebook> noteBooks = new ArrayList<>();

        // 设置笔记本列表内容
        public void setNotes(ArrayList<Notebook> nbs) {
            // 现将原列表清空,再将传入的列表元素全部加入
            this.noteBooks.clear();
            if (nbs != null) {
                this.noteBooks.addAll(nbs);
            }
        }

        @Override
        public NotebookViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            // 根据列表项布局文件创建视图对象
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            View view = inflater.inflate(R.layout.notebook_list_item, parent, false);
            // 基于上面的视图对象创建ViewHolder对象并返回
            NotebookViewHolder vh = new NotebookViewHolder(view);
            return vh;
        }

        @Override
        public void onBindViewHolder(NotebookViewHolder holder, int position) {
           // 取对应位置的笔记对象
            final Notebook nb = noteBooks.get(position);
            // 设置对应ViewHolder对象中各视觉元素的值
            holder.notebookName.setText(nb.getName());

            // 响应点击事件
            // 处理列表项点击事件
            holder.itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    onSelectNotebook(nb);
                }
            });
        }

        @Override
        public int getItemCount() {
            return noteBooks.size();
        }
    }

可以注意到,在处理列表项点击事件时,调用了onSelectNotebook()方法,但是目前并没有创建它,因此会出现报错。

在NotebooksActivity中(适配器类之外)创建onSelectNotebook()方法,它的参数就是当前点击的笔记本对象,它执行的操作就是关闭笔记本列表页面,并向上一级activity返回这个对象的id和名字。编写代码如下:

   private void onSelectNotebook(Notebook notebook) {
       // 创建一个Intent对象,用来携带要返回的参数
       Intent intent = new Intent();
       intent.putExtra("notebookId", notebook.getId());
       intent.putExtra("notebookName", notebook.getName());

       // 设置返回结果,返回码为1
       setResult(1, intent);

       //关闭页面
       finish();
   }

接下来,我们查询数据库,获取笔记本列表,并使其显示出来。
首先,为NotebooksActivity添加RecyclerView及其适配器类型的成员:

public class NotebooksActivity extends AppCompatActivity {
    private RecyclerView mRecyclerView;
    private NotebookAdapter mAdapter;
    ...
}

然后找到onCreate()方法,添加代码以对RecyclerView进行初始化:

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

        // 添加代码:初始化并设置RecyclerView
        mRecyclerView = findViewById(R.id.notebook_list);
        // 设定为垂直列表
        LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
        mRecyclerView.setLayoutManager(layoutManager);

        mAdapter = new NotebookAdapter();
        mRecyclerView.setAdapter(mAdapter);
    }

这里仅仅是初始化了RecyclerView,还没有加载数据。重写NotebooksActivity类的onResume()方法,并通过AsyncTask对笔记本数据进行异步加载:

   @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();
               // 在列表最前添加"全部笔记"项,id设为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();
   }

在这里,我们通过创建一个匿名的AsyncTask子类来实现笔记本数据的异步加载:

  • 在doInBackground()方法中,首先从数据库中查询全部的笔记本,得到一个列表。然后创建一个特殊的Notebook对象,将其id设置为0(从数据库中来的笔记本id不可能为0),名字设置为“全部笔记”,并插入到列表最前面。
  • 在onPostExecute()方法中,更新适配器类持有的数据,并通知数据集变化。

3. 处理返回结果

之前,我们讲到,从笔记列表页面调用笔记本列表页面,选择笔记本后将会把它的id返回过来。下面来处理这个返回值。处理逻辑如下:
分析返回值,将页面标题设置为返回的笔记本名字,判断返回的id:

  • 如果为0,则加载全部笔记,并设置页面标题为“全部笔记”
  • 如果大于0,则读取该笔记本中所有的笔记,显示在列表中

经过这个处理,原来仅仅显示全部笔记的主页面,现在可以根据我们所选择的笔记本,动态的变换内容了。

进行以下修改:

为NoteListActivity类添加notebookId成员:

notebookId成员缺省为0,表明加载全部笔记。当我们从笔记本列表中返回时,用返回的新id值将其重新设置,以切换笔记本:

    private long notebookId;

修改NoteListActivity类的异步加载类:

找到内部类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) {
            // 为适配器设置新的笔记列表
            adapter.setNotes(notes);
            // 通知RecyclerView刷新
            adapter.notifyDataSetChanged();
            // 关闭加载等待视图
            mLoadingView.setVisibility(View.GONE);
        }

        @Override
        protected ArrayList<Note> doInBackground(Void... voids) {
            ArrayList<Note> notes = noteRepository.getAllNotes();
            // 从列表中去掉笔记本id不匹配的笔记对象
            if (notebookId > 0) {
                for (Iterator<Note> iterator = notes.iterator(); iterator.hasNext();) {
                    if (iterator.next().getNotebookId() != notebookId) {
                        iterator.remove();
                    }
                }
            }
            return notes;
        }
    }

在原来的基础上进行了如下改动:

  • 添加了私有成员notebookId,表示当前页面针对哪个笔记本
  • 添加了两个构造方法,一个接收指定的笔记本id来初始化notebookId成员;另一个缺省的将notebookId设置为0,即全部笔记
  • 在doInBackground()中,当notebookId>0,也就是指定了当前页面对应的笔记本时,进行一次循环处理,去掉从数据库中取得的笔记列表中不属于此笔记本的项目

修改onResume()方法中对LoadAllNotesTask的创建和调用

    @Override
    protected void onResume() {
        super.onResume();
        // 修改:创建对象时加上笔记本id参数
        LoadAllNotesTask task = new LoadAllNotesTask(notebookId);
        task.execute();
    }

重写NoteListActivity类的onActivityResult()方法

onActivityResult()方法专门用于处理被调用activity的返回值。
打开NoteListActivity类,重写onActivityResult()方法:

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == 1 && resultCode == 1) {
            // 取得返回参数
            notebookId = data.getLongExtra("notebookId", 0);
            String notebookName = data.getStringExtra("notebookName");
            // 设置页面标题
            setTitle(notebookName);
        }
    }

4. 实现新建笔记本

回到NotebooksActivity,先在它的布局文件中添加漂浮按钮。可以简单的把原来在笔记列表页面中添加的漂浮按钮拷贝过来。完毕后布局内容如下:

<?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>

这里指定按钮点击处理函数为onNewNotebook(),则在NotebooksActivity类中添加此方法:

    public void onNewNotebook(View view) {
    }

在这个方法里面,我们弹出一个对话框,里面包含一个编辑框,用来填写新建的笔记本的名字,点击确认后将其保存到数据库。

为onNewNotebook()方法添加代码如下:

    public void onNewNotebook(View view) {
        // 创建编辑框对象
        final EditText notebookNameEdit = new EditText(this);
        AlertDialog.Builder builder = new AlertDialog.Builder(this)
                .setTitle(R.string.new_notebook)
                .setView(notebookNameEdit)  // 将编辑框对象加入对话框
                .setNegativeButton("取消", null)
                .setPositiveButton("确认", 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切回笔记列表页面,且标题改为当前选中的笔记本标题:

然而,新的笔记本里面并没有任何笔记,这一点要通过修改新建笔记页面来实现。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容