/* * 版权所有 (c) 2010-2011,The MiCode Open Source Community (www.micode.net) * * 本软件根据 Apache 许可证 2.0 版("许可证")发布; * 除非符合许可证,否则不得使用此文件。 * 您可以在以下网址获取许可证副本: * * http://www.apache.org/licenses/LICENSE-2.0 * * 除非法律要求或书面同意,软件 * 根据许可证分发的内容按"原样"提供, * 不附带任何明示或暗示的保证或条件。 * 请参阅许可证,了解有关权限和限制的具体语言。 */ 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; /** * 笔记编辑活动(核心界面逻辑) * 功能: * 1. 支持新建/编辑文本笔记、待办事项、通话记录笔记 * 2. 实现笔记内容编辑、格式设置(背景颜色、字体大小) * 3. 处理提醒设置、桌面小部件更新、快捷方式创建 * 4. 支持列表模式(待办事项)和普通文本模式切换 * 5. 集成搜索高亮、上下文菜单、多编辑项管理 */ public class NoteEditActivity extends Activity implements OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener { // 头部视图ViewHolder(优化UI组件访问) private class HeadViewHolder { public TextView tvModified; // 最后修改时间 public ImageView ivAlertIcon; // 提醒图标 public TextView tvAlertDate; // 提醒时间 public ImageView ibSetBgColor; // 设置背景颜色按钮 } // 背景颜色选择按钮映射(视图ID -> 资源ID) private static final Map sBgSelectorBtnsMap = new HashMap<>(); 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); } // 背景颜色选中状态映射(资源ID -> 选中视图ID) private static final Map sBgSelectorSelectionMap = new HashMap<>(); static { sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select); sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select); sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select); sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); } // 字体大小按钮映射(视图ID -> 资源ID) private static final Map sFontSizeBtnsMap = new HashMap<>(); static { sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL); sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM); sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER); } // 字体大小选中状态映射(资源ID -> 选中视图ID) private static final Map sFontSelectorSelectionMap = new HashMap<>(); static { sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select); sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select); sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select); } private static final String 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 = "\u221A"; // 待办事项选中标记 public static final String TAG_UNCHECKED = "\u25A1"; // 待办事项未选中标记 private LinearLayout mEditTextList; // 列表模式下的编辑项容器 private String mUserQuery; // 搜索查询词(用于高亮显示) private Pattern mPattern; // 搜索匹配模式 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.note_edit); // 加载编辑界面布局 // 恢复因内存不足被销毁的活动状态 if (savedInstanceState == null && !initActivityState(getIntent())) { finish(); return; } initResources(); // 初始化UI组件和资源 } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { // 从已保存的状态中恢复笔记ID Intent intent = new Intent(Intent.ACTION_VIEW); intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); if (!initActivityState(intent)) { finish(); } } } /** * 初始化活动状态(处理打开/新建笔记逻辑) * @param intent 启动意图 * @return 是否初始化成功 */ private boolean initActivityState(Intent intent) { mWorkingNote = null; if (TextUtils.equals(intent.getAction(), Intent.ACTION_VIEW)) { // 打开已有笔记 long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); // 处理搜索结果跳转 // 检查笔记是否存在且可见 if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { showToast(R.string.error_note_not_exist); startActivity(new Intent(this, NotesListActivity.class)); finish(); return false; } mWorkingNote = WorkingNote.load(this, noteId); // 加载笔记数据 if (mWorkingNote == null) { Log.e(TAG, "加载笔记失败:" + noteId); finish(); return false; } // 隐藏软键盘(查看模式) getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); } else if (TextUtils.equals(intent.getAction(), Intent.ACTION_INSERT_OR_EDIT)) { // 新建笔记 long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, Notes.TYPE_WIDGET_INVALIDE); int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, ResourceParser.getDefaultBgId(this)); // 处理通话记录笔记(特殊类型) String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0); if (callDate != 0 && phoneNumber != null) { long noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(), phoneNumber, callDate); if (noteId > 0) { mWorkingNote = WorkingNote.load(this, noteId); // 加载已有通话记录笔记 } else { // 创建新的通话记录笔记 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_STATE_VISIBLE); } else { Log.e(TAG, "不支持的意图操作"); finish(); return false; } // 注册笔记设置变化监听器 mWorkingNote.setOnSettingStatusChangedListener(this); return true; } @Override protected void onResume() { super.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 bgId : sBgSelectorSelectionMap.keySet()) { findViewById(sBgSelectorSelectionMap.get(bgId)).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)); showAlertHeader(); // 显示提醒时间 } /** * 显示提醒时间和图标 */ private void showAlertHeader() { if (mWorkingNote.hasClockAlert()) { long currentTime = System.currentTimeMillis(); if (currentTime > mWorkingNote.getAlertDate()) { mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); // 提醒已过期 } else { // 显示相对时间(如“10分钟后”) mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString( mWorkingNote.getAlertDate(), currentTime, 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); // 处理新的启动意图(如从搜索结果重新打开) } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); // 保存未存入数据库的新笔记(生成ID) if (!mWorkingNote.existInDatabase()) { saveNote(); } outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); // 保存笔记ID } @Override public boolean dispatchTouchEvent(MotionEvent ev) { // 点击外部区域隐藏颜色/字体选择面板 if ((mNoteBgColorSelector.getVisibility() == View.VISIBLE || mFontSizeSelector.getVisibility() == View.VISIBLE) && !inRangeOfView(mNoteBgColorSelector, ev) && !inRangeOfView(mFontSizeSelector, ev)) { mNoteBgColorSelector.setVisibility(View.GONE); mFontSizeSelector.setVisibility(View.GONE); return true; } return super.dispatchTouchEvent(ev); } /** * 判断触摸点是否在指定视图范围内 * @param view 目标视图 * @param ev 触摸事件 * @return 是否在范围内 */ private boolean inRangeOfView(View view, MotionEvent ev) { int[] location = new int[2]; view.getLocationOnScreen(location); int x = location[0], y = location[1]; return ev.getX() >= x && ev.getX() <= x + view.getWidth() && ev.getY() >= y && ev.getY() <= y + view.getHeight(); } /** * 初始化UI组件和资源 */ private void initResources() { // 头部视图组件初始化 mHeadViewPanel = findViewById(R.id.note_title); mNoteHeaderHolder = new HeadViewHolder(); mNoteHeaderHolder.tvModified = findViewById(R.id.tv_modified_date); mNoteHeaderHolder.ivAlertIcon = findViewById(R.id.iv_alert_icon); mNoteHeaderHolder.tvAlertDate = findViewById(R.id.tv_alert_date); mNoteHeaderHolder.ibSetBgColor = findViewById(R.id.btn_set_bg_color); mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); // 设置背景颜色按钮点击监听 // 文本编辑组件初始化 mNoteEditor = findViewById(R.id.note_edit_view); mNoteEditorPanel = findViewById(R.id.sv_note_edit); mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector); // 注册背景颜色选择按钮点击监听 for (int btnId : sBgSelectorBtnsMap.keySet()) { findViewById(btnId).setOnClickListener(this); } // 字体大小选择面板初始化 mFontSizeSelector = findViewById(R.id.font_size_selector); for (int sizeId : sFontSizeBtnsMap.keySet()) { findViewById(sizeId).setOnClickListener(this); } // 加载字体大小偏好设置 mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE); //