You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
git-xiaomibianqian/other/代码标注/ui/210340167肖凯文代码标注NoteEditAct...

880 lines
50 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.ui;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.app.SearchManager;
import android.appwidget.AppWidgetManager;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Paint;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.style.BackgroundColorSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.model.WorkingNote.NoteSettingChangedListener;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.tool.ResourceParser.TextAppearanceResources;
import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener;
import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class NoteEditActivity extends Activity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener {
private class HeadViewHolder {// 内部类HeadViewHolder用于存储标题栏的控件
public TextView tvModified;// 最后修改时间的TextView
public ImageView ivAlertIcon;// 提醒图标的ImageView
public TextView tvAlertDate;// 提醒图标的ImageView
public ImageView ibSetBgColor;// 设置背景颜色的ImageView
}
private static final Map<Integer, Integer> sBgSelectorBtnsMap = new HashMap<Integer, Integer>();
static {
sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW);
sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED);
sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE);
sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN);
sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE);
}
private static final Map<Integer, Integer> sBgSelectorSelectionMap = new HashMap<Integer, Integer>();
static {// 静态代码块初始化sBgSelectorBtnsMap
sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select);// 将黄色按钮id和黄色颜色值放入sBgSelectorBtnsMap中
sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select);// 将红色按钮id和红色颜色值放入sBgSelectorBtnsMap中
sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select); // 将蓝色按钮id和蓝色颜色值放入sBgSelectorBtnsMap中
sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select);// 将绿色按钮id和绿色颜色值放入sBgSelectorBtnsMap中
sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select);// 将白色按钮id和白色颜色值放入sBgSelectorBtnsMap中
}
private static final Map<Integer, Integer> sFontSizeBtnsMap = new HashMap<Integer, Integer>();// 定义一个静态的、不可变的Map用于存储字体大小按钮的ID和对应的字体大小值
static {
sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE);// 将“大号字体”按钮的ID和字体大小值存入Map中
sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL);// 将“小号字体”按钮的ID和字体大小值存入Map中
sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM); // 将“中号字体”按钮的ID和字体大小值存入Map中
sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER);// 将“超大号字体”按钮的ID和字体大小值存入Map中
}
private static final Map<Integer, Integer> sFontSelectorSelectionMap = new HashMap<Integer, Integer>();// 定义一个静态的、不可变的Map用于存储字体大小值和对应的字体选择器选中状态的ID
static {
sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select);// 将字体大小值为“大号字体”的选中状态ID存入Map中
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select); // 将字体大小值为“小号字体”的选中状态ID存入Map中
sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select);// 将字体大小值为“中号字体”的选中状态ID存入Map中
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select);// 将字体大小值为“超大号字体”的选中状态ID存入Map中
}
private static final String TAG = "NoteEditActivity";// 定义常量TAG为字符串"NoteEditActivity"
// 声明变量
private HeadViewHolder mNoteHeaderHolder;// 头部视图的持有者
private View mHeadViewPanel; // 头部视图面板
private View mNoteBgColorSelector;// 笔记背景颜色选择器
private View mFontSizeSelector;// 笔记字体大小选择器
private EditText mNoteEditor;// 笔记编辑器
private View mNoteEditorPanel;// 笔记编辑器面板
private WorkingNote mWorkingNote;// 工作笔记
private SharedPreferences mSharedPrefs; // 共享偏好设置
private int mFontSizeId; // 字体大小ID
private static final String PREFERENCE_FONT_SIZE = "pref_font_size";// 字体大小偏好设置
private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10;// 快捷图标标题最大长度
public static final String TAG_CHECKED = String.valueOf('\u221A');// 选中标记
public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); // 未选中标记
private LinearLayout mEditTextList;// 编辑文本列表
private String mUserQuery;// 用户查询
private Pattern mPattern;// 模式匹配对象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);//调用父类的onCreate方法
this.setContentView(R.layout.note_edit); //设置当前Activity的布局文件为note_edit.xml
if (savedInstanceState == null && !initActivityState(getIntent())) {//如果savedInstanceState为空且initActivityState方法返回false
finish(); //结束当前Activity
return; //返回
}
initResources();//初始化资源
}
/**
* Current activity may be killed when the memory is low. Once it is killed, for another time
* user load this activity, we should restore the former state
*/
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {//重写onRestoreInstanceState方法
super.onRestoreInstanceState(savedInstanceState); //调用父类的onRestoreInstanceState方法
if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) {//如果savedInstanceState不为空且包含Intent.EXTRA_UID键
Intent intent = new Intent(Intent.ACTION_VIEW);//创建一个ACTION_VIEW的Intent对象
intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); //将Intent.EXTRA_UID键对应的值放入Intent中
if (!initActivityState(intent)) {//如果initActivityState方法返回false
finish();//结束当前Activity
return;
}
Log.d(TAG, "Restoring from killed activity"); //在Logcat中输出一条调试信息
}
}
private boolean initActivityState(Intent intent) {{ // 初始化Activity状态的方法传入一个Intent对象
/**
* If the user specified the {@link Intent#ACTION_VIEW} but not provided with id,
* then jump to the NotesListActivity
*/
mWorkingNote = null;{ // 初始化Activity状态的方法传入一个Intent对象
if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { // 如果Intent的动作是ACTION_VIEW
long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); // 获取Intent中的noteId如果没有则默认为0
mUserQuery = "";// 初始化mUserQuery为空字符串
/**
* Starting from the searched result
*/
if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) {// 初始化mUserQuery为空字符串
noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));// 获取搜索结果的noteId
mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY);// 获取用户查询的字符串
}
if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) {// 如果noteId在Note数据库中不可见
Intent jump = new Intent(this, NotesListActivity.class); // 创建一个跳转到NotesListActivity的Intent
startActivity(jump); // 启动该Intent
showToast(R.string.error_note_not_exist); // 显示提示信息
finish(); // 结束当前Activity
return false; // 返回false
} else {// 如果noteId在Note数据库中可见
mWorkingNote = WorkingNote.load(this, noteId);// 加载指定noteId对应的WorkingNote对象
if (mWorkingNote == null) {// 如果加载失败
Log.e(TAG, "load note failed with note id" + noteId);// 记录错误信息
finish();
return false;
}
}
getWindow().setSoftInputMode( // 设置窗口的软输入模式
WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN // 软输入状态为隐藏
| WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); // 软输入调整方式为自适应
} else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) {// 如果Intent的动作是ACTION_INSERT_OR_EDIT
// New note
long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0);// 获取文件夹id
int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);// 获取小部件id
int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE,
Notes.TYPE_WIDGET_INVALIDE); // 获取小部件类型
int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, // 获取背景资源id
ResourceParser.getDefaultBgId(this));
// Parse call-record note
String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); // 获取电话号码
long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0);// 获取通话记录日期
if (callDate != 0 && phoneNumber != null) {//如果通话日期和电话号码都存在
if (TextUtils.isEmpty(phoneNumber)) {//如果电话号码为空
Log.w(TAG, "The call record number is null");//输出警告信息
}
long noteId = 0; //定义笔记id变量
if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(),
phoneNumber, callDate)) > 0) {//如果能够通过电话号码和通话日期获取到笔记id
mWorkingNote = WorkingNote.load(this, noteId);//加载笔记
if (mWorkingNote == null) {//如果笔记加载失败
Log.e(TAG, "load call note failed with note id" + noteId); //输出错误信息
finish();
return false;
}
} else { //如果无法通过电话号码和通话日期获取到笔记id
mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId,
widgetType, bgResId);//创建一个空的笔记
mWorkingNote.convertToCallNote(phoneNumber, callDate);//将笔记转换为通话记录类型
}
} else { //如果通话日期或电话号码不存在
mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType,
bgResId); //创建一个空的笔记
}
getWindow().setSoftInputMode(//设置窗口的软键盘模式
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
| WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
} else { //如果意图未指定操作
Log.e(TAG, "Intent not specified action, should not support"); //输出错误信息
finish();
return false;
}
mWorkingNote.setOnSettingStatusChangedListener(this);//设置 mWorkingNote 对象的状态改变监听器为当前类this
return true;
}
@Override
protected void onResume() {
super.onResume();
initNoteScreen();
}//在onResume方法中调用initNoteScreen方法初始化笔记界面
//初始化笔记界面
private void initNoteScreen() {//设置笔记编辑器的字体样式
mNoteEditor.setTextAppearance(this, TextAppearanceResources
.getTexAppearanceResource(mFontSizeId));//如果是清单模式,则切换到列表模式
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
switchToListMode(mWorkingNote.getContent());
} else {//否则,将笔记内容显示在编辑器中,并高亮查询结果
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));//将光标移到文本末尾
mNoteEditor.setSelection(mNoteEditor.getText().length());//隐藏所有背景选择器
}
for (Integer id : sBgSelectorSelectionMap.keySet()) {
findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE);
}//设置标题栏背景
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());//设置笔记编辑器背景
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());//设置笔记修改时间
mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this,
mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME
| DateUtils.FORMAT_SHOW_YEAR));
/**
* TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker
* is not ready
*/
showAlertHeader();
}
private void showAlertHeader() {// 显示警报头部信息的方法
if (mWorkingNote.hasClockAlert()) {// 如果当前笔记设置了警报
long time = System.currentTimeMillis(); // 获取当前时间
if (time > mWorkingNote.getAlertDate()) { // 如果当前时间已经超过了警报时间
mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); // 在警报时间的文本框中显示“已过期”
} else {
mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString(
mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS));// 在警报时间的文本框中显示距离警报时间还有多长时间
}
mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE); // 显示警报时间的文本框
mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE); // 显示警报图标
} else { // 如果当前笔记没有设置警报
mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE);// 隐藏警报时间的文本框
mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE);// 隐藏警报图标
};
}
@Override
protected void onNewIntent(Intent intent) {// 当用户从其它界面返回到此界面时调用的方法
super.onNewIntent(intent); // 调用父类的方法
initActivityState(intent);// 初始化Activity的状态
}
@Override
protected void onSaveInstanceState(Bundle outState) { // 当Activity被销毁时调用的方法用于保存Activity的状态
super.onSaveInstanceState(outState);
/**
* For new note without note id, we should firstly save it to
* generate a id. If the editing note is not worth saving, there
* is no id which is equivalent to create new note
*/
if (!mWorkingNote.existInDatabase()) {// 如果工作笔记不存在于数据库中
saveNote();// 保存笔记
}
outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId());// 将工作笔记的ID放入保存状态中
Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState");
}// 在日志中记录保存的工作笔记ID
// 触摸事件分发函数
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mNoteBgColorSelector, ev))// 如果笔记背景颜色选择器可见且触摸位置不在选择器内部
{
mNoteBgColorSelector.setVisibility(View.GONE);// 隐藏笔记背景颜色选择器
return true;
}
if (mFontSizeSelector.getVisibility() == View.VISIBLE// 如果字号选择器可见且触摸位置不在选择器内部
&& !inRangeOfView(mFontSizeSelector, ev)) {
mFontSizeSelector.setVisibility(View.GONE);// 隐藏字号选择器
return true;
}// 调用父类的触摸事件分发函数
return super.dispatchTouchEvent(ev);
}
// 判断触摸位置是否在视图范围内
private boolean inRangeOfView(View view, MotionEvent ev) {// 获取视图在屏幕上的位置
int []location = new int[2];
view.getLocationOnScreen(location);
int x = location[0];
int y = location[1];// 获取视图在屏幕上的位置
if (ev.getX() < x
|| ev.getX() > (x + view.getWidth())
|| ev.getY() < y
|| ev.getY() > (y + view.getHeight())) {
return false;
}// 触摸位置在视图范围内
return true;
}
private void initResources() {// 获取笔记标题视图面板
mHeadViewPanel = findViewById(R.id.note_title);// 获取笔记标题视图面板
mNoteHeaderHolder = new HeadViewHolder();// 获取笔记标题视图面板
mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date);// 获取笔记标题视图面板
mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon);// 获取提醒时间文本视图
mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date);// 获取设置背景颜色按钮视图
mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color);// 设置设置背景颜色按钮的点击监听器
mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this);// 获取笔记编辑器视图
mNoteEditor = (EditText) findViewById(R.id.note_edit_view);// 获取笔记编辑器面板视图
mNoteEditorPanel = findViewById(R.id.sv_note_edit);// 获取笔记背景颜色选择器视图
mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);// 为所有背景选择器按钮设置点击监听器
for (int id : sBgSelectorBtnsMap.keySet()) {
ImageView iv = (ImageView) findViewById(id);
iv.setOnClickListener(this);
}
mFontSizeSelector = findViewById(R.id.font_size_selector);// 设置字体大小选择器控件
for (int id : sFontSizeBtnsMap.keySet()) {
View view = findViewById(id);
view.setOnClickListener(this);
};// 遍历字体大小按钮映射表,并为每个按钮设置点击事件监听器
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);// 获取默认的共享首选项对象
mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE);// 获取上一次使用的字体大小ID如果没有则使用默认值
/**
* HACKME: Fix bug of store the resource id in shared preference.
* The id may larger than the length of resources, in this case,
* return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
*/
if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) {
mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
}//ID可能大于资源长度在这种情况下
mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);
}// 获取笔记编辑列表布局
@Override
protected void onPause() {
super.onPause();
if(saveNote()) {
Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length());
}// 如果保存笔记成功,则输出日志记录笔记内容长度
clearSettingState();// 清除设置状态
}
private void updateWidget() {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);// 创建更新小部件的意图
if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) {
intent.setClass(this, NoteWidgetProvider_2x.class);
}// 根据当前笔记的小部件类型,设置意图的类别
else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) {
intent.setClass(this, NoteWidgetProvider_4x.class);
}
else {
Log.e(TAG, "Unspported widget type");
return;
}// 如果小部件类型不支持,则输出错误日志并返回
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {
mWorkingNote.getWidgetId()
});// 将当前笔记的小部件ID添加到意图中
sendBroadcast(intent);// 发送广播更新小部件
setResult(RESULT_OK, intent);// 设置结果为“操作成功”
}
public void onClick(View v) {// 点击事件监听器
int id = v.getId();// 获取被点击的 View 的 ID
if (id == R.id.btn_set_bg_color) {// 如果被点击的是设置背景颜色的按钮
mNoteBgColorSelector.setVisibility(View.VISIBLE);// 显示背景颜色选择器
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- View.VISIBLE); // 显示当前背景颜色的选中状态
} else if (sBgSelectorBtnsMap.containsKey(id)) { // 如果被点击的是背景颜色选择器中的按钮
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.GONE);// 隐藏之前选中的背景颜色的选中状态
mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); // 设置笔记的背景颜色 ID
mNoteBgColorSelector.setVisibility(View.GONE); // 隐藏背景颜色选择器
} else if (sFontSizeBtnsMap.containsKey(id)) {// 如果被点击的是字体大小选择器中的按钮
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE); // 隐藏之前选中的字体大小的选中状态
mFontSizeId = sFontSizeBtnsMap.get(id);// 设置笔记的字体大小 ID
mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit();// 将字体大小 ID 存储到 SharedPreferences 中
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);// 显示新选中的字体大小的选中状态
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {//如果当前笔记处于待办事项模式
getWorkingText();//获取当前笔记的文本内容;
switchToListMode(mWorkingNote.getContent());//将当前笔记的编辑器切换为列表模式
} else {
mNoteEditor.setTextAppearance(this,
TextAppearanceResources.getTexAppearanceResource(mFontSizeId));//将当前笔记的编辑器文本外观设置为指定字体大小
}
mFontSizeSelector.setVisibility(View.GONE);//隐藏字体大小选择器
}
}
@Override
public void onBackPressed() { // 当用户按下返回键时执行该方法
if(clearSettingState()) { // 如果设置状态已经被清除,则直接返回
return;
}
saveNote(); // 保存笔记
super.onBackPressed(); // 调用父类的onBackPressed方法关闭当前Activity
}
private boolean clearSettingState() {// 清除设置状态的方法
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {// 如果笔记背景颜色选择器可见
mNoteBgColorSelector.setVisibility(View.GONE);// 如果笔记背景颜色选择器可见
return true; // 返回true表示设置状态已经被清除
} else if (mFontSizeSelector.getVisibility() == View.VISIBLE) {// 否则如果字体大小选择器可见
mFontSizeSelector.setVisibility(View.GONE); // 隐藏字体大小选择器
return true; // 返回true表示设置状态已经被清除
}
return false; // 返回false表示设置状态未被清除
}
public void onBackgroundColorChanged() {// 当笔记背景颜色改变时执行该方法
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.VISIBLE); // 根据当前背景颜色id获取对应的选择器并将其设置为可见
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); // 根据当前背景颜色id获取对应的选择器并将其设置为可见
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); // 设置笔记标题栏的背景颜色为当前选择的背景颜色
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {// 当准备显示选项菜单时调用
if (isFinishing()) {// 如果Activity正在被销毁则返回true
return true;
}
clearSettingState(); // 清除设置状态(即隐藏字体大小选择器和背景颜色选择器)
menu.clear();// 清除菜单项
if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { // 如果当前笔记所在的文件夹是通话记录文件夹,则加载通话记录编辑菜单项
getMenuInflater().inflate(R.menu.call_note_edit, menu);
} else { // 否则加载笔记编辑菜单项
getMenuInflater().inflate(R.menu.note_edit, menu);
}
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { // 如果当前是清单模式,则将选项菜单中的“清单模式”改为“正常模式”
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode);
} else {// 否则将选项菜单中的“正常模式”改为“清单模式”
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode);
}
if (mWorkingNote.hasClockAlert()) { // 如果当前笔记设置了提醒,则隐藏“添加提醒”菜单项
menu.findItem(R.id.menu_alert).setVisible(false);
} else { // 否则隐藏“删除提醒”菜单项
menu.findItem(R.id.menu_delete_remind).setVisible(false);
}
return true; // 返回true表示已准备好显示选项菜单
}
@Override
public boolean onOptionsItemSelected(MenuItem item) { // 当用户点击菜单项时执行该方法
switch (item.getItemId()) {// 获取用户点击的菜单项ID
case R.id.menu_new_note:// 如果用户点击的是新建笔记菜单项
createNewNote();// 调用创建新笔记的方法
break;
case R.id.menu_delete:// 如果用户点击的是删除笔记菜单项
AlertDialog.Builder builder = new AlertDialog.Builder(this);// 创建一个对话框
builder.setTitle(getString(R.string.alert_title_delete)); // 设置对话框标题
builder.setIcon(android.R.drawable.ic_dialog_alert);// 设置对话框图标
builder.setMessage(getString(R.string.alert_message_delete_note)); // 设置对话框提示信息
builder.setPositiveButton(android.R.string.ok,// 设置对话框确认按钮的点击事件
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) { // 当用户点击确认按钮时执行该方法
deleteCurrentNote(); // 调用删除笔记的方法
finish(); // 关闭当前活动
}
});
builder.setNegativeButton(android.R.string.cancel, null); // 设置对话框取消按钮的点击事件
builder.show();// 显示对话框
break;
case R.id.menu_font_size:// 如果用户点击的是字体大小菜单项
mFontSizeSelector.setVisibility(View.VISIBLE); // 显示字体大小选择器
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);// 显示当前选中的字体大小
break;
case R.id.menu_list_mode: // 如果用户点击的是清单模式菜单项
mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?// 切换清单模式
TextNote.MODE_CHECK_LIST : 0);
break;
case R.id.menu_share:// 如果用户点击的是分享菜单项
getWorkingText();// 获取当前笔记的内容
sendTo(this, mWorkingNote.getContent());// 调用分享方法,将笔记内容分享到其他应用
break;
case R.id.menu_send_to_desktop:// 如果用户点击的是发送到桌面菜单项
sendToDesktop(); // 调用发送到桌面的方法
break;
case R.id.menu_alert:// 如果用户点击的是设置提醒菜单项
setReminder();// 调用设置提醒的方法
break;
case R.id.menu_delete_remind: // 如果用户点击的是删除提醒菜单项
mWorkingNote.setAlertDate(0, false);// 将笔记的提醒日期设置为0表示删除提醒
break;
default:// 如果用户点击的是其他菜单项
break;
}
return true;// 返回true表示菜单项已经被处理
}
private void setReminder() {// 设置提醒时间
DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis());// 创建日期时间选择对话框
d.setOnDateTimeSetListener(new OnDateTimeSetListener() { // 设置日期时间选择监听器
public void OnDateTimeSet(AlertDialog dialog, long date) { // 在日期时间选择监听器中设置提醒时间
mWorkingNote.setAlertDate(date , true);// 设置提醒时间
}
});
d.show(); // 显示日期时间选择对话框
}
/**
* Share note to apps that support {@link Intent#ACTION_SEND} action
* and {@text/plain} type
*/
private void sendTo(Context context, String info) { // 分享笔记到其他应用
Intent intent = new Intent(Intent.ACTION_SEND); // 创建发送意图
intent.putExtra(Intent.EXTRA_TEXT, info);// 设置分享内容
intent.setType("text/plain");// 设置分享内容
context.startActivity(intent);// 启动分享应用
}
private void createNewNote() { // 创建新笔记
// Firstly, save current editing notes
saveNote();// 保存笔记
// For safety, start a new NoteEditActivity
finish();// 结束当前Activity
Intent intent = new Intent(this, NoteEditActivity.class);// 结束当前Activity
intent.setAction(Intent.ACTION_INSERT_OR_EDIT); // 设置意图的操作为插入或编辑
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId()); // 设置意图的操作为插入或编辑
startActivity(intent);// 启动新笔记的Activity
}
private void deleteCurrentNote() {// 删除当前笔记
if (mWorkingNote.existInDatabase()) {// 如果当前笔记存在于数据库中
HashSet<Long> ids = new HashSet<Long>();// 初始化一个HashSet用于存储笔记的id
long id = mWorkingNote.getNoteId();// 初始化一个HashSet用于存储笔记的id
if (id != Notes.ID_ROOT_FOLDER) {// 如果id不是根文件夹的id
ids.add(id);
} else {
Log.d(TAG, "Wrong note id, should not happen");// 把id添加到HashSet中
}
if (!isSyncMode()) {// 把id添加到HashSet中
if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) {// 批量删除笔记
Log.e(TAG, "Delete Note error");// 输出错误日志
}
} else {
if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) {// 批量移动笔记到垃圾箱
Log.e(TAG, "Move notes to trash folder error, should not happens");// 输出错误日志
}
}
}
mWorkingNote.markDeleted(true);// 标记当前笔记已被删除
}
private boolean isSyncMode() {// 判断是否为同步模式
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
public void onClockAlertChanged(long date, boolean set) {// 监听提醒时间的变化
/**
* User could set clock to an unsaved note, so before setting the
* alert clock, we should save the note first
*/
if (!mWorkingNote.existInDatabase()) {
saveNote();
}// 如果笔记已保存到数据库中
if (mWorkingNote.getNoteId() > 0) {
Intent intent = new Intent(this, AlarmReceiver.class);// 初始化一个意图用于启动闹钟接收器
intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId()));// 设置意图的数据为当前笔记的Uri
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);// 获取一个用于启动闹钟接收器的PendingIntent
AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE));// 获取一个闹钟管理器
showAlertHeader();// 获取一个闹钟管理器
if(!set) {// 如果不设置提醒时间
alarmManager.cancel(pendingIntent);// 如果不设置提醒时间
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent);// 设置当前闹钟
}
} else {
/**
* There is the condition that user has input nothing (the note is
* not worthy saving), we have no note id, remind the user that he
* should input something
*/
Log.e(TAG, "Clock alert setting error");
showToast(R.string.error_note_empty_for_clock);
}
}
public void onWidgetChanged() {// 当小部件发生改变时触发该方法
updateWidget();// 更新小部件
}
public void onEditTextDelete(int index, String text) {// 当删除编辑文本时触发该方法index表示编辑文本的索引text表示删除的文本内容
int childCount = mEditTextList.getChildCount();// 获取编辑文本列表中的子项数量
if (childCount == 1) {// 如果只有一个子项,则不进行删除操作
return;
}
for (int i = index + 1; i < childCount; i++) {// 循环遍历编辑文本列表将所有索引大于被删除文本的索引的文本的索引减1
((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
.setIndex(i - 1);
}
mEditTextList.removeViewAt(index); // 移除被删除的编辑文本
NoteEditText edit = null;
if(index == 0) { // 如果被删除的是第一个编辑文本,则将焦点设置到第一个编辑文本
edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById(
R.id.et_edit_text);
} else { // 如果被删除的是第一个编辑文本,则将焦点设置到第一个编辑文本
edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById(
R.id.et_edit_text);
}
int length = edit.length();// 获取编辑文本的长度
edit.append(text);// 将被删除的文本追加到编辑文本上
edit.requestFocus();// 获取焦点
edit.setSelection(length); // 设置光标位置为文本末尾
}
public void onEditTextEnter(int index, String text) {// 当添加编辑文本时触发该方法index表示编辑文本的索引text表示添加的文本内容
/**
* Should not happen, check for debug
*/
if(index > mEditTextList.getChildCount()) {// 如果索引大于编辑文本列表中的子项数量,则输出错误日志
Log.e(TAG, "Index out of mEditTextList boundrary, should not happen");
}
View view = getListItem(text, index); // 根据传入的文本和索引获取列表项视图
mEditTextList.addView(view, index);// 将列表项添加到编辑文本列表中
NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
edit.requestFocus();// 获取焦点
edit.setSelection(0); // 设置光标位置为文本开头
for (int i = index + 1; i < mEditTextList.getChildCount(); i++) {// 循环遍历编辑文本列表将所有索引大于被添加文本的索引的文本的索引加1
((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
.setIndex(i);
}
}
private void switchToListMode(String text) {// 切换到列表模式
mEditTextList.removeAllViews();// 移除所有的 View
String[] items = text.split("\n");// 以换行符为分隔符,将字符串 text 分割成多个条目
int index = 0;
for (String item : items) {// 遍历每个条目
if(!TextUtils.isEmpty(item)) {// 如果条目不为空
mEditTextList.addView(getListItem(item, index));// 将新建的条目添加到 mEditTextList 中
index++;
}
}
mEditTextList.addView(getListItem("", index));// 添加一个空条目
mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus();// 获取最后一个条目中的 NoteEditText并将其设为焦点
mNoteEditor.setVisibility(View.GONE);// 隐藏 mNoteEditor
mEditTextList.setVisibility(View.VISIBLE);// 显示 mEditTextList
}
private Spannable getHighlightQueryResult(String fullText, String userQuery) {// 获取高亮显示查询结果的 Spannable 对象
SpannableString spannable = new SpannableString(fullText == null ? "" : fullText);// 将 fullText 转换为 SpannableString 对象
if (!TextUtils.isEmpty(userQuery)) {// 使用正则表达式匹配 userQuery
mPattern = Pattern.compile(userQuery);
Matcher m = mPattern.matcher(fullText);
int start = 0;
while (m.find(start)) {// 将匹配到的字符串进行高亮显示
spannable.setSpan(
new BackgroundColorSpan(this.getResources().getColor(
R.color.user_query_highlight)), m.start(), m.end(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
start = m.end();
}
}
return spannable;
}
private View getListItem(String item, int index) {// 获取列表项视图
View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null);// 从布局文件中加载视图
final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);// 获取编辑框
edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId));// 设置编辑框文本的外观样式
CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item));// 获取复选框
cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {// 设置复选框状态改变的监听器
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {// 复选框状态改变时的回调方法
if (isChecked) {// 如果被选中,则在编辑框中添加删除线
edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} else {// 否则,编辑框中的文本恢复正常样式
edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
}
}
});
if (item.startsWith(TAG_CHECKED)) {// 否则,编辑框中的文本恢复正常样式
cb.setChecked(true); // 设置复选框为选中状态
edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); // 在编辑框中添加删除线
item = item.substring(TAG_CHECKED.length(), item.length()).trim();// 截取文本内容
} else if (item.startsWith(TAG_UNCHECKED)) { // 未选中
cb.setChecked(false); // 设置复选框为未选中状态
edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); // 编辑框中的文本恢复正常样式
item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); // 截取文本内容
}
edit.setOnTextViewChangeListener(this);// 设置编辑框的文本内容变化监听器
edit.setIndex(index);// 设置编辑框的索引值
edit.setText(getHighlightQueryResult(item, mUserQuery));// 设置编辑框的文本内容,并高亮显示用户查询结果
return view;// 返回列表项视图
}
public void onTextChange(int index, boolean hasText) {
if (index >= mEditTextList.getChildCount()) {// 当文本框的内容改变时,根据文本框的索引和是否有文本来显示或隐藏复选框
Log.e(TAG, "Wrong index, should not happen");
return;// 如果索引超出范围,打印错误日志并返回
}
if(hasText) {
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE);// 如果有文本,显示复选框
} else {
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE);// 如果没有文本,隐藏复选框
}
}
public void onCheckListModeChanged(int oldMode, int newMode) {// 当清单模式改变时
if (newMode == TextNote.MODE_CHECK_LIST) { // 如果新模式是清单模式
switchToListMode(mNoteEditor.getText().toString()); // 切换到清单模式
} else { // 如果新模式不是清单模式
if (!getWorkingText()) { // 如果当前没有工作文本
mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ",
"")); // 将工作文本设置为去掉未勾选标记的内容
}
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));// 设置文本框的内容为高亮查询结果
mEditTextList.setVisibility(View.GONE); // 隐藏文本框列表
mNoteEditor.setVisibility(View.VISIBLE); // 显示文本框
}
}
private boolean getWorkingText() {//获取当前编辑的文本内容,包括已完成和未完成的任务列表
boolean hasChecked = false;//标记是否有已完成的任务
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {//如果目前是任务列表模式
StringBuilder sb = new StringBuilder(); //创建一个字符串构建器
for (int i = 0; i < mEditTextList.getChildCount(); i++) { //循环遍历所有的子项
View view = mEditTextList.getChildAt(i);//获取当前子项的视图
NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);//获取当前子项的编辑框
if (!TextUtils.isEmpty(edit.getText())) { //如果编辑框不为空
if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) { //如果当前项被选中
sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n");//在字符串构建器中添加已完成的任务项
hasChecked = true;//标记有已完成的任务
} else {//如果当前项未被选中
sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n");//在字符串构建器中添加未完成的任务项
}
}
}
mWorkingNote.setWorkingText(sb.toString()); //将构建好的字符串设置为当前工作的文本内容
} else { //如果不是任务列表模式
mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); //将编辑框中的文本设置为当前工作的文本内容
}
return hasChecked; //返回是否有已完成的任务项的标记
}
private boolean saveNote() {// 保存笔记到数据库中
getWorkingText();// 获取编辑框中的文本内容
boolean saved = mWorkingNote.saveNote();// 将笔记保存到数据库中
if (saved) {
/**
* There are two modes from List view to edit view, open one note,
* create/edit a node. Opening node requires to the original
* position in the list when back from edit view, while creating a
* new node requires to the top of the list. This code
* {@link #RESULT_OK} is used to identify the create/edit state
*/
setResult(RESULT_OK);// 设置返回结果为RESULT_OK
}
return saved; // 返回是否保存成功的结果
}
private void sendToDesktop() {
/**
* Before send message to home, we should make sure that current
* editing note is exists in databases. So, for new note, firstly
* save it
*/
if (!mWorkingNote.existInDatabase()) {
saveNote(); // 如果笔记不存在于数据库中,则先保存笔记
}
if (mWorkingNote.getNoteId() > 0) { // 如果笔记存在于数据库中
Intent sender = new Intent(); // 创建Intent对象
Intent shortcutIntent = new Intent(this, NoteEditActivity.class); // 创建快捷方式Intent对象
shortcutIntent.setAction(Intent.ACTION_VIEW); // 设置快捷方式Intent的Action为ACTION_VIEW
shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId()); // 设置快捷方式Intent的额外数据
sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); // 设置发送Intent的额外数据
sender.putExtra(Intent.EXTRA_SHORTCUT_NAME,
makeShortcutIconTitle(mWorkingNote.getContent())); // 设置发送Intent的额外数据
sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app)); // 设置发送Intent的额外数据
sender.putExtra("duplicate", true); // 设置发送Intent的额外数据
sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); // 设置发送Intent的Action为INSTALL_SHORTCUT
showToast(R.string.info_note_enter_desktop);// 显示Toast提示信息
sendBroadcast(sender); // 发送广播
} else {
/**
* There is the condition that user has input nothing (the note is
* not worthy saving), we have no note id, remind the user that he
* should input something
*/
Log.e(TAG, "Send to desktop error");// 打印错误日志信息
showToast(R.string.error_note_empty_for_send_to_desktop); // 显示Toast提示信息
}
}
private String makeShortcutIconTitle(String content) { // 生成快捷方式的图标标题,去掉勾选框的标记
content = content.replace(TAG_CHECKED, ""); // 去掉已勾选的标记
content = content.replace(TAG_UNCHECKED, ""); // 去掉未勾选的标记
return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0,
SHORTCUT_ICON_TITLE_MAX_LEN) : content; // 如果标题超出指定长度,截取前 SHORTCUT_ICON_TITLE_MAX_LEN 个字符作为标题
}
private void showToast(int resId) { // 显示短时间的提示信息
showToast(resId, Toast.LENGTH_SHORT);
}
private void showToast(int resId, int duration) { // 显示指定时间的提示信息
Toast.makeText(this, resId, duration).show();
}
}