From 0170cdac97a87b6b3587c7921b8060b92012f0ab Mon Sep 17 00:00:00 2001 From: Surponess Date: Tue, 27 Jan 2026 11:08:28 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9ui=E9=83=A8=E5=88=86bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Notes-master/custom_background_design.md | 80 ++++++++ src/Notes-master/res/layout/note_edit.xml | 24 +++ .../net/micode/notes/ui/NoteEditActivity.java | 143 +++++++++---- .../micode/notes/ui/NotesListActivity.java | 4 +- .../net/micode/notes/ui/NotesListItem.java | 190 ++++++++++++------ 5 files changed, 349 insertions(+), 92 deletions(-) create mode 100644 src/Notes-master/custom_background_design.md diff --git a/src/Notes-master/custom_background_design.md b/src/Notes-master/custom_background_design.md new file mode 100644 index 0000000..06af831 --- /dev/null +++ b/src/Notes-master/custom_background_design.md @@ -0,0 +1,80 @@ +# 自定义背景图片功能实现方案 + +## 1. 功能概述 + +在现有背景颜色修改功能的基础上,增加自定义背景图片功能。用户可通过点击指定按钮访问手机本地存储中的图片文件,选择目标图片后进行裁剪操作,完成裁剪后将该图片设置为当前笔记的背景。 + +## 2. 设计思路 + +### 2.1 与现有系统兼容 + +- 利用现有背景颜色ID系统,为自定义背景图片分配一个特殊的ID值 +- 当背景颜色ID为特殊值时,应用加载自定义图片而非预定义背景资源 + +### 2.2 存储设计 + +- 在WorkingNote类中添加字段存储自定义背景图片路径 +- 将裁剪后的图片保存到应用私有存储目录 + +### 2.3 功能流程 + +1. 用户点击背景颜色选择器中的"自定义图片"选项 +2. 系统调用图片选择器,用户选择本地图片 +3. 系统调用图片裁剪器,用户调整图片大小和比例 +4. 裁剪完成后,图片保存到应用私有存储 +5. 更新笔记背景为裁剪后的图片 +6. 保存笔记时,同时保存背景图片信息 + +## 3. 具体实现方案 + +### 3.1 代码修改点 + +#### 3.1.1 ResourceParser类 + +- 添加CUSTOM_IMAGE常量,用于标识自定义背景图片 +- 修改相关方法,支持自定义背景图片的处理 + +#### 3.1.2 WorkingNote类 + +- 添加mCustomBgImagePath字段,存储自定义背景图片路径 +- 添加相应的getter和setter方法 +- 修改setBgColorId和getBgColorResId方法,支持自定义背景图片 + +#### 3.1.3 NoteEditActivity类 + +- 在背景颜色选择器中添加"自定义图片"选项 +- 实现图片选择功能(调用系统图片选择器) +- 实现图片裁剪功能(调用系统裁剪器) +- 实现图片保存和设置为背景的功能 +- 处理权限请求(READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE) + +### 3.2 界面设计 + +- 在现有背景颜色选择器中添加一个新的选项卡片,显示"自定义图片"文字和图标 +- 点击该选项后,启动图片选择流程 + +### 3.3 权限处理 + +- 在AndroidManifest.xml中添加必要的权限声明 +- 在运行时请求相应的权限 + +### 3.4 图片处理 + +- 选择图片后,调用系统裁剪功能,限制裁剪比例为笔记的宽高比 +- 将裁剪后的图片保存到应用的私有存储目录 +- 使用FileProvider确保安全访问 + +## 4. 兼容性考虑 + +- 确保自定义背景图片功能与原有背景颜色修改功能可以无缝切换 +- 当用户选择预定义背景颜色时,清除自定义背景图片信息 +- 当用户选择自定义背景图片时,更新背景颜色ID为CUSTOM_IMAGE + +## 5. 测试要点 + +- 测试图片选择功能是否正常工作 +- 测试图片裁剪功能是否正常工作 +- 测试自定义背景图片是否能正确设置为笔记背景 +- 测试自定义背景图片与原有背景颜色功能的切换是否正常 +- 测试笔记保存和加载时,自定义背景图片是否能正确恢复 +- 测试不同尺寸和比例的图片是否都能正确显示 \ No newline at end of file diff --git a/src/Notes-master/res/layout/note_edit.xml b/src/Notes-master/res/layout/note_edit.xml index d60768e..ac9a3d8 100644 --- a/src/Notes-master/res/layout/note_edit.xml +++ b/src/Notes-master/res/layout/note_edit.xml @@ -385,6 +385,30 @@ android:visibility="gone" android:src="@drawable/selected" /> + + + + + + + + viewsToAdd = new ArrayList<>(); + for (String item : items) { + if(!TextUtils.isEmpty(item)) { + View listItem = getListItem(item, index); + viewsToAdd.add(listItem); + index++; + } + } + + // 添加最后一个空列表项 + lastItem = getListItem("", index); + viewsToAdd.add(lastItem); + + // 一次性添加所有视图到容器 + for (View view : viewsToAdd) { + mEditTextList.addView(view); + } + + // 设置焦点 + ListItemViewHolder holder = (ListItemViewHolder) lastItem.getTag(); + holder.etEditText.requestFocus(); + + mNoteEditor.setVisibility(View.GONE); // 隐藏普通编辑器 + mEditTextList.setVisibility(View.VISIBLE); // 显示清单编辑器 + + long endTime = System.currentTimeMillis(); + Log.d(TAG, "switchToListMode completed in " + (endTime - startTime) + "ms"); + } + }); } - } - mEditTextList.addView(getListItem("", index)); // 添加最后一个空列表项 - mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus(); // 设置焦点 - - mNoteEditor.setVisibility(View.GONE); // 隐藏普通编辑器 - mEditTextList.setVisibility(View.VISIBLE); // 显示清单编辑器 + }).start(); } /** @@ -1306,24 +1351,42 @@ public class NoteEditActivity extends Activity implements OnClickListener, * @return 带有高亮效果的文本 */ private Spannable getHighlightQueryResult(String fullText, String userQuery) { + long startTime = System.currentTimeMillis(); + String text = fullText == null ? "" : fullText; SpannableString spannable = new SpannableString(text); // 处理查询关键词高亮 - if (!TextUtils.isEmpty(userQuery)) { - mPattern = Pattern.compile(userQuery); // 编译正则表达式 - Matcher m = mPattern.matcher(text); // 创建匹配器 - 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(); + if (!TextUtils.isEmpty(userQuery) && !TextUtils.isEmpty(text)) { + try { + mPattern = Pattern.compile(userQuery); // 编译正则表达式 + Matcher m = mPattern.matcher(text); // 创建匹配器 + int start = 0; + int matchCount = 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(); + matchCount++; + + // 限制匹配数量,避免处理过多匹配项导致性能问题 + if (matchCount > 100) { + Log.w(TAG, "getHighlightQueryResult: too many matches, stopping at 100"); + break; + } + } + Log.d(TAG, "getHighlightQueryResult: found " + matchCount + " matches"); + } catch (Exception e) { + Log.e(TAG, "Error in getHighlightQueryResult: " + e.getMessage()); } } - + + long endTime = System.currentTimeMillis(); + Log.d(TAG, "getHighlightQueryResult completed in " + (endTime - startTime) + "ms"); + return spannable; } @@ -1336,11 +1399,18 @@ public class NoteEditActivity extends Activity implements OnClickListener, */ 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); + + // 创建并缓存ViewHolder + ListItemViewHolder holder = new ListItemViewHolder(); + holder.etEditText = (NoteEditText) view.findViewById(R.id.et_edit_text); + holder.cbEditItem = (CheckBox) view.findViewById(R.id.cb_edit_item); + view.setTag(holder); + + final NoteEditText edit = holder.etEditText; edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); // 设置复选框的选中状态变化监听器 - CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item)); + CheckBox cb = holder.cbEditItem; cb.setOnCheckedChangeListener(new OnCheckedChangeListener() { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { @@ -1379,10 +1449,14 @@ public class NoteEditActivity extends Activity implements OnClickListener, Log.e(TAG, "Wrong index, should not happen"); return; } + + View view = mEditTextList.getChildAt(index); + ListItemViewHolder holder = (ListItemViewHolder) view.getTag(); + if(hasText) { - mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE); + holder.cbEditItem.setVisibility(View.VISIBLE); } else { - mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE); + holder.cbEditItem.setVisibility(View.GONE); } } @@ -1461,9 +1535,10 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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); + ListItemViewHolder holder = (ListItemViewHolder) view.getTag(); + NoteEditText edit = holder.etEditText; if (!TextUtils.isEmpty(edit.getText())) { - if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) { + if (holder.cbEditItem.isChecked()) { sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n"); hasChecked = true; } else { 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 6d5cc26..00c1d98 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java @@ -648,8 +648,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt selection = "(" + NoteColumns.TYPE + "=" + Notes.TYPE_CHECKLIST + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR (" + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND " + NoteColumns.NOTES_COUNT + ">0)"; } else { - // 根文件夹下的笔记模式:显示文件夹、普通笔记和非空通话记录文件夹 - selection = "(" + NoteColumns.TYPE + "<>" + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + + // 根文件夹下的笔记模式:只显示文件夹、普通笔记和非空通话记录文件夹,不显示清单 + selection = "(" + NoteColumns.TYPE + " IN (" + Notes.TYPE_NOTE + ", " + Notes.TYPE_FOLDER + ") AND " + NoteColumns.PARENT_ID + "=?)" + " OR (" + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND " + NoteColumns.NOTES_COUNT + ">0)"; } } else { diff --git a/src/Notes-master/src/net/micode/notes/ui/NotesListItem.java b/src/Notes-master/src/net/micode/notes/ui/NotesListItem.java index 46c35f0..140fee5 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesListItem.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesListItem.java @@ -144,6 +144,98 @@ public class NotesListItem extends LinearLayout { } } }); + + // 初始化复选框点击监听器 + mCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + // 切换完成状态 + if (mItemData != null) { + mItemData.setChecked(isChecked); + + // 更新UI + updateChecklistStyle(isChecked); + + // 通知监听器,保存完成状态到数据库 + if (mOnCheckStatusChangedListener != null) { + mOnCheckStatusChangedListener.onCheckStatusChanged(mItemData.getId(), isChecked); + } + } + } + }); + + // 初始化长按事件,长按后可编辑 + mChecklistTitle.setLongClickable(true); + mChecklistTitle.setOnLongClickListener(new OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + // 设置为可编辑状态 + mChecklistTitle.setFocusable(true); + mChecklistTitle.setFocusableInTouchMode(true); + mChecklistTitle.setClickable(true); + mChecklistTitle.setCursorVisible(true); + + // 获得焦点并弹出键盘 + mChecklistTitle.requestFocus(); + mChecklistTitle.setSelection(mChecklistTitle.getText().length()); + + // 强制弹出键盘 + InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + imm.showSoftInput(mChecklistTitle, InputMethodManager.SHOW_FORCED); + } + + return true; // 消耗长按事件,避免触发其他长按事件 + } + }); + + // 初始化双击事件,双击切换完成状态 + mChecklistTitle.setClickable(true); + mChecklistTitle.setOnClickListener(new OnClickListener() { + private long lastClickTime = 0; + + @Override + public void onClick(View v) { + long currentTime = System.currentTimeMillis(); + if (currentTime - lastClickTime < 300) { + // 双击事件:切换完成状态 + if (mItemData != null) { + boolean isChecked = !mItemData.isChecked(); + mItemData.setChecked(isChecked); + + // 更新复选框状态 + mCheckBox.setChecked(isChecked); + + // 更新UI + updateChecklistStyle(isChecked); + + // 添加完成特效 + addCheckEffect(); + + // 通知监听器,保存完成状态到数据库 + if (mOnCheckStatusChangedListener != null) { + mOnCheckStatusChangedListener.onCheckStatusChanged(mItemData.getId(), isChecked); + } + } + } + lastClickTime = currentTime; + } + }); + + // 初始化焦点变化监听器,失去焦点时保存修改并恢复为不可编辑状态 + mChecklistTitle.setOnFocusChangeListener(new OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (!hasFocus) { + // 失去焦点时,保存修改并恢复为不可编辑状态 + mChecklistTitle.setFocusable(false); + mChecklistTitle.setFocusableInTouchMode(false); + // 保持可点击状态,以便双击事件能够触发 + mChecklistTitle.setClickable(true); + mChecklistTitle.setCursorVisible(false); + } + } + }); } /** @@ -229,7 +321,8 @@ public class NotesListItem extends LinearLayout { // 初始状态下,EditText不可编辑,只能长按后编辑 mChecklistTitle.setFocusable(false); mChecklistTitle.setFocusableInTouchMode(false); - mChecklistTitle.setClickable(false); + // 保持可点击状态,以便双击事件能够触发 + mChecklistTitle.setClickable(true); mChecklistTitle.setCursorVisible(false); // 根据完成状态设置复选框和样式 @@ -237,62 +330,8 @@ public class NotesListItem extends LinearLayout { mCheckBox.setChecked(isChecked); updateChecklistStyle(isChecked); - // 添加复选框点击事件,切换完成状态 + // 启用复选框点击事件 mCheckBox.setClickable(true); - mCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - // 切换完成状态 - data.setChecked(isChecked); - - // 更新UI - updateChecklistStyle(isChecked); - - // 通知监听器,保存完成状态到数据库 - if (mOnCheckStatusChangedListener != null) { - mOnCheckStatusChangedListener.onCheckStatusChanged(data.getId(), isChecked); - } - } - }); - - // 添加长按事件,长按后可编辑 - mChecklistTitle.setLongClickable(true); - mChecklistTitle.setOnLongClickListener(new OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - // 设置为可编辑状态 - mChecklistTitle.setFocusable(true); - mChecklistTitle.setFocusableInTouchMode(true); - mChecklistTitle.setClickable(true); - mChecklistTitle.setCursorVisible(true); - - // 获得焦点并弹出键盘 - mChecklistTitle.requestFocus(); - mChecklistTitle.setSelection(mChecklistTitle.getText().length()); - - // 强制弹出键盘 - InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE); - if (imm != null) { - imm.showSoftInput(mChecklistTitle, InputMethodManager.SHOW_FORCED); - } - - return true; // 消耗长按事件,避免触发其他长按事件 - } - }); - - // 添加焦点变化监听器,失去焦点时保存修改并恢复为不可编辑状态 - mChecklistTitle.setOnFocusChangeListener(new OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (!hasFocus) { - // 失去焦点时,保存修改并恢复为不可编辑状态 - mChecklistTitle.setFocusable(false); - mChecklistTitle.setFocusableInTouchMode(false); - mChecklistTitle.setClickable(false); - mChecklistTitle.setCursorVisible(false); - } - } - }); } else { // 普通模式:显示TextView,隐藏编辑框 mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); @@ -352,6 +391,45 @@ public class NotesListItem extends LinearLayout { } } + /** + * 添加完成特效 + * 为双击完成的清单项目添加视觉反馈 + */ + private void addCheckEffect() { + // 创建缩放动画 + android.view.animation.ScaleAnimation scaleAnimation = new android.view.animation.ScaleAnimation( + 1.0f, 1.2f, 1.0f, 1.2f, // 从正常大小到放大1.2倍 + android.view.animation.Animation.RELATIVE_TO_SELF, 0.5f, // 缩放中心X + android.view.animation.Animation.RELATIVE_TO_SELF, 0.5f // 缩放中心Y + ); + scaleAnimation.setDuration(200); // 动画持续时间 + scaleAnimation.setRepeatMode(android.view.animation.Animation.REVERSE); // 反向重复 + scaleAnimation.setRepeatCount(1); // 重复一次 + + // 创建淡入淡出动画 + android.view.animation.AlphaAnimation alphaAnimation = new android.view.animation.AlphaAnimation( + 1.0f, 0.6f // 从完全不透明到半透明 + ); + alphaAnimation.setDuration(200); // 动画持续时间 + alphaAnimation.setRepeatMode(android.view.animation.Animation.REVERSE); // 反向重复 + alphaAnimation.setRepeatCount(1); // 重复一次 + + // 创建动画集 + android.view.animation.AnimationSet animationSet = new android.view.animation.AnimationSet(true); + animationSet.addAnimation(scaleAnimation); + animationSet.addAnimation(alphaAnimation); + + // 应用动画到复选框 + if (mCheckBox != null) { + mCheckBox.startAnimation(animationSet); + } + + // 应用动画到标题 + if (mChecklistTitle != null) { + mChecklistTitle.startAnimation(animationSet); + } + } + /** * 设置笔记项的背景 * @param data 笔记项数据 -- 2.34.1