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()。这个操作还要区分保存数据的操作是否成功,成功则返回一个boolean值true,失败则返回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()回调方法将被触发。因此,我们重写NoteListActivity的onResume()方法,在其中刷新数据。
打开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();
}
运行程序效果如下:
网友评论