美文网首页
任务6.1:实现笔记本(2)

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

作者: jingz课程 | 来源:发表于2018-05-07 23:40 被阅读0次

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

  • 展示全部笔记本
  • 创建新的笔记本
  • 选择笔记本,并向调用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切回笔记列表页面,且标题改为当前选中的笔记本标题:

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

相关文章

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

    前面提到,笔记本列表页面具有以下功能: 展示全部笔记本 创建新的笔记本 选择笔记本,并向调用NotebooksAc...

  • 任务6.1:实现笔记本(3)

    上一篇教程的最后,我们实现了新建笔记本以及笔记本的切换。然后新的笔记本中并没有关联任何一条笔记。我们需要修改新建笔...

  • 任务6.1:实现笔记本(1)

    设计思路 笔记本与笔记是一对多的关系: 每条笔记属于一个笔记本 一个笔记本包含多条笔记 1. 数据库设计 首先,我...

  • 2018-10-08

    9月30日任务6.1 常见压缩文件 6.1 压缩打包介绍 6.2 gzip压缩工具 6.3 bzip2压缩工具 6...

  • 6.1每日任务

    6.1每日任务: 1 基进课。听课 作业 闯关 笔记 输出 2每日阅读《解读基金》这本书,并输出笔记。 3基进群爬...

  • 《30天读出生产力训练营》复盘

    2月8日给自己定下目标 1、完成所有的打卡任务,奖励自己,一直想换的新笔记本电脑。 2、实现高效阅读、养成阅读好习...

  • 第六章 需求分析与建模最佳实践

    6.1 需求分析建模的要点与误区 6.1.1 需求分析到底做什么 需求分析的任务不是分析系统如何实现用户的需要,而...

  • Eggjs使用总结

    1. router2. Controller3. Service4. 日志5. 定时任务6. 读取MySQL6.1...

  • 分享-python多任务

    多任务的实现 多任务处理,同一时刻进行多次代码实现。 任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再...

  • 下载管理器 思路

    下载管理器思路: 1. 实现单个任务的下载 2.实现 多个任务的下载

网友评论

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

      本文链接:https://www.haomeiwen.com/subject/yitbrftx.html