美文网首页
任务3:新建页面

任务3:新建页面

作者: jingz课程 | 来源:发表于2018-03-10 21:19 被阅读0次

1. 在ActionBar增加“完成”项

我们用ActionBar按钮取代任务1中放置的临时进行“退出”“完成”操作的两个按钮。
ActionBar添加操作按钮,需要重写Activity类中与Option menu相关的两个方法。也就是说,ActionBar上的操作按钮,是被作为菜单项来处理的。
打开新建页对应的EditNoteActivity,在onCreate()方法下面的空行定位光标,执行菜单命令“Code->Override Methods”,将看到如下的对话框:

找到图中高亮显示的两个方法:

  • onCreateOptionsMenu():创建ActionBar菜单
  • onOptionsItemSelected():设定菜单项对应的操作

然后点击“OK”,Android Studio自动为我们在代码中插入这两个方法:

public class EditNoteActivity extends AppCompatActivity {
    ...

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        return super.onOptionsItemSelected(item);
    }

首先,我们对onCreateOptionsMenu()方法进行改写,为其添加“完成”菜单项。

菜单结构在XML格式的资源文件中定义。我们假设菜单资源文件名叫“menu_edit_note.xml”(此时还未创建),那么首先在onCreateOptionsMenu()方法中添加以下代码:

    MenuInflater menuInflater = getMenuInflater();
    menuInflater.inflate(R.menu.menu_edit_note, menu);

这两行代码的含义就是按照menu_edit_note.xml文件的描述来初始化菜单对象menu
然而此时并不存在menu_edit_note.xml文件,这将导致开发环境提示错误。将光标定位到出错行,按快捷键“Alt+Enter”,在弹出的纠错建议菜单中选择“Create menu resource file ‘menu_edit_note.xml’”,系统弹出对话框:

保持默认设置,确认之后,在res文件夹下将多出一个名为“menu”的文件夹,其中已经为我们创建好了menu_edit_note.xml文件。双击将其打开,看到内容如下:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

</menu>

可以看到这个文件中尚未定义任何菜单项。我们为它增加一个“完成”菜单项。在<menu></menu>标签之间创建<item>标签,并设置其title为“完成”:

    <item
        android:id="@+id/menu_item_finish"
        android:title="@string/finish" />

运行程序查看效果:

虽然ActionBar上出现了菜单项,但是并不是我们设计图上的按钮形式。我们还需要稍微进行一下设置。在menu_edit_note.xml中“完成”菜单项下增加app:showAsAction属性,取值为“always”

    <item
        ...
        app:showAsAction="always"
        />

再次运行程序,菜单项变成按钮的形式:

接下来,为它添加操作。找到前面自动插入的方法onOptionsItemSelected(),这是处理菜单项点击操作的方法。它通过id来识别不同菜单项。我们首先从传入的参数item对象中获取其id,然后判断如果是“完成”项,则执行对应操作。:

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        switch (id) {
            case R.id.menu_item_finish:
                // 在这里添加对完成项的处理

                return true;
        }
        return super.onOptionsItemSelected(item);
    }

对于完成项,其实就是取代之前的“完成”按钮。所以,我们首先从布局中把原来的完成按钮去掉;然后将它对应的响应方法onFinishEdit()改写成私有方法,并且去掉原来传入的参数:

    private void onFinishEdit() {
        ...
    }

然后在onOptionsItemSelected()中调用它即可实现“完成”操作:

            case R.id.menu_item_finish:
                onFinishEdit();
                return true;

运行程序查看效果:


2. 为ActionBar增加回退项

ActionBar本身带有一个回退项(即退回上一层UI),默认状态下是隐藏的。我们需要将其显示出来。只要少量代码即可达到目的。找到EditNoteActivity类的onCreate()方法,在里面setContentView(R.layout.activity_edit_note)方法下面添加代码:

        ActionBar actionBar = getSupportActionBar();
        actionBar.setDisplayHomeAsUpEnabled(true);

运行程序查看效果:


具有回退按钮的ActionBar

