diff --git a/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java b/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java index 96a9ff8..b334f54 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java @@ -71,19 +71,23 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; - +/** + * 便签编辑核心页面 + * 核心职责:处理便签的新建/编辑/查看/删除,支持样式定制、清单模式、提醒设置、分享/桌面快捷方式等扩展能力 + * 核心依赖:WorkingNote(便签数据模型)、ResourceParser(样式资源解析)、AlarmManager(提醒功能) + */ public class NoteEditActivity extends Activity implements OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener { + // 标题栏控件持有者:封装便签头部的核心UI控件,避免多次findViewById private class HeadViewHolder { - public TextView tvModified; - - public ImageView ivAlertIcon; - - public TextView tvAlertDate; - - public ImageView ibSetBgColor; + public TextView tvModified; // 便签最后修改时间展示控件 + public ImageView ivAlertIcon; // 提醒功能的图标(有提醒时显示) + public TextView tvAlertDate; // 提醒时间/过期提示文本 + public ImageView ibSetBgColor; // 背景色设置按钮 } + // ===================== 核心映射表:解耦UI控件ID与业务标识 ===================== + // 背景色选择按钮ID → ResourceParser中的颜色常量,统一管理背景色选择逻辑 private static final Map sBgSelectorBtnsMap = new HashMap(); static { sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); @@ -93,6 +97,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); } + // ResourceParser颜色常量 → 背景色选中态控件ID,控制选择器的选中效果展示 private static final Map sBgSelectorSelectionMap = new HashMap(); static { sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); @@ -102,6 +107,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); } + // 字体大小选择按钮ID → ResourceParser中的字体常量,统一管理字体大小选择逻辑 private static final Map sFontSizeBtnsMap = new HashMap(); static { sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); @@ -110,6 +116,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER); } + // ResourceParser字体常量 → 字体选中态控件ID,控制字体选择器的选中效果展示 private static final Map sFontSelectorSelectionMap = new HashMap(); static { sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); @@ -118,56 +125,48 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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; - - 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; - + // ===================== 核心常量:日志/存储/业务规则 ===================== + private static final String TAG = "NoteEditActivity"; // 日志标签 + private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; // 字体大小偏好设置存储Key + 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 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; // 当前选中的字体大小标识(关联ResourceParser) + private LinearLayout mEditTextList; // 清单模式下的编辑列表(包含多个带复选框的编辑项) + private String mUserQuery; // 搜索关键词(从搜索结果跳转时,用于文本高亮) + private Pattern mPattern; // 搜索关键词的正则匹配器(用于文本高亮) + + // ===================== 生命周期核心方法 ===================== @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - this.setContentView(R.layout.note_edit); + setContentView(R.layout.note_edit); + // 初始化页面状态(区分查看/新建/通话记录便签),初始化失败则关闭页面 if (savedInstanceState == null && !initActivityState(getIntent())) { finish(); return; } - initResources(); + 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 + * 恢复Activity状态:当Activity因内存不足被销毁后重建时,恢复之前编辑的便签状态 */ @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); + // 从Bundle中读取之前保存的便签ID,重新初始化页面状态 if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); @@ -179,24 +178,27 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + /** + * 初始化Activity核心状态:根据Intent的Action区分不同的启动场景 + * @param intent 启动Activity的Intent + * @return true-初始化成功,false-初始化失败(如便签不存在) + * 场景1:ACTION_VIEW - 查看已有便签(含从搜索结果跳转) + * 场景2:ACTION_INSERT_OR_EDIT - 新建便签(含通话记录便签) + */ private boolean initActivityState(Intent intent) { - /** - * If the user specified the {@link Intent#ACTION_VIEW} but not provided with id, - * then jump to the NotesListActivity - */ mWorkingNote = null; + // 场景1:查看已有便签 if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); mUserQuery = ""; - /** - * Starting from the searched result - */ + // 处理从搜索结果跳转的场景:获取搜索关键词(用于文本高亮) if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); } + // 校验便签是否存在,不存在则跳转到便签列表页并提示 if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { Intent jump = new Intent(this, NotesListActivity.class); startActivity(jump); @@ -204,6 +206,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, finish(); return false; } else { + // 加载便签数据,加载失败则关闭页面 mWorkingNote = WorkingNote.load(this, noteId); if (mWorkingNote == null) { Log.e(TAG, "load note failed with note id" + noteId); @@ -211,11 +214,14 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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())) { - // New note + } + // 场景2:新建/编辑便签 + else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { + // 解析新建便签的参数:文件夹ID、小组件ID、小组件类型、默认背景色 long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); @@ -224,7 +230,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_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) { @@ -232,6 +238,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, Log.w(TAG, "The call record number is null"); } long noteId = 0; + // 根据手机号和通话时间查询已有便签 if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(), phoneNumber, callDate)) > 0) { mWorkingNote = WorkingNote.load(this, noteId); @@ -241,23 +248,28 @@ public class NoteEditActivity extends Activity implements OnClickListener, return false; } } 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_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); } else { + // 不支持的Intent Action,关闭页面 Log.e(TAG, "Intent not specified action, should not support"); finish(); return false; } + // 注册便签设置变化监听器:监听背景色、提醒、模式等变化 mWorkingNote.setOnSettingStatusChangedListener(this); return true; } @@ -265,48 +277,61 @@ public class NoteEditActivity extends Activity implements OnClickListener, @Override protected void onResume() { super.onResume(); - initNoteScreen(); + initNoteScreen(); // 恢复/初始化便签展示(适配模式、样式、高亮等) } + /** + * 初始化便签展示界面:适配普通/清单模式、应用字体样式、设置背景色、展示修改时间/提醒信息 + */ private void initNoteScreen() { + // 应用字体大小样式 mNoteEditor.setTextAppearance(this, TextAppearanceResources .getTexAppearanceResource(mFontSizeId)); + // 清单模式:切换到带复选框的列表布局 if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { switchToListMode(mWorkingNote.getContent()); - } else { + } + // 普通模式:显示编辑框,高亮搜索关键词 + else { mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); - mNoteEditor.setSelection(mNoteEditor.getText().length()); + 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 { + // 提醒未过期:显示相对时间(如“10分钟后”) 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); }; @@ -315,32 +340,34 @@ public class NoteEditActivity extends Activity implements OnClickListener, @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); - initActivityState(intent); + initActivityState(intent); // 处理新Intent(如从桌面快捷方式再次打开) } @Override protected void onSaveInstanceState(Bundle outState) { 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 - */ + // 新便签(未持久化到数据库)先保存生成ID,避免Activity销毁后数据丢失 if (!mWorkingNote.existInDatabase()) { saveNote(); } + // 保存便签ID到Bundle,用于Activity重建时恢复状态 outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); } + /** + * 触摸事件分发:处理背景色/字体大小选择器的点击外部关闭逻辑 + */ @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); @@ -349,11 +376,18 @@ public class NoteEditActivity extends Activity implements OnClickListener, return super.dispatchTouchEvent(ev); } + /** + * 判断触摸事件是否在指定View范围内 + * @param view 目标View + * @param ev 触摸事件 + * @return true-在范围内,false-不在范围内 + */ private boolean inRangeOfView(View view, MotionEvent ev) { int []location = new int[2]; view.getLocationOnScreen(location); int x = location[0]; int y = location[1]; + // 校验触摸坐标是否在View的矩形范围内 if (ev.getX() < x || ev.getX() > (x + view.getWidth()) || ev.getY() < y @@ -363,51 +397,63 @@ public class NoteEditActivity extends Activity implements OnClickListener, return true; } + /** + * 初始化资源:绑定UI控件、设置监听器、读取偏好设置(字体大小) + */ 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); + 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); - /** - * 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; } + // 绑定清单模式的编辑列表 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(); + clearSettingState(); // 关闭背景色/字体大小选择器面板 } + /** + * 更新桌面小组件:当便签关联小组件时,同步更新小组件内容 + */ private void updateWidget() { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + // 区分2x/4x小组件类型,指定对应的Provider if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) { intent.setClass(this, NoteWidgetProvider_2x.class); } else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) { @@ -417,6 +463,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, return; } + // 传入小组件ID,发送更新广播 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { mWorkingNote.getWidgetId() }); @@ -425,22 +472,34 @@ public class NoteEditActivity extends Activity implements OnClickListener, setResult(RESULT_OK, intent); } + /** + * 点击事件处理:处理背景色/字体大小选择器的核心交互 + * @param v 被点击的View + */ public void onClick(View v) { int id = v.getId(); + // 打开背景色选择器,显示当前选中的背景色 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)) { + View.VISIBLE); + } + // 选择背景色:更新便签背景色,关闭选择器 + else if (sBgSelectorBtnsMap.containsKey(id)) { findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( View.GONE); mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); mNoteBgColorSelector.setVisibility(View.GONE); - } else if (sFontSizeBtnsMap.containsKey(id)) { + } + // 选择字体大小:持久化设置,更新UI样式,关闭选择器 + else if (sFontSizeBtnsMap.containsKey(id)) { findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE); mFontSizeId = sFontSizeBtnsMap.get(id); + // 持久化字体大小到偏好设置 mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit(); findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); + + // 适配清单/普通模式的字体样式更新 if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { getWorkingText(); switchToListMode(mWorkingNote.getContent()); @@ -452,51 +511,85 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + /** + * 返回键事件处理:优先关闭背景色/字体大小选择器面板,再保存便签并执行默认返回逻辑 + * 设计意图:避免用户误触返回键时丢失设置面板的操作,同时保证便签数据不丢失 + */ @Override public void onBackPressed() { + // 若关闭了设置面板(背景色/字体选择器),则直接返回,不执行后续逻辑 if(clearSettingState()) { return; } + // 保存当前便签内容,避免数据丢失 saveNote(); + // 执行系统默认的返回逻辑 super.onBackPressed(); } + /** + * 清理设置面板状态:关闭背景色/字体大小选择器 + * @return true-关闭了某个面板,false-无面板需要关闭 + */ private boolean clearSettingState() { + // 关闭背景色选择器 if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { mNoteBgColorSelector.setVisibility(View.GONE); return true; - } else if (mFontSizeSelector.getVisibility() == View.VISIBLE) { + } + // 关闭字体大小选择器 + else if (mFontSizeSelector.getVisibility() == View.VISIBLE) { mFontSizeSelector.setVisibility(View.GONE); return true; } return false; } + /** + * 背景色变化回调:当便签背景色修改后,更新UI的背景色展示和选择器选中态 + * (WorkingNote背景色变更后触发此回调) + */ public void onBackgroundColorChanged() { + // 更新背景色选择器的选中态 findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( View.VISIBLE); + // 更新编辑区域和标题栏的背景色 mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); } + /** + * 准备选项菜单:根据便签类型/状态动态加载菜单,更新菜单显示内容 + * @param menu 要初始化的菜单 + * @return true-菜单初始化成功 + */ @Override public boolean onPrepareOptionsMenu(Menu menu) { + // 页面正在关闭时,无需初始化菜单 if (isFinishing()) { 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 { @@ -505,46 +598,59 @@ public class NoteEditActivity extends Activity implements OnClickListener, return true; } + /** + * 选项菜单点击事件处理:处理所有菜单项的核心业务逻辑 + * @param item 被点击的菜单项 + * @return true-事件已处理 + */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { 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.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(); + deleteCurrentNote(); // 执行删除逻辑 + finish(); // 关闭编辑页面 } }); - builder.setNegativeButton(android.R.string.cancel, null); + 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: + // 切换便签模式:普通↔清单(0=普通模式,MODE_CHECK_LIST=清单模式) 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); break; default: @@ -553,111 +659,161 @@ public class NoteEditActivity extends Activity implements OnClickListener, return 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(); + d.show(); // 显示对话框 } /** - * Share note to apps that support {@link Intent#ACTION_SEND} action - * and {@text/plain} type + * 分享便签内容:调用系统的ACTION_SEND接口,分享纯文本格式的便签内容 + * @param context 上下文 + * @param info 要分享的便签文本内容 */ 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); + 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(); Intent intent = new Intent(this, NoteEditActivity.class); - intent.setAction(Intent.ACTION_INSERT_OR_EDIT); - intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId()); + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); // 标记为新建/编辑模式 + intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId()); // 继承当前文件夹ID startActivity(intent); } + /** + * 删除当前便签:区分同步模式和非同步模式,执行不同的删除逻辑 + * 同步模式:移到回收站;非同步模式:直接从数据库删除 + */ private void deleteCurrentNote() { + // 仅处理已持久化到数据库的便签 if (mWorkingNote.existInDatabase()) { HashSet ids = new HashSet(); long id = mWorkingNote.getNoteId(); + // 校验便签ID有效性(避免根文件夹ID) if (id != Notes.ID_ROOT_FOLDER) { ids.add(id); } else { Log.d(TAG, "Wrong note id, should not happen"); } + + // 非同步模式:直接删除便签 if (!isSyncMode()) { if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) { Log.e(TAG, "Delete Note error"); } - } else { + } + // 同步模式:将便签移到回收站(而非直接删除) + else { if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) { Log.e(TAG, "Move notes to trash folder error, should not happens"); } } } + // 标记便签为已删除(更新WorkingNote状态) mWorkingNote.markDeleted(true); } + /** + * 判断是否为同步模式:检测是否配置了同步账号(有账号则为同步模式) + * @return true-同步模式,false-非同步模式 + */ private boolean isSyncMode() { return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; } + /** + * 提醒时间变化回调:当便签提醒时间修改后,注册/取消系统闹钟 + * @param date 新的提醒时间戳 + * @param set true-设置提醒,false-取消提醒 + */ 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 + * 处理未保存的便签:设置提醒前必须先保存(否则无NoteID,无法绑定闹钟) */ if (!mWorkingNote.existInDatabase()) { saveNote(); } + + // 仅处理有效NoteID的便签 if (mWorkingNote.getNoteId() > 0) { + // 构建提醒广播Intent(指向AlarmReceiver,用于接收闹钟触发事件) Intent intent = new Intent(this, AlarmReceiver.class); intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId())); + // 创建PendingIntent:广播类型,用于AlarmManager触发 PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE)); + + // 更新提醒UI展示 showAlertHeader(); + + // 取消提醒:取消已注册的闹钟 if(!set) { alarmManager.cancel(pendingIntent); - } else { + } + // 设置提醒:注册新的闹钟(RTC_WAKEUP模式:唤醒设备触发) + 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 + * 异常场景:便签无有效ID(未输入内容/未保存),提示用户输入内容 */ Log.e(TAG, "Clock alert setting error"); showToast(R.string.error_note_empty_for_clock); } } + /** + * 小组件关联变化回调:当便签关联的小组件信息变更时,更新小组件内容 + */ public void onWidgetChanged() { updateWidget(); } + /** + * 清单模式-删除项回调:删除指定索引的清单项,调整剩余项的索引并合并文本 + * @param index 要删除的项索引 + * @param text 被删除项的文本内容(合并到前一项) + */ public void onEditTextDelete(int index, String text) { int childCount = mEditTextList.getChildCount(); + // 仅保留最后一项时,不执行删除(避免清单列表为空) if (childCount == 1) { return; } + // 调整删除项之后的所有项的索引(索引-1) for (int i = index + 1; i < childCount; i++) { ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) .setIndex(i - 1); } + // 移除指定索引的清单项 mEditTextList.removeViewAt(index); + + // 获取合并目标项(删除项的前一项,索引0则取第一项) NoteEditText edit = null; if(index == 0) { edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById( @@ -666,207 +822,315 @@ public class NoteEditActivity extends Activity implements OnClickListener, edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById( R.id.et_edit_text); } + + // 合并被删除项的文本到目标项,光标定位到合并后的末尾 int length = edit.length(); edit.append(text); edit.requestFocus(); edit.setSelection(length); } + /** + * 清单模式-回车回调:在指定索引位置添加新的清单项,调整后续项的索引 + * @param index 回车的项索引 + * @param text 原项中回车后的文本内容(拆分到新项) + */ public void onEditTextEnter(int index, String text) { /** - * Should not happen, check for debug + * 异常校验:索引超出列表长度(理论上不会触发,仅调试用) */ if(index > mEditTextList.getChildCount()) { Log.e(TAG, "Index out of mEditTextList boundrary, should not happen"); } + // 创建新的清单项View,插入到指定索引位置 View view = getListItem(text, index); mEditTextList.addView(view, index); + + // 新项获取焦点,光标定位到开头 NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); edit.requestFocus(); edit.setSelection(0); + + // 调整新项之后的所有项的索引(索引+1) for (int i = index + 1; i < mEditTextList.getChildCount(); i++) { ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) .setIndex(i); } } + /** + * 切换到清单模式:将便签文本按换行拆分,生成对应的清单项列表 + * @param text 便签的原始文本内容 + */ private void switchToListMode(String text) { + // 清空原有清单项 mEditTextList.removeAllViews(); + // 按换行拆分文本为单个清单项 String[] items = text.split("\n"); int index = 0; + // 遍历拆分后的文本,创建并添加清单项 for (String item : items) { if(!TextUtils.isEmpty(item)) { mEditTextList.addView(getListItem(item, index)); index++; } } + // 添加空的清单项(用于继续编辑) mEditTextList.addView(getListItem("", index)); + // 空项获取焦点,便于用户继续输入 mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus(); + // 切换UI显示:隐藏普通编辑框,显示清单列表 mNoteEditor.setVisibility(View.GONE); mEditTextList.setVisibility(View.VISIBLE); } + /** + * 搜索关键词高亮:为匹配搜索关键词的文本添加背景色Span + * @param fullText 便签完整文本 + * @param userQuery 搜索关键词 + * @return 带高亮效果的Spannable文本 + */ private Spannable getHighlightQueryResult(String fullText, String userQuery) { + // 初始化Spannable(兼容null文本) SpannableString spannable = new SpannableString(fullText == null ? "" : fullText); + // 仅当关键词非空时,执行高亮逻辑 if (!TextUtils.isEmpty(userQuery)) { - mPattern = Pattern.compile(userQuery); - Matcher m = mPattern.matcher(fullText); + mPattern = Pattern.compile(userQuery); // 编译关键词正则 + Matcher m = mPattern.matcher(fullText); // 匹配文本 int start = 0; + // 遍历所有匹配项,添加背景色Span 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(); + R.color.user_query_highlight)), // 高亮背景色 + m.start(), m.end(), // 匹配文本的起止索引 + Spannable.SPAN_INCLUSIVE_EXCLUSIVE); // Span模式:包含起始,不包含结束 + start = m.end(); // 更新起始位置,避免重复匹配 } } return spannable; } + /** + * 创建清单项View:加载布局,绑定复选框和编辑框,处理勾选状态和文本高亮 + * @param item 清单项的文本内容 + * @param index 清单项的索引 + * @return 组装好的清单项View + */ 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 { + } + // 取消勾选:移除删除线,恢复默认样式 + 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(); + 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(); + item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); // 移除标记符 } + // 绑定文本变化监听(用于控制复选框显隐) edit.setOnTextViewChangeListener(this); + // 设置清单项索引 edit.setIndex(index); + // 设置文本(含搜索高亮) edit.setText(getHighlightQueryResult(item, mUserQuery)); return view; } + /** + * 清单项文本变化回调:根据文本是否为空,控制复选框的显隐 + * @param index 清单项索引 + * @param hasText 文本是否非空 + */ 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 { + } + // 文本为空:隐藏复选框 + else { mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE); } } + /** + * 模式切换回调:当便签在普通/清单模式间切换时,更新UI和数据 + * @param oldMode 旧模式(0=普通,MODE_CHECK_LIST=清单) + * @param newMode 新模式 + */ public void onCheckListModeChanged(int oldMode, int newMode) { + // 切换到清单模式:将普通编辑框的文本转为清单项 if (newMode == TextNote.MODE_CHECK_LIST) { switchToListMode(mNoteEditor.getText().toString()); - } else { + } + // 切换到普通模式:将清单项合并为文本,显示到普通编辑框 + else { + // 获取清单项内容,合并为文本 if (!getWorkingText()) { + // 清理未勾选标记符(避免普通模式显示冗余标记) mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ", "")); } + // 显示合并后的文本(含搜索高亮),切换UI显示 mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); mEditTextList.setVisibility(View.GONE); mNoteEditor.setVisibility(View.VISIBLE); } } + /** + * 获取编辑内容:根据当前模式(普通/清单),读取编辑框/清单项的文本,更新到WorkingNote + * @return true-清单模式下存在已勾选项,false-无勾选项/普通模式 + */ 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 { + } + // 未勾选:添加未勾选标记符 + else { sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n"); } } } + // 更新WorkingNote的内容 mWorkingNote.setWorkingText(sb.toString()); - } else { + } + // 普通模式:直接读取编辑框文本 + else { mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); } return hasChecked; } + /** + * 保存便签:先获取最新编辑内容,再通过WorkingNote持久化到数据库 + * @return true-保存成功,false-保存失败 + */ 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 + * 设置返回结果:区分“查看便签返回”和“新建/编辑便签返回” + * RESULT_OK:告知列表页,需要刷新数据(新建/编辑后) */ setResult(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 + * 前置校验:新便签必须先保存(否则无NoteID,无法创建快捷方式) */ if (!mWorkingNote.existInDatabase()) { saveNote(); } + // 仅处理有效NoteID的便签 if (mWorkingNote.getNoteId() > 0) { + // 构建快捷方式的Intent(指向当前便签的查看页面) Intent sender = new Intent(); Intent shortcutIntent = new Intent(this, NoteEditActivity.class); - shortcutIntent.setAction(Intent.ACTION_VIEW); - shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId()); - sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); - sender.putExtra(Intent.EXTRA_SHORTCUT_NAME, - makeShortcutIconTitle(mWorkingNote.getContent())); - sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, - Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app)); - sender.putExtra("duplicate", true); - sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); + shortcutIntent.setAction(Intent.ACTION_VIEW); // 标记为查看模式 + shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId()); // 绑定便签ID + + // 设置快捷方式参数 + sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); // 点击快捷方式触发的Intent + sender.putExtra(Intent.EXTRA_SHORTCUT_NAME, makeShortcutIconTitle(mWorkingNote.getContent())); // 快捷方式标题 + sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app)); // 快捷方式图标 + sender.putExtra("duplicate", true); // 允许重复创建(同一便签可创建多个快捷方式) + sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); // 桌面快捷方式创建广播 + + // 提示用户创建成功 showToast(R.string.info_note_enter_desktop); + // 发送广播,通知桌面启动器创建快捷方式 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 + * 异常场景:便签无有效ID(未输入内容/未保存),提示用户输入内容 */ Log.e(TAG, "Send to desktop error"); showToast(R.string.error_note_empty_for_send_to_desktop); } } + /** + * 生成桌面快捷方式标题:清理清单标记符,截断超长文本(最多10个字符) + * @param content 便签原始内容 + * @return 处理后的快捷方式标题 + */ private String makeShortcutIconTitle(String content) { + // 移除清单模式的勾选/未勾选标记符 content = content.replace(TAG_CHECKED, ""); content = content.replace(TAG_UNCHECKED, ""); + // 截断超长文本,最多保留10个字符 return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0, SHORTCUT_ICON_TITLE_MAX_LEN) : content; } + /** + * 显示短时长Toast提示(封装方法,简化调用) + * @param resId 提示文本的资源ID + */ private void showToast(int resId) { showToast(resId, Toast.LENGTH_SHORT); } + /** + * 显示指定时长的Toast提示 + * @param resId 提示文本的资源ID + * @param duration 时长(Toast.LENGTH_SHORT/Toast.LENGTH_LONG) + */ private void showToast(int resId, int duration) { Toast.makeText(this, resId, duration).show(); } diff --git a/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java b/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java index e843aec..00df199 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java @@ -78,77 +78,86 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.util.HashSet; +/** + * 便签列表核心页面 + * 核心职责:展示便签/文件夹列表、处理便签的新建/批量删除/批量移动、文件夹管理(重命名/删除/查看)、 + * 首次使用引导、同步模式适配、桌面小组件关联更新等 + * 核心依赖:AsyncQueryHandler(异步数据库查询)、NotesListAdapter(列表数据适配)、DataUtils(数据操作工具) + */ public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener { - private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0; - - private static final int FOLDER_LIST_QUERY_TOKEN = 1; - - private static final int MENU_FOLDER_DELETE = 0; + // ===================== 异步查询Token:区分不同类型的异步查询任务 ===================== + private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0; // 文件夹下便签列表查询标识 + private static final int FOLDER_LIST_QUERY_TOKEN = 1; // 文件夹列表查询标识(用于移动便签时选择目标文件夹) - private static final int MENU_FOLDER_VIEW = 1; - - private static final int MENU_FOLDER_CHANGE_NAME = 2; + // ===================== 文件夹上下文菜单ID:区分文件夹操作类型 ===================== + private static final int MENU_FOLDER_DELETE = 0; // 删除文件夹 + private static final int MENU_FOLDER_VIEW = 1; // 查看文件夹下的便签 + private static final int MENU_FOLDER_CHANGE_NAME = 2;// 重命名文件夹 + // ===================== 偏好设置Key:标记是否已展示首次使用引导 ===================== private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction"; + // ===================== 列表编辑状态枚举:区分当前列表展示的内容类型 ===================== private enum ListEditState { - NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER + NOTE_LIST, // 普通便签列表状态(默认) + SUB_FOLDER, // 子文件夹列表状态 + CALL_RECORD_FOLDER// 通话记录文件夹状态(专属) }; - private ListEditState mState; - - private BackgroundQueryHandler mBackgroundQueryHandler; - - private NotesListAdapter mNotesListAdapter; - - private ListView mNotesListView; - - private Button mAddNewNote; - - private boolean mDispatch; - - private int mOriginY; - - private int mDispatchY; - - private TextView mTitleBar; - - private long mCurrentFolderId; - - private ContentResolver mContentResolver; - - private ModeCallback mModeCallBack; - - private static final String TAG = "NotesListActivity"; - - public static final int NOTES_LISTVIEW_SCROLL_RATE = 30; - - private NoteItemData mFocusNoteDataItem; - + // ===================== 核心成员变量 ===================== + private ListEditState mState; // 当前列表编辑状态(枚举) + private BackgroundQueryHandler mBackgroundQueryHandler; // 异步数据库查询处理器(避免主线程阻塞) + private NotesListAdapter mNotesListAdapter; // 便签列表适配器(绑定数据与UI) + private ListView mNotesListView; // 便签列表展示控件 + private Button mAddNewNote; // 新建便签按钮 + private boolean mDispatch; // 新建按钮触摸事件分发标记(处理透明区域事件透传) + private int mOriginY; // 新建按钮触摸事件原始Y坐标(用于事件分发计算) + private int mDispatchY; // 新建按钮触摸事件分发后的Y坐标 + private TextView mTitleBar; // 标题栏文本控件(显示当前文件夹名称) + private long mCurrentFolderId; // 当前选中的文件夹ID(默认根文件夹) + private ContentResolver mContentResolver; // 内容解析器(操作ContentProvider) + private ModeCallback mModeCallBack; // 列表多选模式回调(处理批量操作) + private static final String TAG = "NotesListActivity"; // 日志标签 + public static final int NOTES_LISTVIEW_SCROLL_RATE = 30; // 列表滚动速率(自定义滚动行为) + private NoteItemData mFocusNoteDataItem; // 当前聚焦的便签数据项(用于多选模式初始化) + + // ===================== 数据库查询条件:区分根文件夹和普通文件夹的查询逻辑 ===================== + // 普通文件夹查询条件:仅查询指定父文件夹下的便签 private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?"; - + // 根文件夹查询条件:排除系统文件夹 + 包含有内容的通话记录文件夹 private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>" + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR (" + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND " + NoteColumns.NOTES_COUNT + ">0)"; - private final static int REQUEST_CODE_OPEN_NODE = 102; - private final static int REQUEST_CODE_NEW_NODE = 103; + // ===================== Activity请求码:区分不同场景的返回结果 ===================== + private final static int REQUEST_CODE_OPEN_NODE = 102; // 打开已有便签的请求码 + private final static int REQUEST_CODE_NEW_NODE = 103; // 新建便签的请求码 + /** + * 生命周期-创建:初始化资源、加载布局、执行首次使用引导逻辑 + */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.note_list); - initResources(); + setContentView(R.layout.note_list); // 加载列表布局 + initResources(); // 初始化控件、适配器、查询处理器等资源 /** - * Insert an introduction when user firstly use this application + * 首次使用引导:首次打开应用时,从raw资源读取引导文本,创建引导便签 */ setAppInfoFromRawRes(); } + /** + * 生命周期-Activity返回结果处理:处理从NoteEditActivity返回的结果 + * @param requestCode 请求码(区分打开/新建便签) + * @param resultCode 结果码(RESULT_OK表示操作成功) + * @param data 返回的Intent数据 + */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // 打开/新建便签返回且操作成功:清空列表游标,触发重新查询(刷新列表) if (resultCode == RESULT_OK && (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) { mNotesListAdapter.changeCursor(null); @@ -157,14 +166,21 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 首次使用引导:从raw资源读取引导文本,创建引导便签(仅首次打开应用时执行) + * 设计意图:降低用户使用门槛,展示应用核心功能 + */ private void setAppInfoFromRawRes() { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + // 已展示过引导,直接返回 if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) { StringBuilder sb = new StringBuilder(); InputStream in = null; try { + // 打开raw目录下的introduction.txt资源(引导文本) in = getResources().openRawResource(R.raw.introduction); if (in != null) { + // 读取引导文本内容 InputStreamReader isr = new InputStreamReader(in); BufferedReader br = new BufferedReader(isr); char [] buf = new char[1024]; @@ -180,20 +196,22 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt e.printStackTrace(); return; } finally { + // 关闭输入流,避免资源泄漏 if(in != null) { try { in.close(); } catch (IOException e) { - // TODO Auto-generated catch block e.printStackTrace(); } } } + // 创建引导便签:根文件夹、红色背景、无小组件关联 WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER, AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE, ResourceParser.RED); - note.setWorkingText(sb.toString()); + note.setWorkingText(sb.toString()); // 设置引导文本 + // 保存引导便签成功后,标记已展示引导(持久化到偏好设置) if (note.saveNote()) { sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit(); } else { @@ -203,116 +221,183 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 生命周期-启动:启动异步查询,加载当前文件夹下的便签列表 + */ @Override protected void onStart() { super.onStart(); startAsyncNotesListQuery(); } + /** + * 初始化资源:绑定UI控件、初始化适配器/查询处理器、设置监听器、初始化状态 + * 设计意图:集中管理资源初始化,避免分散在多个生命周期方法中 + */ private void initResources() { - mContentResolver = this.getContentResolver(); + mContentResolver = this.getContentResolver(); // 获取内容解析器 + // 初始化异步查询处理器(用于数据库异步查询) mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); - mCurrentFolderId = Notes.ID_ROOT_FOLDER; - mNotesListView = (ListView) findViewById(R.id.notes_list); + mCurrentFolderId = Notes.ID_ROOT_FOLDER; // 默认选中根文件夹 + mNotesListView = (ListView) findViewById(R.id.notes_list); // 绑定列表控件 + + // 为列表添加底部视图(footer),避免列表为空时UI塌陷 mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null), null, false); + // 设置列表项点击监听器(打开便签/进入文件夹) mNotesListView.setOnItemClickListener(new OnListItemClickListener()); + // 设置列表项长按监听器(触发多选模式) mNotesListView.setOnItemLongClickListener(this); + // 初始化列表适配器 mNotesListAdapter = new NotesListAdapter(this); - mNotesListView.setAdapter(mNotesListAdapter); + mNotesListView.setAdapter(mNotesListAdapter); // 绑定适配器到列表 + + // 绑定新建便签按钮,设置点击/触摸监听器 mAddNewNote = (Button) findViewById(R.id.btn_new_note); - mAddNewNote.setOnClickListener(this); - mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); + mAddNewNote.setOnClickListener(this); // 点击:新建便签 + mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); // 触摸:处理透明区域事件透传 + + // 初始化事件分发相关变量 mDispatch = false; mDispatchY = 0; mOriginY = 0; + // 绑定标题栏控件 mTitleBar = (TextView) findViewById(R.id.tv_title_bar); + // 初始化列表状态为普通便签列表 mState = ListEditState.NOTE_LIST; + // 初始化多选模式回调 mModeCallBack = new ModeCallback(); } + /** + * 列表多选模式回调:处理便签批量操作(删除/移动)、多选UI状态管理 + * 实现MultiChoiceModeListener:监听多选模式的创建/准备/销毁/项选中状态变化 + * 实现OnMenuItemClickListener:监听批量操作菜单点击 + */ private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener { - private DropdownMenu mDropDownMenu; - private ActionMode mActionMode; - private MenuItem mMoveMenu; + private DropdownMenu mDropDownMenu; // 多选模式下的下拉菜单(全选/取消全选) + private ActionMode mActionMode; // 多选模式的ActionMode(顶部操作栏) + private MenuItem mMoveMenu; // 移动便签菜单项(根据场景控制显隐) + /** + * 创建多选模式:初始化菜单、设置菜单点击监听、适配移动菜单显隐、创建自定义下拉菜单 + * @param mode 多选模式的ActionMode + * @param menu 要初始化的菜单 + * @return true-创建成功 + */ public boolean onCreateActionMode(ActionMode mode, Menu menu) { + // 加载批量操作菜单(删除/移动) getMenuInflater().inflate(R.menu.note_list_options, menu); + // 设置删除菜单点击监听 menu.findItem(R.id.delete).setOnMenuItemClickListener(this); mMoveMenu = menu.findItem(R.id.move); + + // 控制移动菜单显隐: + // 1. 通话记录文件夹下的便签不可移动;2. 无用户文件夹时不可移动 if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER || DataUtils.getUserFolderCount(mContentResolver) == 0) { mMoveMenu.setVisible(false); } else { mMoveMenu.setVisible(true); - mMoveMenu.setOnMenuItemClickListener(this); + mMoveMenu.setOnMenuItemClickListener(this); // 设置移动菜单点击监听 } + mActionMode = mode; - mNotesListAdapter.setChoiceMode(true); - mNotesListView.setLongClickable(false); - mAddNewNote.setVisibility(View.GONE); + mNotesListAdapter.setChoiceMode(true); // 适配器切换到多选模式 + mNotesListView.setLongClickable(false); // 多选模式下禁用长按(避免重复触发) + mAddNewNote.setVisibility(View.GONE); // 隐藏新建便签按钮(避免干扰) + // 创建自定义多选顶部视图(包含全选/取消全选下拉菜单) View customView = LayoutInflater.from(NotesListActivity.this).inflate( R.layout.note_list_dropdown_menu, null); mode.setCustomView(customView); + // 初始化下拉菜单(绑定按钮与菜单资源) mDropDownMenu = new DropdownMenu(NotesListActivity.this, (Button) customView.findViewById(R.id.selection_menu), R.menu.note_list_dropdown); + // 设置下拉菜单点击监听(全选/取消全选) mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener(){ public boolean onMenuItemClick(MenuItem item) { + // 切换全选状态:已全选→取消全选,未全选→全选 mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected()); - updateMenu(); + updateMenu(); // 更新菜单标题(显示选中数量) return true; } - }); return true; } + /** + * 更新多选菜单:同步选中数量到下拉菜单标题,更新全选/取消全选按钮文本 + */ private void updateMenu() { int selectedCount = mNotesListAdapter.getSelectedCount(); - // Update dropdown menu + // 更新下拉菜单标题(如“已选择 3 项”) String format = getResources().getString(R.string.menu_select_title, selectedCount); mDropDownMenu.setTitle(format); + // 更新全选/取消全选按钮文本 MenuItem item = mDropDownMenu.findItem(R.id.action_select_all); if (item != null) { if (mNotesListAdapter.isAllSelected()) { item.setChecked(true); - item.setTitle(R.string.menu_deselect_all); + item.setTitle(R.string.menu_deselect_all); // 已全选→显示“取消全选” } else { item.setChecked(false); - item.setTitle(R.string.menu_select_all); + item.setTitle(R.string.menu_select_all); // 未全选→显示“全选” } } } + /** + * 准备多选模式:预留扩展接口(当前无逻辑) + */ public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - // TODO Auto-generated method stub return false; } + /** + * 多选模式菜单项点击:预留扩展接口(当前无逻辑,实际点击监听在onMenuItemClick) + */ public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - // TODO Auto-generated method stub return false; } + /** + * 销毁多选模式:恢复列表默认状态、显示新建便签按钮 + */ public void onDestroyActionMode(ActionMode mode) { - mNotesListAdapter.setChoiceMode(false); - mNotesListView.setLongClickable(true); - mAddNewNote.setVisibility(View.VISIBLE); + mNotesListAdapter.setChoiceMode(false); // 适配器退出多选模式 + mNotesListView.setLongClickable(true); // 恢复列表项长按 + mAddNewNote.setVisibility(View.VISIBLE); // 显示新建便签按钮 } + /** + * 结束多选模式:主动关闭ActionMode + */ public void finishActionMode() { mActionMode.finish(); } + /** + * 列表项选中状态变化:同步适配器的选中状态,更新菜单 + * @param mode 多选模式ActionMode + * @param position 列表项位置 + * @param id 列表项ID + * @param checked 是否选中 + */ public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { - mNotesListAdapter.setCheckedItem(position, checked); - updateMenu(); + mNotesListAdapter.setCheckedItem(position, checked); // 更新适配器选中状态 + updateMenu(); // 同步更新菜单标题 } + /** + * 多选模式菜单点击处理:处理删除/移动操作 + * @param item 被点击的菜单项 + * @return true-事件已处理 + */ public boolean onMenuItemClick(MenuItem item) { + // 无选中项时,提示用户选择便签 if (mNotesListAdapter.getSelectedCount() == 0) { Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none), Toast.LENGTH_SHORT).show(); @@ -321,22 +406,24 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt switch (item.getItemId()) { case R.id.delete: + // 批量删除:弹窗确认,确认后执行批量删除逻辑 AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); builder.setTitle(getString(R.string.alert_title_delete)); - builder.setIcon(android.R.drawable.ic_dialog_alert); + builder.setIcon(android.R.drawable.ic_dialog_alert); // 警告图标 builder.setMessage(getString(R.string.alert_message_delete_notes, - mNotesListAdapter.getSelectedCount())); + mNotesListAdapter.getSelectedCount())); // 提示删除数量 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - batchDelete(); + batchDelete(); // 执行批量删除 } }); - builder.setNegativeButton(android.R.string.cancel, null); + builder.setNegativeButton(android.R.string.cancel, null); // 取消无操作 builder.show(); break; case R.id.move: + // 批量移动:查询目标文件夹列表,展示文件夹选择菜单 startQueryDestinationFolders(); break; default: @@ -346,47 +433,51 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 新建便签按钮触摸事件监听器:处理按钮透明区域的事件透传 + * 设计意图:新建按钮有透明区域,点击透明区域时,事件需透传到下方的列表控件(滚动/点击列表项) + */ private class NewNoteOnTouchListener implements OnTouchListener { public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { + // 获取屏幕/按钮尺寸,计算事件坐标 Display display = getWindowManager().getDefaultDisplay(); int screenHeight = display.getHeight(); int newNoteViewHeight = mAddNewNote.getHeight(); int start = screenHeight - newNoteViewHeight; int eventY = start + (int) event.getY(); - /** - * Minus TitleBar's height - */ + + // 子文件夹状态下,扣除标题栏高度(坐标校准) if (mState == ListEditState.SUB_FOLDER) { eventY -= mTitleBar.getHeight(); start -= mTitleBar.getHeight(); } + /** - * HACKME:When click the transparent part of "New Note" button, dispatch - * the event to the list view behind this button. The transparent part of - * "New Note" button could be expressed by formula y=-0.12x+94(Unit:pixel) - * and the line top of the button. The coordinate based on left of the "New - * Note" button. The 94 represents maximum height of the transparent part. - * Notice that, if the background of the button changes, the formula should - * also change. This is very bad, just for the UI designer's strong requirement. + * 核心逻辑:判断是否点击了新建按钮的透明区域 + * 透明区域公式:y=-0.12x+94(像素),基于按钮左侧坐标 + * (UI设计要求,若按钮背景变更,公式需同步调整) */ if (event.getY() < (event.getX() * (-0.12) + 94)) { + // 获取列表最后一个可见项(排除footer) View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1 - mNotesListView.getFooterViewsCount()); + // 列表项在透明区域范围内:触发事件分发 if (view != null && view.getBottom() > start && (view.getTop() < (start + 94))) { - mOriginY = (int) event.getY(); - mDispatchY = eventY; - event.setLocation(event.getX(), mDispatchY); - mDispatch = true; - return mNotesListView.dispatchTouchEvent(event); + mOriginY = (int) event.getY(); // 记录原始Y坐标 + mDispatchY = eventY; // 初始化分发Y坐标 + event.setLocation(event.getX(), mDispatchY); // 校准事件坐标 + mDispatch = true; // 标记为分发状态 + return mNotesListView.dispatchTouchEvent(event); // 分发事件到列表 } } break; } case MotionEvent.ACTION_MOVE: { + // 分发状态下:同步移动坐标,继续分发事件到列表 if (mDispatch) { mDispatchY += (int) event.getY() - mOriginY; event.setLocation(event.getX(), mDispatchY); @@ -395,6 +486,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt break; } default: { + // 触摸结束:分发最后一次事件,重置分发标记 if (mDispatch) { event.setLocation(event.getX(), mDispatchY); mDispatch = false; @@ -403,32 +495,55 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt break; } } - return false; + return false; // 非透明区域:不分发,交由按钮默认处理 } - }; + /** + * 启动异步查询:查询当前文件夹下的便签列表(区分根文件夹/普通文件夹查询条件) + * 设计意图:异步查询避免主线程阻塞,提升UI流畅度 + */ private void startAsyncNotesListQuery() { + // 选择查询条件:根文件夹使用特殊条件,普通文件夹使用默认条件 String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION : NORMAL_SELECTION; + // 启动异步查询: + // Token:FOLDER_NOTE_LIST_QUERY_TOKEN(标识为便签列表查询) + // Uri:Notes.CONTENT_NOTE_URI(便签内容URI) + // Projection:NoteItemData.PROJECTION(查询字段) + // Selection:上述查询条件 + // SelectionArgs:当前文件夹ID + // SortOrder:按类型降序、修改时间降序(系统文件夹/便签区分,最新修改在前) mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null, Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] { String.valueOf(mCurrentFolderId) }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); } + /** + * 异步查询处理器:处理数据库异步查询的结果回调 + * 继承AsyncQueryHandler:封装异步查询逻辑,简化主线程与子线程通信 + */ private final class BackgroundQueryHandler extends AsyncQueryHandler { public BackgroundQueryHandler(ContentResolver contentResolver) { super(contentResolver); } + /** + * 查询完成回调:根据Token区分查询类型,处理查询结果 + * @param token 查询标识(区分便签列表/文件夹列表) + * @param cookie 自定义参数(当前未使用) + * @param cursor 查询结果游标 + */ @Override protected void onQueryComplete(int token, Object cookie, Cursor cursor) { switch (token) { case FOLDER_NOTE_LIST_QUERY_TOKEN: + // 便签列表查询完成:更新适配器游标(刷新列表) mNotesListAdapter.changeCursor(cursor); break; case FOLDER_LIST_QUERY_TOKEN: + // 文件夹列表查询完成:展示文件夹选择菜单(批量移动便签时) if (cursor != null && cursor.getCount() > 0) { showFolderListMenu(cursor); } else { @@ -441,48 +556,73 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 展示文件夹选择菜单:用于批量移动便签时选择目标文件夹 + * @param cursor 文件夹列表游标 + */ private void showFolderListMenu(Cursor cursor) { AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); - builder.setTitle(R.string.menu_title_select_folder); - final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor); + builder.setTitle(R.string.menu_title_select_folder); // 标题:选择文件夹 + final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor); // 文件夹列表适配器 builder.setAdapter(adapter, new DialogInterface.OnClickListener() { - + /** + * 选择文件夹回调:执行批量移动操作,提示用户,结束多选模式 + * @param dialog 对话框 + * @param which 选中的文件夹位置 + */ public void onClick(DialogInterface dialog, int which) { + // 批量移动便签到选中的文件夹 DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which)); + // 提示移动成功(显示移动数量和目标文件夹名称) Toast.makeText( NotesListActivity.this, getString(R.string.format_move_notes_to_folder, mNotesListAdapter.getSelectedCount(), adapter.getFolderName(NotesListActivity.this, which)), Toast.LENGTH_SHORT).show(); + // 结束多选模式 mModeCallBack.finishActionMode(); } }); - builder.show(); + builder.show(); // 显示对话框 } + /** + * 新建便签:启动NoteEditActivity,标记为新建模式,传递当前文件夹ID + * 设计意图:新建便签默认归属当前文件夹,保持文件夹上下文一致性 + */ private void createNewNote() { Intent intent = new Intent(this, NoteEditActivity.class); - intent.setAction(Intent.ACTION_INSERT_OR_EDIT); - intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId); - this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); // 标记为新建/编辑模式 + intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId); // 传递当前文件夹ID + this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); // 启动Activity,接收返回结果 } + /** + * 批量删除便签:区分同步/非同步模式,执行不同的删除逻辑 + * 同步模式:移到回收站;非同步模式:直接删除;删除后更新关联的桌面小组件 + * 设计意图:异步执行删除操作,避免主线程阻塞;适配同步模式,防止数据丢失 + */ private void batchDelete() { new AsyncTask>() { + /** + * 后台执行:处理批量删除逻辑,获取关联的小组件信息 + * @param unused 无参数 + * @return 关联的小组件属性集合(用于后续更新小组件) + */ protected HashSet doInBackground(Void... unused) { - HashSet widgets = mNotesListAdapter.getSelectedWidget(); + HashSet widgets = mNotesListAdapter.getSelectedWidget(); // 获取选中项关联的小组件 + // 非同步模式:直接删除便签 if (!isSyncMode()) { - // if not synced, delete notes directly if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter .getSelectedItemIds())) { } else { Log.e(TAG, "Delete notes error, should not happens"); } - } else { - // in sync mode, we'll move the deleted note into the trash - // folder + } + // 同步模式:将便签移到回收站(而非直接删除) + else { if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter .getSelectedItemIds(), Notes.ID_TRASH_FOLER)) { Log.e(TAG, "Move notes to trash folder error, should not happens"); @@ -491,8 +631,13 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt return widgets; } + /** + * 主线程回调:更新关联的桌面小组件,结束多选模式 + * @param widgets 关联的小组件属性集合 + */ @Override protected void onPostExecute(HashSet widgets) { + // 遍历小组件,更新内容 if (widgets != null) { for (AppWidgetAttribute widget : widgets) { if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID @@ -501,28 +646,40 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } } + // 结束多选模式 mModeCallBack.finishActionMode(); } }.execute(); } + /** + * 删除文件夹:校验文件夹ID有效性,区分同步/非同步模式执行删除逻辑,更新关联的桌面小组件 + * @param folderId 要删除的文件夹ID + */ private void deleteFolder(long folderId) { + // 校验:根文件夹不可删除(异常场景,理论上不会触发) if (folderId == Notes.ID_ROOT_FOLDER) { Log.e(TAG, "Wrong folder id, should not happen " + folderId); return; } + // 构建要删除的文件夹ID集合 HashSet ids = new HashSet(); ids.add(folderId); + // 获取该文件夹下便签关联的桌面小组件(用于后续更新) HashSet widgets = DataUtils.getFolderNoteWidget(mContentResolver, folderId); + + // 非同步模式:直接删除文件夹 if (!isSyncMode()) { - // if not synced, delete folder directly DataUtils.batchDeleteNotes(mContentResolver, ids); - } else { - // in sync mode, we'll move the deleted folder into the trash folder + } + // 同步模式:将文件夹移到回收站(而非直接删除,适配同步逻辑) + else { DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER); } + + // 更新关联的桌面小组件(文件夹删除后,同步更新小组件内容) if (widgets != null) { for (AppWidgetAttribute widget : widgets) { if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID @@ -533,57 +690,88 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 打开便签:启动NoteEditActivity,标记为查看模式,传递便签ID + * @param data 要打开的便签数据项 + */ private void openNode(NoteItemData data) { Intent intent = new Intent(this, NoteEditActivity.class); - intent.setAction(Intent.ACTION_VIEW); - intent.putExtra(Intent.EXTRA_UID, data.getId()); - this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); + intent.setAction(Intent.ACTION_VIEW); // 标记为查看模式(区别于新建/编辑) + intent.putExtra(Intent.EXTRA_UID, data.getId()); // 传递便签ID + this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); // 启动Activity,接收返回结果 } + /** + * 打开文件夹:更新当前文件夹ID,重新查询便签列表,切换列表状态,更新标题栏 + * @param data 要打开的文件夹数据项 + */ private void openFolder(NoteItemData data) { - mCurrentFolderId = data.getId(); - startAsyncNotesListQuery(); + mCurrentFolderId = data.getId(); // 更新当前文件夹ID + startAsyncNotesListQuery(); // 重新查询该文件夹下的便签列表 + + // 切换列表状态:通话记录文件夹为专属状态,其他为子文件夹状态 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { mState = ListEditState.CALL_RECORD_FOLDER; - mAddNewNote.setVisibility(View.GONE); + mAddNewNote.setVisibility(View.GONE); // 通话记录文件夹下隐藏新建便签按钮 } else { mState = ListEditState.SUB_FOLDER; } + + // 更新标题栏文本:通话记录文件夹显示固定名称,其他显示文件夹名称 if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { mTitleBar.setText(R.string.call_record_folder_name); } else { mTitleBar.setText(data.getSnippet()); } - mTitleBar.setVisibility(View.VISIBLE); + mTitleBar.setVisibility(View.VISIBLE); // 显示标题栏(根文件夹下隐藏) } + /** + * 点击事件处理:仅处理新建便签按钮的点击 + * @param v 被点击的视图 + */ public void onClick(View v) { switch (v.getId()) { case R.id.btn_new_note: - createNewNote(); + createNewNote(); // 点击新建按钮→创建新便签 break; default: break; } } + /** + * 显示软键盘:强制弹出软键盘(用于文件夹名称编辑) + */ private void showSoftInput() { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (inputMethodManager != null) { + // SHOW_FORCED:强制显示软键盘,0:无额外标志 inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); } } + /** + * 隐藏软键盘:根据指定视图的WindowToken隐藏软键盘 + * @param view 用于获取WindowToken的视图(通常是输入框) + */ private void hideSoftInput(View view) { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); } + /** + * 显示创建/修改文件夹对话框:处理文件夹名称输入、校验唯一性、执行创建/修改逻辑 + * @param create true-创建新文件夹,false-修改已有文件夹名称 + */ private void showCreateOrModifyFolderDialog(final boolean create) { final AlertDialog.Builder builder = new AlertDialog.Builder(this); + // 加载对话框布局(包含输入框) View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null); final EditText etName = (EditText) view.findViewById(R.id.et_foler_name); - showSoftInput(); + showSoftInput(); // 弹出软键盘,便于输入 + + // 修改文件夹:初始化输入框为当前文件夹名称,设置标题为“重命名文件夹” if (!create) { if (mFocusNoteDataItem != null) { etName.setText(mFocusNoteDataItem.getSnippet()); @@ -592,62 +780,72 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt Log.e(TAG, "The long click data item is null"); return; } - } else { + } + // 创建文件夹:清空输入框,设置标题为“创建文件夹” + else { etName.setText(""); builder.setTitle(this.getString(R.string.menu_create_folder)); } + // 设置对话框按钮:确定(先不绑定点击,后续自定义)、取消(隐藏软键盘) builder.setPositiveButton(android.R.string.ok, null); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - hideSoftInput(etName); + hideSoftInput(etName); // 取消时隐藏软键盘 } }); + // 显示对话框,自定义确定按钮的点击逻辑(避免默认点击关闭对话框) final Dialog dialog = builder.setView(view).show(); final Button positive = (Button)dialog.findViewById(android.R.id.button1); positive.setOnClickListener(new OnClickListener() { public void onClick(View v) { - hideSoftInput(etName); + hideSoftInput(etName); // 隐藏软键盘 String name = etName.getText().toString(); + + // 校验文件夹名称唯一性:已存在则提示,聚焦输入框 if (DataUtils.checkVisibleFolderName(mContentResolver, name)) { Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name), Toast.LENGTH_LONG).show(); - etName.setSelection(0, etName.length()); + etName.setSelection(0, etName.length()); // 选中输入框文本,便于修改 return; } + + // 修改文件夹名称:非空则更新数据库 if (!create) { if (!TextUtils.isEmpty(name)) { ContentValues values = new ContentValues(); - values.put(NoteColumns.SNIPPET, name); - values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); - values.put(NoteColumns.LOCAL_MODIFIED, 1); + values.put(NoteColumns.SNIPPET, name); // 文件夹名称 + values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); // 类型为文件夹 + values.put(NoteColumns.LOCAL_MODIFIED, 1); // 标记本地已修改(适配同步) + // 更新指定ID的文件夹 mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID + "=?", new String[] { String.valueOf(mFocusNoteDataItem.getId()) }); } - } else if (!TextUtils.isEmpty(name)) { + } + // 创建新文件夹:非空则插入数据库 + else if (!TextUtils.isEmpty(name)) { ContentValues values = new ContentValues(); - values.put(NoteColumns.SNIPPET, name); - values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); - mContentResolver.insert(Notes.CONTENT_NOTE_URI, values); + values.put(NoteColumns.SNIPPET, name); // 文件夹名称 + values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); // 类型为文件夹 + mContentResolver.insert(Notes.CONTENT_NOTE_URI, values); // 插入新文件夹 } - dialog.dismiss(); + dialog.dismiss(); // 关闭对话框 } }); + // 初始化确定按钮状态:输入框为空时禁用 if (TextUtils.isEmpty(etName.getText())) { positive.setEnabled(false); } /** - * When the name edit text is null, disable the positive button + * 输入框文本变化监听:为空时禁用确定按钮,非空时启用 + * 设计意图:避免创建/修改空名称的文件夹 */ etName.addTextChangedListener(new TextWatcher() { - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - // TODO Auto-generated method stub - - } + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} public void onTextChanged(CharSequence s, int start, int before, int count) { if (TextUtils.isEmpty(etName.getText())) { @@ -657,23 +855,26 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } - public void afterTextChanged(Editable s) { - // TODO Auto-generated method stub - - } + public void afterTextChanged(Editable s) {} }); } + /** + * 返回键事件处理:根据当前列表状态,执行不同的返回逻辑 + * 设计意图:子文件夹/通话记录文件夹返回根文件夹,普通列表执行默认返回 + */ @Override public void onBackPressed() { switch (mState) { case SUB_FOLDER: + // 子文件夹状态:返回根文件夹,重置状态,重新查询列表,隐藏标题栏 mCurrentFolderId = Notes.ID_ROOT_FOLDER; mState = ListEditState.NOTE_LIST; startAsyncNotesListQuery(); mTitleBar.setVisibility(View.GONE); break; case CALL_RECORD_FOLDER: + // 通话记录文件夹状态:返回根文件夹,重置状态,显示新建按钮,隐藏标题栏,重新查询列表 mCurrentFolderId = Notes.ID_ROOT_FOLDER; mState = ListEditState.NOTE_LIST; mAddNewNote.setVisibility(View.VISIBLE); @@ -681,6 +882,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt startAsyncNotesListQuery(); break; case NOTE_LIST: + // 普通便签列表状态:执行系统默认返回逻辑 super.onBackPressed(); break; default: @@ -688,8 +890,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + /** + * 更新桌面小组件:发送广播通知小组件刷新内容 + * @param appWidgetId 小组件ID + * @param appWidgetType 小组件类型(2x/4x) + */ private void updateWidget(int appWidgetId, int appWidgetType) { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + // 根据小组件类型,指定对应的广播接收者 if (appWidgetType == Notes.TYPE_WIDGET_2X) { intent.setClass(this, NoteWidgetProvider_2x.class); } else if (appWidgetType == Notes.TYPE_WIDGET_4X) { @@ -699,25 +907,33 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt return; } + // 传递要更新的小组件ID intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { appWidgetId }); - sendBroadcast(intent); - setResult(RESULT_OK, intent); + sendBroadcast(intent); // 发送广播,通知小组件更新 + setResult(RESULT_OK, intent); // 设置返回结果,告知上层页面更新成功 } + /** + * 文件夹上下文菜单创建监听器:长按文件夹时,创建包含“查看/删除/重命名”的上下文菜单 + */ private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() { public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { if (mFocusNoteDataItem != null) { - menu.setHeaderTitle(mFocusNoteDataItem.getSnippet()); - menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view); - menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete); - menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name); + menu.setHeaderTitle(mFocusNoteDataItem.getSnippet()); // 菜单标题为文件夹名称 + menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view); // 查看文件夹 + menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete); // 删除文件夹 + menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name); // 重命名文件夹 } } }; + /** + * 上下文菜单关闭回调:清空列表的上下文菜单创建监听器,避免内存泄漏 + * @param menu 关闭的菜单 + */ @Override public void onContextMenuClosed(Menu menu) { if (mNotesListView != null) { @@ -726,17 +942,24 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt super.onContextMenuClosed(menu); } + /** + * 上下文菜单项选择处理:处理文件夹的“查看/删除/重命名”操作 + * @param item 被选中的菜单项 + * @return true-事件已处理 + */ @Override public boolean onContextItemSelected(MenuItem item) { + // 异常校验:无聚焦的文件夹数据项,直接返回 if (mFocusNoteDataItem == null) { Log.e(TAG, "The long click data item is null"); return false; } switch (item.getItemId()) { case MENU_FOLDER_VIEW: - openFolder(mFocusNoteDataItem); + openFolder(mFocusNoteDataItem); // 查看文件夹→打开该文件夹 break; case MENU_FOLDER_DELETE: + // 删除文件夹:弹窗确认,确认后执行删除逻辑 AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.alert_title_delete)); builder.setIcon(android.R.drawable.ic_dialog_alert); @@ -751,66 +974,74 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt builder.show(); break; case MENU_FOLDER_CHANGE_NAME: - showCreateOrModifyFolderDialog(false); + showCreateOrModifyFolderDialog(false); // 重命名文件夹→显示修改对话框 break; default: break; } - return true; } + /** + * 准备选项菜单:根据当前列表状态加载不同的菜单资源,更新同步按钮文本 + * @param menu 要初始化的菜单 + * @return true-菜单初始化成功 + */ @Override public boolean onPrepareOptionsMenu(Menu menu) { - menu.clear(); + menu.clear(); // 清空原有菜单,避免重复加载 + // 根据列表状态加载对应菜单 if (mState == ListEditState.NOTE_LIST) { - getMenuInflater().inflate(R.menu.note_list, menu); - // set sync or sync_cancel + getMenuInflater().inflate(R.menu.note_list, menu); // 普通列表→加载便签列表菜单 + // 更新同步按钮文本:同步中→显示“取消同步”,未同步→显示“同步” menu.findItem(R.id.menu_sync).setTitle( GTaskSyncService.isSyncing() ? R.string.menu_sync_cancel : R.string.menu_sync); } else if (mState == ListEditState.SUB_FOLDER) { - getMenuInflater().inflate(R.menu.sub_folder, menu); + getMenuInflater().inflate(R.menu.sub_folder, menu); // 子文件夹→加载子文件夹菜单 } else if (mState == ListEditState.CALL_RECORD_FOLDER) { - getMenuInflater().inflate(R.menu.call_record_folder, menu); + getMenuInflater().inflate(R.menu.call_record_folder, menu); // 通话记录文件夹→加载专属菜单 } else { Log.e(TAG, "Wrong state:" + mState); } return true; } + /** + * 选项菜单点击处理:处理“新建文件夹/导出文本/同步/设置/新建便签/搜索”操作 + * @param item 被选中的菜单项 + * @return true-事件已处理 + */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case R.id.menu_new_folder: { - showCreateOrModifyFolderDialog(true); + case R.id.menu_new_folder: + showCreateOrModifyFolderDialog(true); // 新建文件夹→显示创建对话框 break; - } - case R.id.menu_export_text: { - exportNoteToText(); + case R.id.menu_export_text: + exportNoteToText(); // 导出文本→执行便签导出逻辑 break; - } - case R.id.menu_sync: { + case R.id.menu_sync: + // 同步操作:区分同步模式/非同步模式 if (isSyncMode()) { + // 同步模式:点击“同步”→启动同步,点击“取消同步”→取消同步 if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) { GTaskSyncService.startSync(this); } else { GTaskSyncService.cancelSync(this); } } else { + // 非同步模式:跳转到设置页面(配置同步账号) startPreferenceActivity(); } break; - } - case R.id.menu_setting: { - startPreferenceActivity(); + case R.id.menu_setting: + startPreferenceActivity(); // 设置→跳转到偏好设置页面 break; - } - case R.id.menu_new_note: { - createNewNote(); + case R.id.menu_new_note: + createNewNote(); // 新建便签→执行新建逻辑 break; - } case R.id.menu_search: - onSearchRequested(); + onSearchRequested(); // 搜索→触发系统搜索 break; default: break; @@ -818,23 +1049,40 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt return true; } + /** + * 触发搜索:调用系统搜索接口,启动搜索页面 + * @return true-搜索请求已触发 + */ @Override public boolean onSearchRequested() { + // 启动搜索:无初始查询词、不包含应用数据、不强制显示搜索界面 startSearch(null, false, null /* appData */, false); return true; } + /** + * 导出便签到文本文件:异步执行导出操作,根据导出结果展示不同的提示弹窗 + * 设计意图:异步执行避免主线程阻塞,适配SD卡状态、系统异常等场景 + */ private void exportNoteToText() { final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this); new AsyncTask() { + /** + * 后台执行:调用BackupUtils执行导出逻辑,返回导出状态码 + */ @Override protected Integer doInBackground(Void... unused) { return backup.exportToText(); } + /** + * 主线程回调:根据导出状态码展示对应的提示弹窗 + * @param result 导出状态码(SD卡未挂载/成功/系统异常) + */ @Override protected void onPostExecute(Integer result) { + // SD卡未挂载:提示SD卡未挂载 if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) { AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); builder.setTitle(NotesListActivity.this @@ -843,7 +1091,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt .getString(R.string.error_sdcard_unmounted)); builder.setPositiveButton(android.R.string.ok, null); builder.show(); - } else if (result == BackupUtils.STATE_SUCCESS) { + } + // 导出成功:提示导出成功,显示文件路径和名称 + else if (result == BackupUtils.STATE_SUCCESS) { AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); builder.setTitle(NotesListActivity.this .getString(R.string.success_sdcard_export)); @@ -852,7 +1102,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt .getExportedTextFileName(), backup.getExportedTextFileDir())); builder.setPositiveButton(android.R.string.ok, null); builder.show(); - } else if (result == BackupUtils.STATE_SYSTEM_ERROR) { + } + // 系统异常:提示导出失败 + else if (result == BackupUtils.STATE_SYSTEM_ERROR) { AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); builder.setTitle(NotesListActivity.this .getString(R.string.failed_sdcard_export)); @@ -862,36 +1114,53 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt builder.show(); } } - }.execute(); } + /** + * 判断是否为同步模式:检测是否配置了同步账号(有账号则为同步模式) + * @return true-同步模式,false-非同步模式 + */ private boolean isSyncMode() { return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; } + /** + * 启动偏好设置页面:适配嵌套Activity场景,使用父Activity或当前Activity启动 + */ private void startPreferenceActivity() { - Activity from = getParent() != null ? getParent() : this; + Activity from = getParent() != null ? getParent() : this; // 适配嵌套Activity Intent intent = new Intent(from, NotesPreferenceActivity.class); - from.startActivityIfNeeded(intent, -1); + from.startActivityIfNeeded(intent, -1); // 启动设置页面,-1表示无返回结果 } + /** + * 列表项点击监听器:区分多选/普通模式,处理便签/文件夹的点击逻辑 + * 核心逻辑:多选模式下切换选中状态,普通模式下打开便签/文件夹 + */ private class OnListItemClickListener implements OnItemClickListener { public void onItemClick(AdapterView parent, View view, int position, long id) { + // 仅处理NotesListItem类型的列表项 if (view instanceof NotesListItem) { NoteItemData item = ((NotesListItem) view).getItemData(); + + // 多选模式下:切换便签的选中状态(文件夹不可选) if (mNotesListAdapter.isInChoiceMode()) { if (item.getType() == Notes.TYPE_NOTE) { + // 校准位置(排除header) position = position - mNotesListView.getHeaderViewsCount(); + // 切换选中状态 mModeCallBack.onItemCheckedStateChanged(null, position, id, !mNotesListAdapter.isSelectedItem(position)); } return; } + // 普通模式下:根据当前列表状态,处理不同类型的列表项 switch (mState) { case NOTE_LIST: + // 普通列表:文件夹/系统文件夹→打开文件夹,便签→打开便签 if (item.getType() == Notes.TYPE_FOLDER || item.getType() == Notes.TYPE_SYSTEM) { openFolder(item); @@ -903,6 +1172,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt break; case SUB_FOLDER: case CALL_RECORD_FOLDER: + // 子文件夹/通话记录文件夹:仅处理便签(无文件夹) if (item.getType() == Notes.TYPE_NOTE) { openNode(item); } else { @@ -914,14 +1184,23 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } } - } + /** + * 启动目标文件夹查询:查询可移动便签的目标文件夹,构建差异化的查询条件 + * 设计意图:排除回收站、当前文件夹,子文件夹状态下包含根文件夹 + */ private void startQueryDestinationFolders() { + // 基础查询条件:类型为文件夹 + 父文件夹不是回收站 + ID不是当前文件夹 String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?"; + // 子文件夹状态下:额外包含根文件夹(允许移到根文件夹) selection = (mState == ListEditState.NOTE_LIST) ? selection: "(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")"; + // 启动异步查询: + // Token:FOLDER_LIST_QUERY_TOKEN(标识为文件夹列表查询) + // SelectionArgs:文件夹类型、回收站ID、当前文件夹ID + // SortOrder:按修改时间降序(最新修改的文件夹在前) mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN, null, Notes.CONTENT_NOTE_URI, @@ -935,20 +1214,32 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt NoteColumns.MODIFIED_DATE + " DESC"); } + /** + * 列表项长按监听:触发便签多选模式或文件夹上下文菜单 + * @param parent 列表控件 + * @param view 被长按的列表项 + * @param position 列表项位置 + * @param id 列表项ID + * @return false-不消费事件(允许后续处理) + */ public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { if (view instanceof NotesListItem) { mFocusNoteDataItem = ((NotesListItem) view).getItemData(); + // 便签且非多选模式:启动多选模式,选中当前项,触发长按震动反馈 if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) { if (mNotesListView.startActionMode(mModeCallBack) != null) { mModeCallBack.onItemCheckedStateChanged(null, position, id, true); + // 长按震动反馈(提升交互体验) mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } else { Log.e(TAG, "startActionMode fails"); } - } else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) { + } + // 文件夹:设置上下文菜单创建监听器(长按显示菜单) + else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) { mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener); } } return false; } -} +} \ No newline at end of file