修改ui部分显示bug #27

Merged
p82feo7wg merged 1 commits from wangjiaqi_branch into master 2 months ago

@ -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. 测试要点
- 测试图片选择功能是否正常工作
- 测试图片裁剪功能是否正常工作
- 测试自定义背景图片是否能正确设置为笔记背景
- 测试自定义背景图片与原有背景颜色功能的切换是否正常
- 测试笔记保存和加载时,自定义背景图片是否能正确恢复
- 测试不同尺寸和比例的图片是否都能正确显示

@ -385,6 +385,30 @@
android:visibility="gone"
android:src="@drawable/selected" />
</FrameLayout>
<!-- Custom background image option -->
<FrameLayout
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_bg_custom"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@android:drawable/ic_menu_gallery"
android:scaleType="center"
android:background="#FFFFFF" />
<ImageView
android:id="@+id/iv_bg_custom_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:src="@drawable/selected" />
</FrameLayout>
</LinearLayout>
<LinearLayout

@ -130,6 +130,15 @@ public class NoteEditActivity extends Activity implements OnClickListener,
public ImageView ibSetBgColor; // 设置背景颜色按钮
}
/**
*
*
*/
private class ListItemViewHolder {
public CheckBox cbEditItem; // 清单项复选框
public NoteEditText etEditText; // 清单项编辑文本
}
/**
*
* ID
@ -1277,25 +1286,61 @@ public class NoteEditActivity extends Activity implements OnClickListener,
* @param text
*/
private void switchToListMode(String text) {
long startTime = System.currentTimeMillis();
Log.d(TAG, "switchToListMode started, text length: " + (text != null ? text.length() : 0));
// 处理null文本避免空指针异常OMO
if (text == null) {
text = "";
}
final String processedText = text != null ? 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++;
// 预处理:在后台线程中分割文本和准备数据
new Thread(new Runnable() {
@Override
public void run() {
final String[] items = processedText.split("\n"); // 按行分割文本
Log.d(TAG, "switchToListMode: split into " + items.length + " items");
// 切换回主线程更新UI
runOnUiThread(new Runnable() {
@Override
public void run() {
// 批量创建和添加视图,减少布局重绘
mEditTextList.removeAllViews(); // 清空编辑文本列表
int index = 0;
View lastItem = null;
// 先创建所有视图
ArrayList<View> 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 {

@ -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 {

@ -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

Loading…
Cancel
Save