不过现在点击回退按钮不会产生任何操作。我们需要手动为它添加操作。回退项本身也被纳入到ActionBar菜单的处理机制中,并具有缺省的菜单项id:“android.R.id.home”。所以我们在onOptionsItemSelected()方法中增加对这个id的处理即可:

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        switch (id) {
            ...
            case android.R.id.home:
                
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

接下来,在这里只要让它取代原来“退出”按钮的功能即可。我们删掉退出按钮,并将它原来的响应方法onCancelEdit()改为以下形式:

private void onCancelEdit() {
       ...
}

然后在上面的回退项处理代码中调用它:

            case android.R.id.home:
                onCancelEdit();
                return true;

运行代码看效果:



3. 创建主编辑区

市场上成熟的笔记类应用产品通常提供支持富文本的强大编辑功能。但是,我们目前仅对文本进行支持。
首先,对编辑界面进行简要的设计:

全视图从视觉上划分为3个区域,可以按下述要点进行设计:

区域(1):标题编辑框

这部分比较简单,只有一个编辑框(EditText),可以设计如下:

  • 字体较大:设置为18sp
  • 单行编辑android:singleLine属性设置为true
  • 提示文字(hint):“点击输入标题”
  • 背景色:不采用Android提供的默认样式,简单设置为白色

区域(2):分隔线

  • 用一个高度为1个像素(px)的View实现
  • 背景色设为灰色

区域(3):正文编辑框

这一部分主要是一个支持多行编辑的编辑框。但是需要考虑的是,如果输入内容较长并超出屏幕范围,则需要进行滚动操作,因此编辑框需要被一个滚动视图(ScrollView)所包围。
对于编辑框本身,可以按如下的样式进行设计:

  • 字体较小:设置为16sp
  • 多行编辑android:inputType属性设置为“textMultiLine”
  • 背景色同样简单设为白色

下面来实现上面的设计。

定义参数资源

在规范的Android设计与开发中,通常并不直接将字符串、颜色值、文字大小或尺寸数值写入到代码或布局文件中(称为“硬编码”),而是分别定义在资源文件中,然后引用对应的名字。定义这些数据参数的XML资源文件通常放置在res/values目录下:

  • strings.xml:定义字符串资源
  • dimens.xml:定义字体大小对象尺寸
  • colors.xml:定义颜色值

所以,根据前文对编辑界面的设计,我们逐一为其创建资源条目。

字符串:
打开strings.xml文件,向其中添加以下字符串:

    <string name="hint_edit_name">请在此输入标题</string>
    <string name="hint_edit_content">请在此记录你的想法</string>

字体大小:
res/values下创建名为dimens.xml的资源文件。方法是,右键单击values目录,选择“New->Values resource file”,在弹出的对话框中填写“dimens.xml”并确认即可。
打开新创建的dimens.xml文件,内容类似:

<?xml version="1.0" encoding="utf-8"?>
<resources>

</resources>

向其中为以下几个参数添加定义:

  • 标题编辑框字体大小:18sp
  • 分隔线高度:1px
  • 正文编辑框字体大小:16sp
    此外,为了使界面拥有足够的留白区域,减少视觉压力,我们为两个编辑框定义四个方向的内边距:
  • 水平方向(左/右):22dp
  • 垂直方向(上/下):16dp

具体如下:

    <dimen name="edit_title_font_size">18sp</dimen>
    <dimen name="edit_content_font_size">16sp</dimen>
    <dimen name="edit_divider_height">1px</dimen>
    <dimen name="edit_padding_horizontal">22dp</dimen>
    <dimen name="edit_padding_vertical">16dp</dimen>

颜色:
打开res/values/colors.xml文件(没有就自行创建),向其中添加如下的颜色定义:

  • 编辑框背景色(白):#FFFFFF
  • 分隔线颜色(深灰):#909090
    具体如下:
    <color name="divider">#909090</color>
    <color name="white">#FFFFFF</color>

修改EditNoteActivity的布局文件activity_edit_note.xml,向其中添加这几部分组件:

标题编辑框:

    <EditText
        android:id="@+id/edit_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="@dimen/edit_padding_horizontal"
        android:paddingRight="@dimen/edit_padding_horizontal"
        android:paddingTop="@dimen/edit_padding_vertical"
        android:paddingBottom="@dimen/edit_padding_vertical"
        android:ems="10"
        android:singleLine="true"
        android:hint="@string/hint_edit_name"
        android:background="@color/white"
        android:textSize="@dimen/edit_title_font_size"/>

分隔线:

    <View
        android:layout_width="match_parent"
        android:layout_height="@dimen/edit_divider_height"
        android:background="@color/divider"/>

正文编辑区:

  • 首先我们来放置一个ScrollView占满屏幕剩下的区域,使我们的编辑区能够滚动:
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white">
        ...
    </ScrollView>
  • 然后再在ScrollView内部放置编辑框
        <EditText
            android:id="@+id/edit_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:paddingLeft="@dimen/edit_padding_horizontal"
            android:paddingRight="@dimen/edit_padding_horizontal"
            android:paddingTop="@dimen/edit_padding_vertical"
            android:paddingBottom="@dimen/edit_padding_vertical"
            android:gravity="left|top"
            android:ems="10"
            android:inputType="textMultiLine"
            android:hint="@string/hint_edit_content"
            android:textSize="@dimen/edit_content_font_size"
            android:background="@color/white"/>

完成之后运行程序简单试用:


4. 实现新增笔记条目

为INoteRepository接口增加保存笔记对应的操作:
先不考虑将数据存储到数据库或文件这类长期保存的媒介。
在开发实现全部笔记页的时候,我们创建了数据仓库接口INoteRepository,如下:

public interface INoteRepository {
    ArrayList<Note> getAllNotes();
}

其中,该接口仅仅定义了一个操作getAllNotes(),用来获取全部笔记。那么,为了实现对新建页面中撰写的新笔记的保存,这里还需要添加新的操作saveNote()。这个操作还要区分保存数据的操作是否成功,成功则返回一个booleantrue,失败则返回false,如下:

public interface INoteRepository {
    ...

    /**
     * 保存笔记对象
     * @param note 被保存的笔记对象
     * @return 保存成功则返回true,失败返回false
     */
    boolean saveNote(Note note);
}

在用于测试的仓库类TestNoteRepository中实现保存操作
打开TestNoteRepository.java文件,由于它实现的接口INoteRepository刚刚添加了新操作,系统将会提示错误。
将光标定位到红线标出的错误行,按快捷键“Alt+Enter”,系统弹出修改建议菜单:

选择第一项“实现方法”,Android Studio为我们自动插入缺少的方法定义:

public class TestNoteRepository implements INoteRepository {
    ...

    @Override
    public boolean saveNote(Note note) {
        return false;
    }
}

那么,我们的数据保存到哪里呢?改写TestNoteRepository的代码,将getAllNotes()方法中定义的笔记列表notes由局部变量改写为TestNoteRepository类的静态属性

public class TestNoteRepository implements INoteRepository {
    private static ArrayList<Note> notes = new ArrayList<>();
    static {
        notes.add(new Note(1, "笔记1", "笔记1正文", System.currentTimeMillis()));
        notes.add(new Note(2, "笔记2", "笔记2正文", System.currentTimeMillis()));
        notes.add(new Note(3, "笔记3", "笔记3正文", System.currentTimeMillis()));
        notes.add(new Note(4, "笔记4", "笔记4正文", System.currentTimeMillis()));
        notes.add(new Note(5, "笔记5", "笔记5正文", System.currentTimeMillis()));
        notes.add(new Note(6, "笔记6", "笔记6正文", System.currentTimeMillis()));
        notes.add(new Note(7, "笔记7", "笔记7正文", System.currentTimeMillis()));
        notes.add(new Note(8, "笔记8", "笔记8正文", System.currentTimeMillis()));
        notes.add(new Note(9, "笔记9", "笔记9正文", System.currentTimeMillis()));
        notes.add(new Note(10, "笔记10", "笔记10正文", System.currentTimeMillis()));
    }

    @Override
    public ArrayList<Note> getAllNotes() {
        return notes;
    }
    ...
}

接下来改写自动生成的saveNote()方法,简单将作为参数传入的note对象插入到笔记列表notes最前面即可(显示列表时,要将最新的笔记放在最前面):

    @Override
    public boolean saveNote(Note note) {
        if (note != null) {
            notes.add(0, note);
        }
        return true;
    }

这个操作不太可能失败,直接返回true
接下来,只要在新建页面的“完成”操作之下调用saveNote()操作就可以实现新笔记的存储了。
打开EditNoteActivity.java文件编写代码:
增加属性:
用户在两个编辑框输入文字,我们需要获取这两个编辑框的内容,因此在类中定义两个EditText类型的属性,来分别表示标题编辑框正文编辑区;此外,要进行数据存储操作,需要再定义一个INoteRepository接口类型的属性,并用TestNoteRepository类来实例化:

public class EditNoteActivity extends AppCompatActivity {

    private EditText mTitleEdit;        // 标题编辑框
    private EditText mContentEdit;  // 正文编辑区

    private INoteRepository noteRepository = new TestNoteRepository();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...

同时还要在onCreate()方法中初始化两个编辑框:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        mTitleEdit = (EditText) findViewById(R.id.edit_title);
        mContentEdit = (EditText) findViewById(R.id.edit_content);
    }

实现笔记数据保存:
接下来,改写onFinishEdit()方法。原来我们在onFinishEdit()方法中仅仅提示用户并退出页面,并没有真正的执行存储数据的操作:

    private void onFinishEdit() {
        Toast.makeText(this, R.string.msg_note_saved, Toast.LENGTH_SHORT).show();
        finish();   // 关闭窗口
    }

现在我们为它加上存储数据代码:

    private void onFinishEdit() {

        // 1. 生成id
        long id = noteRepository.getAllNotes().size() + 1;

        // 2. 从编辑区获取标题和内容字符串
        String title = mTitleEdit.getEditableText().toString();
        String content = mContentEdit.getEditableText().toString();

        // 3. 创建笔记对象
        Note note = new Note(id, title, content, System.currentTimeMillis());

        // 4. 存储笔记
        noteRepository.saveNote(note);

        Toast.makeText(this, R.string.msg_note_saved, Toast.LENGTH_SHORT).show();
        finish();   // 关闭窗口
    }

刷新全部笔记页面:
如果此时运行程序,在新建一条笔记并保存后,我们的全部笔记页面并没有立即将其加入显示列表中。因此需要在恰当的位置重新为全部笔记页面中的RecyclerView设定数据集。
根据Activity的生命周期设计,当被盖住的Activity重新出现在屏幕最前端时,它的onResume()回调方法将被触发。因此,我们重写NoteListActivityonResume()方法,在其中刷新数据。
打开NoteListActivity.java文件,在其onCreate()方法下面添加onResume()方法。可由Android Studio自动生成,方法参考onCreateOptionMenu()方法代码的自动生成。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
    }

    @Override
    protected void onResume() {
        super.onResume();
    }

RecyclerView刷新数据,实际上是要对它的适配器重新设定数据并刷新。因此,我们要首先修改onCreate()方法中的适配器定义,将它由局部变量改写成NoteListActivity类的属性

public class NoteListActivity extends AppCompatActivity {
    ...
    private NoteAdapter adapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        adapter = new NoteAdapter();
        // 为适配器设定数据集
        adapter.setNotes(noteRepository.getAllNotes());
        mRecyclerView.setAdapter(adapter);
    }

然后为onResume()方法添加以下代码完成数据更新:

    @Override
    protected void onResume() {
        super.onResume();
        // 1. 重新设定数据集
        adapter.setNotes(noteRepository.getAllNotes());
        // 2. 触发RecyclerView重新绘制
        adapter.notifyDataSetChanged();
    }

运行程序效果如下:

相关文章

网友评论

      本文标题:任务3:新建页面

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