/* * 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.text.Editable; import android.text.TextWatcher; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.Button; import android.app.Dialog; import android.graphics.Color; import android.graphics.Typeface; import android.text.Editable; import android.text.Html; import android.text.SpannableStringBuilder; import android.text.TextWatcher; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; import android.text.style.UnderlineSpan; import android.view.Gravity; import android.view.LayoutInflater; import android.view.Window; import android.view.WindowManager; import android.graphics.drawable.ColorDrawable; import android.widget.RadioGroup; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.graphics.Rect; import android.view.ActionMode; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.widget.TextView; import android.widget.Toast; import net.micode.notes.R; import android.database.Cursor; import android.content.ContentValues; 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 { /** * 标题栏视图持有者 * 用于管理笔记标题区域的UI组件 */ private class HeadViewHolder { public TextView tvModified; // 显示最后修改时间 public ImageView ivAlertIcon; // 提醒图标 public TextView tvAlertDate; // 提醒时间 public ImageView ibSetBgColor; // 设置背景颜色按钮 } // 背景颜色选择器按钮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的映射 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与字体大小值的映射 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的映射 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 = String.valueOf('\u221A'); // 勾选标记 √ public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); // 未勾选标记 □ private LinearLayout mEditTextList; // 清单模式下的编辑列表容器 private String mUserQuery; // 搜索查询词(用于高亮显示) private Pattern mPattern; // 搜索高亮正则表达式模式 private TextView mWordCountView; // 字数统计显示控件 private Button mStudyTimerButton; // 学习计时器按钮 // 富文本工具栏相关变量 private View mRichTextToolbar; // 富文本工具栏 private Button mBtnBold; // 加粗按钮 private Button mBtnItalic; // 斜体按钮 private Button mBtnUnderline; // 下划线按钮 private Map mColorMap; // 颜色按钮ID与颜色值的映射 // 学习计时器相关变量 private boolean mIsTimerRunning = false; // 计时器是否正在运行 private boolean mIsCountdownMode = false; // 是否为逆向计时模式 private long mTimerCurrentTime = 0; // 当前计时时间(毫秒) private long mTimerDuration = 0; // 逆向计时时长(毫秒) private android.os.Handler mTimerHandler = new android.os.Handler(); private android.os.Handler mDisplayHandler = new android.os.Handler(); private Runnable mTimerRunnable = new Runnable() { @Override public void run() { if (mIsTimerRunning) { if (mIsCountdownMode) { // 逆向计时 long elapsed = System.currentTimeMillis() - mTimerStartTime; mTimerCurrentTime = mTimerDuration - elapsed; if (mTimerCurrentTime <= 0) { // 计时结束 mTimerCurrentTime = mTimerDuration; // 设置为完整时长,确保计入专注时间 mIsTimerRunning = false; updateTimerDisplay(); Toast.makeText(NoteEditActivity.this, "学习时间结束!", Toast.LENGTH_LONG).show(); return; } } else { // 正向计时 mTimerCurrentTime = System.currentTimeMillis() - mTimerStartTime; } updateTimerDisplay(); mTimerHandler.postDelayed(this, 1000); // 每秒更新一次 } } }; private long mTimerStartTime = 0; // 计时器开始时间 // 富文本颜色选择器按钮ID与颜色值的映射 private static final Map sColorMap = new HashMap(); static { sColorMap.put(R.id.color_black, Color.BLACK); sColorMap.put(R.id.color_red, Color.RED); sColorMap.put(R.id.color_blue, Color.BLUE); sColorMap.put(R.id.color_green, Color.GREEN); sColorMap.put(R.id.color_orange, Color.rgb(255, 165, 0)); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.note_edit); // 设置布局 // 初始化活动状态,如果失败则结束活动 if (savedInstanceState == null && !initActivityState(getIntent())) { finish(); return; } // 接收并处理传递的计时器状态 Intent intent = getIntent(); if (intent != null) { mIsTimerRunning = intent.getBooleanExtra("is_timer_running", false); mTimerCurrentTime = intent.getLongExtra("timer_current_time", 0); mIsCountdownMode = intent.getBooleanExtra("is_countdown_mode", false); mTimerDuration = intent.getLongExtra("timer_duration", 0); // 如果计时器正在运行,启动计时器 if (mIsTimerRunning) { if (mIsCountdownMode) { // 逆向计时模式:计算已经过去的时间 long elapsedTime = mTimerDuration - mTimerCurrentTime; mTimerStartTime = System.currentTimeMillis() - elapsedTime; } else { // 正向计时模式:使用当前时间减去已计时时间 mTimerStartTime = System.currentTimeMillis() - mTimerCurrentTime; } mTimerHandler.post(mTimerRunnable); } } initResources(); // 初始化资源 } /** * 当活动被系统杀死后恢复时调用 * 用于恢复之前的状态 */ @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); // 恢复笔记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)); if (!initActivityState(intent)) { finish(); return; } Log.d(TAG, "Restoring from killed activity"); } } /** * 初始化活动状态 * @param intent 启动活动的意图 * @return 初始化是否成功 */ private boolean initActivityState(Intent intent) { mWorkingNote = null; // 处理查看笔记的意图 if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); mUserQuery = ""; // 处理从搜索结果打开的情况 if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); } // 检查笔记是否存在 // 对于回收站中的便签,使用existInNoteDatabase而不是visibleInNoteDatabase if (!DataUtils.existInNoteDatabase(getContentResolver(), noteId)) { Intent jump = new Intent(this, NotesListActivity.class); startActivity(jump); showToast(R.string.error_note_not_exist); finish(); return false; } else { // 加载笔记 mWorkingNote = WorkingNote.load(this, noteId); 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())) { // 新笔记参数 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) { if (TextUtils.isEmpty(phoneNumber)) { 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); if (mWorkingNote == null) { Log.e(TAG, "load call note failed with note id" + noteId); finish(); 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 { Log.e(TAG, "Intent not specified action, should not support"); 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 { // 普通模式,显示内容并高亮搜索词 // 将HTML格式转换为SpannableString以保留富文本格式 String content = mWorkingNote.getContent(); CharSequence styledText = Html.fromHtml(content != null ? content : ""); mNoteEditor.setText(getHighlightQueryResult(styledText, mUserQuery)); mNoteEditor.setSelection(mNoteEditor.getText().length()); // 更新字数统计 updateWordCount(); } // 隐藏所有背景选择指示器 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)); // 显示提醒信息 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); }; } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); initActivityState(intent); // 重新初始化活动状态 } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); /** * 对于没有ID的新笔记,需要先保存以生成ID * 如果编辑的笔记不值得保存,则没有ID,相当于创建新笔记 */ if (!mWorkingNote.existInDatabase()) { saveNote(); } 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); 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; } /** * 初始化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.ib_set_bg_color); mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); // 初始化学习计时器按钮 mStudyTimerButton = (Button) findViewById(R.id.btn_study_timer); if (mStudyTimerButton != null) { mStudyTimerButton.setOnClickListener(this); // 检查是否为学习文件夹中的笔记 long folderId = mWorkingNote.getFolderId(); if (folderId > 0) { // 检查文件夹是否为学习文件夹 if (isStudyFolder(folderId)) { mStudyTimerButton.setVisibility(View.VISIBLE); } else { mStudyTimerButton.setVisibility(View.GONE); } } else { mStudyTimerButton.setVisibility(View.GONE); } } 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的bug。 * 如果ID大于资源长度,则返回默认字体大小 */ if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) { mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; } mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); // 初始化字数统计控件 mWordCountView = (TextView) findViewById(R.id.tv_word_count); // 设置文本变化监听器 mNoteEditor.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { // 文本变化前的处理,暂不需要 } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { // 文本变化时更新字数统计 updateWordCount(); } @Override public void afterTextChanged(Editable s) { // 文本变化后的处理,暂不需要 } }); // 初始化富文本工具栏 initRichTextToolbar(); // 监听触摸事件,点击空白区域时隐藏工具栏 findViewById(R.id.sv_note_edit).setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // 点击编辑器面板空白区域时隐藏工具栏 if (v.getId() == R.id.sv_note_edit) { int selStart = mNoteEditor.getSelectionStart(); int selEnd = mNoteEditor.getSelectionEnd(); // 只有当没有文本被选择时才隐藏工具栏 if (selStart == selEnd) { hideRichTextToolbar(); } } return false; } }); // 添加全局触摸监听器,检测点击空白区域 findViewById(android.R.id.content).setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { // 检查点击是否在编辑器外部 Rect editorRect = new Rect(); mNoteEditor.getGlobalVisibleRect(editorRect); if (!editorRect.contains((int) event.getRawX(), (int) event.getRawY())) { // 点击在编辑器外部,隐藏工具栏 hideRichTextToolbar(); } } return false; } }); // 自定义文本选择动作模式,只显示我们的富文本工具栏 mNoteEditor.setCustomSelectionActionModeCallback(new ActionMode.Callback() { @Override public boolean onCreateActionMode(ActionMode mode, Menu menu) { // 清除所有系统菜单项 menu.clear(); // 显示我们的富文本工具栏 showRichTextToolbar(); // 返回true表示创建动作模式,但不显示系统菜单 return true; } @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { // 清除所有系统菜单项 menu.clear(); // 确保富文本工具栏显示 showRichTextToolbar(); return false; } @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { return false; } @Override public void onDestroyActionMode(ActionMode mode) { // 当选择取消时,隐藏工具栏 hideRichTextToolbar(); } }); } /** * 初始化富文本工具栏 */ private void initRichTextToolbar() { mRichTextToolbar = findViewById(R.id.rich_text_toolbar); mBtnBold = (Button) findViewById(R.id.btn_bold); mBtnItalic = (Button) findViewById(R.id.btn_italic); mBtnUnderline = (Button) findViewById(R.id.btn_underline); // 设置加粗按钮点击事件 mBtnBold.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { toggleBold(); } }); // 设置斜体按钮点击事件 mBtnItalic.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { toggleItalic(); } }); // 设置下划线按钮点击事件 mBtnUnderline.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { toggleUnderline(); } }); // 设置颜色选择器按钮点击事件 for (int id : sColorMap.keySet()) { ImageButton btn = (ImageButton) findViewById(id); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { int color = sColorMap.get(v.getId()); setTextColor(color); } }); } } /** * 显示富文本工具栏 */ private void showRichTextToolbar() { mRichTextToolbar.setVisibility(View.VISIBLE); } /** * 隐藏富文本工具栏 */ private void hideRichTextToolbar() { mRichTextToolbar.setVisibility(View.GONE); } /** * 切换文本加粗状态 */ private void toggleBold() { // 保存当前选择范围 int start = mNoteEditor.getSelectionStart(); int end = mNoteEditor.getSelectionEnd(); if (start == end) { return; } // 直接获取当前可编辑文本,避免使用setText()导致ActionMode丢失 Editable editable = mNoteEditor.getText(); boolean hasBold = false; // 检查选中的文本是否已经加粗 StyleSpan[] spans = editable.getSpans(start, end, StyleSpan.class); for (StyleSpan span : spans) { if (span.getStyle() == Typeface.BOLD) { editable.removeSpan(span); hasBold = true; } } if (!hasBold) { // 如果没有加粗,则添加加粗样式 editable.setSpan(new StyleSpan(Typeface.BOLD), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); } // 保存笔记,但不破坏当前选择状态 saveNote(); } /** * 切换文本斜体状态 */ private void toggleItalic() { // 保存当前选择范围 int start = mNoteEditor.getSelectionStart(); int end = mNoteEditor.getSelectionEnd(); if (start == end) { return; } // 直接获取当前可编辑文本,避免使用setText()导致ActionMode丢失 Editable editable = mNoteEditor.getText(); boolean hasItalic = false; // 检查选中的文本是否已经斜体 StyleSpan[] spans = editable.getSpans(start, end, StyleSpan.class); for (StyleSpan span : spans) { if (span.getStyle() == Typeface.ITALIC) { editable.removeSpan(span); hasItalic = true; } } if (!hasItalic) { // 如果没有斜体,则添加斜体样式 editable.setSpan(new StyleSpan(Typeface.ITALIC), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); } // 保存笔记,但不破坏当前选择状态 saveNote(); } /** * 切换文本下划线状态 */ private void toggleUnderline() { // 保存当前选择范围 int start = mNoteEditor.getSelectionStart(); int end = mNoteEditor.getSelectionEnd(); if (start == end) { return; } // 直接获取当前可编辑文本,避免使用setText()导致ActionMode丢失 Editable editable = mNoteEditor.getText(); boolean hasUnderline = false; // 检查选中的文本是否已经有下划线 UnderlineSpan[] spans = editable.getSpans(start, end, UnderlineSpan.class); if (spans.length > 0) { // 如果有下划线,则移除下划线 for (UnderlineSpan span : spans) { editable.removeSpan(span); } hasUnderline = true; } if (!hasUnderline) { // 如果没有下划线,则添加下划线 editable.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); } // 保存笔记,但不破坏当前选择状态 saveNote(); } /** * 设置文本颜色 */ private void setTextColor(int color) { // 保存当前选择范围 int start = mNoteEditor.getSelectionStart(); int end = mNoteEditor.getSelectionEnd(); if (start == end) { return; } // 直接获取当前可编辑文本,避免使用setText()导致ActionMode丢失 Editable editable = mNoteEditor.getText(); // 移除原有的颜色样式 ForegroundColorSpan[] oldSpans = editable.getSpans(start, end, ForegroundColorSpan.class); for (ForegroundColorSpan span : oldSpans) { editable.removeSpan(span); } // 添加新的颜色样式 editable.setSpan(new ForegroundColorSpan(color), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); // 保存笔记,但不破坏当前选择状态 saveNote(); } @Override protected void onPause() { super.onPause(); // 暂停时保存笔记 if(saveNote()) { Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); } clearSettingState(); // 清除设置状态 // 暂停计时器显示更新 mDisplayHandler.removeCallbacksAndMessages(null); } /** * 更新字数统计显示 */ private void updateWordCount() { if (mWordCountView != null) { String content = getCurrentNoteContent(); int wordCount = DataUtils.getTotalWordCount(content); mWordCountView.setText(getString(R.string.note_word_count, wordCount)); } } /** * 获取当前便签的内容,根据不同模式选择不同的获取方式 * @return 当前便签的内容 */ private String getCurrentNoteContent() { StringBuilder content = new StringBuilder(); // 检查当前模式 if (mNoteEditor.getVisibility() == View.VISIBLE) { // 普通模式,直接获取编辑框内容 content.append(mNoteEditor.getText().toString()); } else if (mEditTextList.getVisibility() == View.VISIBLE) { // 清单模式,收集所有列表项的内容 for (int i = 0; i < mEditTextList.getChildCount(); i++) { View itemView = mEditTextList.getChildAt(i); EditText editText = (EditText) itemView.findViewById(R.id.et_edit_text); if (editText != null && !TextUtils.isEmpty(editText.getText())) { // 添加当前列表项的内容 content.append(editText.getText().toString()); // 添加换行符,保持原有格式 if (i < mEditTextList.getChildCount() - 1) { content.append("\n"); } } } } return content.toString(); } /** * 更新桌面小部件 */ 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() }); sendBroadcast(intent); setResult(RESULT_OK, intent); } // 计时器对话框变量 private Dialog mTimerDialog = null; /** * 点击事件处理 */ public void onClick(View v) { int id = v.getId(); // 学习计时器按钮 if (id == R.id.btn_study_timer) { if (mIsTimerRunning) { // 如果计时器正在运行,切换计时控制界面显示/隐藏 if (mTimerDialog != null && mTimerDialog.isShowing()) { // 如果对话框已显示,则隐藏 mTimerDialog.dismiss(); } else { // 如果对话框未显示,则显示 showTimerControlDialog(); } } } // 背景颜色设置按钮 else if (id == R.id.ib_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)); mNoteBgColorSelector.setVisibility(View.GONE); } // 字体大小选择 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()); } else { mNoteEditor.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); } mFontSizeSelector.setVisibility(View.GONE); } } @Override public void onBackPressed() { if(clearSettingState()) { return; // 如果有设置面板打开,先关闭它们 } saveNote(); // 保存笔记 // 传递计时器状态回 NotesListActivity Intent intent = new Intent(); intent.putExtra("is_timer_running", mIsTimerRunning); intent.putExtra("timer_current_time", mTimerCurrentTime); intent.putExtra("is_countdown_mode", mIsCountdownMode); intent.putExtra("timer_duration", mTimerDuration); setResult(RESULT_OK, intent); super.onBackPressed(); } /** * 清除设置状态(隐藏选择器) */ private boolean clearSettingState() { if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { mNoteBgColorSelector.setVisibility(View.GONE); return true; } else if (mFontSizeSelector.getVisibility() == View.VISIBLE) { mFontSizeSelector.setVisibility(View.GONE); return true; } return false; } /** * 背景颜色变化回调 */ public void onBackgroundColorChanged() { findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( View.VISIBLE); mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); } @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 { menu.findItem(R.id.menu_delete_remind).setVisible(false); } // 根据便签的置顶状态设置菜单项标题 MenuItem pinItem = menu.findItem(R.id.menu_pin_note); if (pinItem != null) { if (mWorkingNote.isPinned()) { pinItem.setTitle(R.string.menu_unpin_note); } else { pinItem.setTitle(R.string.menu_pin_note); } } return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_new_note: // 新建笔记 createNewNote(); break; case R.id.menu_delete: // 删除笔记 showDeleteConfirmDialog(); 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_alert: // 设置提醒 setReminder(); break; case R.id.menu_delete_remind: // 删除提醒 mWorkingNote.setAlertDate(0, false); break; case R.id.menu_pin_note: // 置顶/取消置顶 mWorkingNote.setPinned(!mWorkingNote.isPinned()); break; default: break; } return true; } /** * 显示删除确认对话框 */ private void showDeleteConfirmDialog() { 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(); } /** * 设置提醒 */ 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(); } /** * 分享笔记到支持ACTION_SEND的应用 */ 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() { // 首先保存当前编辑的笔记 saveNote(); // 启动新的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()); startActivity(intent); } /** * 删除当前笔记 */ private void deleteCurrentNote() { if (mWorkingNote.existInDatabase()) { HashSet ids = new HashSet(); long id = mWorkingNote.getNoteId(); if (id != Notes.ID_ROOT_FOLDER) { ids.add(id); } else { Log.d(TAG, "Wrong note id, should not happen"); } // 所有模式下,都将删除的笔记移动到回收站 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) { /** * 用户可以为未保存的笔记设置闹钟,因此在设置提醒之前,应先保存笔记 */ 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())); PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE)); showAlertHeader(); // 设置或取消闹钟 if(!set) { alarmManager.cancel(pendingIntent); } else { alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent); } } else { /** * 用户没有输入任何内容(笔记不值得保存),我们没有笔记ID, * 提醒用户需要输入一些内容 */ 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) { int childCount = mEditTextList.getChildCount(); if (childCount == 1) { return; // 不能删除最后一个项目 } // 更新后续项目的索引 for (int i = index + 1; i < childCount; i++) { ((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) { /** * 不应该发生,检查调试 */ 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++) { ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) .setIndex(i); } } /** * 切换到清单模式 */ 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(); mNoteEditor.setVisibility(View.GONE); // 隐藏普通编辑框 mEditTextList.setVisibility(View.VISIBLE); // 显示清单列表 } /** * 获取高亮搜索结果的Spannable */ private Spannable getHighlightQueryResult(CharSequence fullText, String userQuery) { SpannableString spannable; if (fullText instanceof SpannableString) { spannable = (SpannableString) fullText; } else { spannable = new SpannableString(fullText == null ? "" : fullText); } if (!TextUtils.isEmpty(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); } // 更新字数统计 updateWordCount(); } /** * 清单模式变化回调 */ 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); } // 更新字数统计 updateWordCount(); } /** * 获取工作文本(从UI提取到数据模型) */ 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 { // 普通模式:保存富文本格式为HTML String htmlText = Html.toHtml(mNoteEditor.getText()); mWorkingNote.setWorkingText(htmlText); } return hasChecked; } /** * 保存笔记 */ private boolean saveNote() { getWorkingText(); // 获取当前工作文本 boolean saved = mWorkingNote.saveNote(); // 保存到数据库 if (saved) { /** * 从列表视图到编辑视图有两种模式:打开笔记和创建/编辑笔记。 * 打开笔记需要返回到列表中的原始位置, * 而创建新笔记需要返回到列表顶部。 * 使用RESULT_OK来标识创建/编辑状态 */ setResult(RESULT_OK); } return saved; } /** * 发送到桌面(创建快捷方式) */ private void sendToDesktop() { /** * 在发送到桌面之前,应确保当前编辑的笔记存在于数据库中。 * 因此,对于新笔记,首先保存它 */ if (!mWorkingNote.existInDatabase()) { saveNote(); } if (mWorkingNote.getNoteId() > 0) { 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"); // 安装快捷方式 showToast(R.string.info_note_enter_desktop); sendBroadcast(sender); } else { /** * 用户没有输入任何内容(笔记不值得保存),我们没有笔记ID, * 提醒用户需要输入一些内容 */ Log.e(TAG, "Send to desktop error"); showToast(R.string.error_note_empty_for_send_to_desktop); } } /** * 生成快捷方式图标标题 */ 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; } /** * 显示Toast提示 */ private void showToast(int resId) { showToast(resId, Toast.LENGTH_SHORT); } private void showToast(int resId, int duration) { Toast.makeText(this, resId, duration).show(); } /** * 更新计时器显示 */ private void updateTimerDisplay() { long totalSeconds = mTimerCurrentTime / 1000; int hours = (int) (totalSeconds / 3600); int minutes = (int) ((totalSeconds % 3600) / 60); int seconds = (int) (totalSeconds % 60); String timeStr = String.format("%02d:%02d:%02d", hours, minutes, seconds); } /** * 启动计时器显示更新 */ private void startTimerDisplayUpdate() { mDisplayHandler.removeCallbacksAndMessages(null); final Runnable updateRunnable = new Runnable() { @Override public void run() { if (mIsTimerRunning) { updateTimerDisplay(); mDisplayHandler.postDelayed(this, 1000); } } }; mDisplayHandler.postDelayed(updateRunnable, 1000); } /** * 显示计时器控制对话框 */ private void showTimerControlDialog() { // 创建一个非模态对话框 final Dialog timerDialog = new Dialog(this, android.R.style.Theme_Dialog); timerDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); timerDialog.setCancelable(true); timerDialog.setCanceledOnTouchOutside(true); // 设置对话框大小和位置 Window window = timerDialog.getWindow(); if (window != null) { WindowManager.LayoutParams params = window.getAttributes(); params.width = WindowManager.LayoutParams.MATCH_PARENT; params.height = WindowManager.LayoutParams.WRAP_CONTENT; params.gravity = Gravity.BOTTOM; params.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; params.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; window.setAttributes(params); window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); } View view = LayoutInflater.from(this).inflate(R.layout.dialog_timer_control, null); final TextView timerDisplayView = (TextView) view.findViewById(R.id.timer_display); // 隐藏所有控制按钮 view.findViewById(R.id.btn_pause_resume).setVisibility(View.GONE); view.findViewById(R.id.btn_stop).setVisibility(View.GONE); // 更新计时器显示 long totalSeconds; if (mIsCountdownMode) { // 逆向计时模式:显示剩余时间 totalSeconds = mTimerCurrentTime / 1000; } else { // 正向计时模式:显示已用时间 totalSeconds = mTimerCurrentTime / 1000; } int hours = (int) (totalSeconds / 3600); int minutes = (int) ((totalSeconds % 3600) / 60); int seconds = (int) (totalSeconds % 60); String timeStr = String.format("%02d:%02d:%02d", hours, minutes, seconds); timerDisplayView.setText(timeStr); // 添加实时更新计时器显示的逻辑 final android.os.Handler displayHandler = new android.os.Handler(); final Runnable updateRunnable = new Runnable() { @Override public void run() { if (mIsTimerRunning && timerDialog.isShowing()) { long totalSeconds; if (mIsCountdownMode) { // 逆向计时模式:显示剩余时间 totalSeconds = mTimerCurrentTime / 1000; } else { // 正向计时模式:显示已用时间 totalSeconds = mTimerCurrentTime / 1000; } int hours = (int) (totalSeconds / 3600); int minutes = (int) ((totalSeconds % 3600) / 60); int seconds = (int) (totalSeconds % 60); String timeStr = String.format("%02d:%02d:%02d", hours, minutes, seconds); timerDisplayView.setText(timeStr); displayHandler.postDelayed(this, 1000); } } }; // 启动实时更新 if (mIsTimerRunning) { displayHandler.postDelayed(updateRunnable, 1000); } // 对话框关闭时停止更新 timerDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { displayHandler.removeCallbacks(updateRunnable); } }); timerDialog.setContentView(view); timerDialog.show(); mTimerDialog = timerDialog; } /** * 暂停学习计时器 */ private void pauseStudyTimer() { mIsTimerRunning = false; mTimerHandler.removeCallbacks(mTimerRunnable); // 暂停计时器显示更新 mDisplayHandler.removeCallbacksAndMessages(null); } /** * 恢复学习计时器 */ private void resumeStudyTimer() { if (!mIsTimerRunning) { if (mIsCountdownMode) { // 倒计时模式:计算已经过去的时间 long elapsedTime = mTimerDuration - mTimerCurrentTime; mTimerStartTime = System.currentTimeMillis() - elapsedTime; } else { // 正向计时模式:使用当前时间减去已计时时间 mTimerStartTime = System.currentTimeMillis() - mTimerCurrentTime; } mIsTimerRunning = true; mTimerHandler.post(mTimerRunnable); // 重启计时器显示更新 startTimerDisplayUpdate(); } } /** * 停止学习计时器 */ private void stopStudyTimer() { mIsTimerRunning = false; mTimerHandler.removeCallbacks(mTimerRunnable); mDisplayHandler.removeCallbacksAndMessages(null); // 验证学习有效性 if (mTimerCurrentTime >= 60000) { // 学习时间超过1分钟,视为有效学习 updateFocusDuration(mTimerCurrentTime); } // 重置计时器变量 mTimerCurrentTime = 0; mTimerDuration = 0; // 关闭计时器对话框 if (mTimerDialog != null && mTimerDialog.isShowing()) { mTimerDialog.dismiss(); mTimerDialog = null; } } /** * 检查文件夹是否为学习文件夹 */ private boolean isStudyFolder(long folderId) { // 查询文件夹的 IS_STUDY 字段 Cursor cursor = getContentResolver().query( Notes.CONTENT_NOTE_URI, new String[]{Notes.NoteColumns.IS_STUDY}, Notes.NoteColumns.ID + "=? AND " + Notes.NoteColumns.TYPE + "=?", new String[]{String.valueOf(folderId), String.valueOf(Notes.TYPE_FOLDER)}, null ); boolean isStudy = false; if (cursor != null && cursor.moveToFirst()) { isStudy = (cursor.getInt(0) > 0); cursor.close(); } return isStudy; } /** * 更新专注时长 */ private void updateFocusDuration(long duration) { // 获取当前笔记所属的文件夹ID long folderId = mWorkingNote.getFolderId(); if (folderId <= 0) { return; } // 查询当前文件夹的专注时长 Cursor cursor = getContentResolver().query( Notes.CONTENT_NOTE_URI, new String[]{Notes.NoteColumns.FOCUS_DURATION}, Notes.NoteColumns.ID + "=? AND " + Notes.NoteColumns.TYPE + "=?", new String[]{String.valueOf(folderId), String.valueOf(Notes.TYPE_FOLDER)}, null ); long currentDuration = 0; if (cursor != null && cursor.moveToFirst()) { currentDuration = cursor.getLong(0); cursor.close(); } // 更新专注时长 long newDuration = currentDuration + duration; ContentValues values = new ContentValues(); values.put(Notes.NoteColumns.FOCUS_DURATION, newDuration); values.put(Notes.NoteColumns.LOCAL_MODIFIED, 1); getContentResolver().update( Notes.CONTENT_NOTE_URI, values, Notes.NoteColumns.ID + "=? AND " + Notes.NoteColumns.TYPE + "=?", new String[]{String.valueOf(folderId), String.valueOf(Notes.TYPE_FOLDER)} ); } /** * 显示学习计时器对话框 */ private void showStudyTimerDialog() { final AlertDialog.Builder builder = new AlertDialog.Builder(this); View view = LayoutInflater.from(this).inflate(R.layout.dialog_study_timer, null); final RadioGroup timerModeGroup = (RadioGroup) view.findViewById(R.id.timer_mode_group); final EditText hoursEdit = (EditText) view.findViewById(R.id.edit_hours); final EditText minutesEdit = (EditText) view.findViewById(R.id.edit_minutes); final EditText secondsEdit = (EditText) view.findViewById(R.id.edit_seconds); builder.setTitle("设置学习计时器"); builder.setView(view); builder.setPositiveButton("开始", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 获取计时模式 int selectedModeId = timerModeGroup.getCheckedRadioButtonId(); mIsCountdownMode = (selectedModeId == R.id.radio_countdown); // 获取时间设置 int hours = Integer.parseInt(!TextUtils.isEmpty(hoursEdit.getText()) ? hoursEdit.getText().toString() : "0"); int minutes = Integer.parseInt(!TextUtils.isEmpty(minutesEdit.getText()) ? minutesEdit.getText().toString() : "0"); int seconds = Integer.parseInt(!TextUtils.isEmpty(secondsEdit.getText()) ? secondsEdit.getText().toString() : "0"); // 计算总时长(毫秒) mTimerDuration = (hours * 3600 + minutes * 60 + seconds) * 1000; // 验证时间设置 if (mIsCountdownMode && mTimerDuration < 60000) { // 逆向计时时长小于1分钟 Toast.makeText(NoteEditActivity.this, "逆向计时时长必须大于等于1分钟", Toast.LENGTH_SHORT).show(); return; } // 验证时间设置 if (!mIsCountdownMode && mTimerDuration == 0) { // 正向计时时长为0 Toast.makeText(NoteEditActivity.this, "正向计时时长必须大于0", Toast.LENGTH_SHORT).show(); return; } // 开始计时 startStudyTimer(); dialog.dismiss(); } }); builder.setNegativeButton("取消", null); builder.show(); } /** * 开始学习计时器 */ private void startStudyTimer() { mTimerStartTime = System.currentTimeMillis(); mIsTimerRunning = true; mTimerHandler.post(mTimerRunnable); // 显示计时控制对话框 showTimerControlDialog(); } }