From 0b89941200287e596ba9fa8bcaf42e51071bf0cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=B6=E4=BF=8A=E5=AE=87?= <2643473564@qq.com> Date: Tue, 27 Jan 2026 11:59:17 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B0=9D=E8=AF=95=E8=A7=A3=E5=86=B3=E5=86=B2?= =?UTF-8?q?=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../net/micode/notes/ui/NoteEditActivity.java | 246 ++++++++++++++++-- .../net/micode/notes/ui/NoteEditText.java | 61 +++++ src/main/res/menu/note_edit.xml | 4 + src/main/res/values/strings.xml | 8 + 4 files changed, 296 insertions(+), 23 deletions(-) diff --git a/src/main/java/net/micode/notes/ui/NoteEditActivity.java b/src/main/java/net/micode/notes/ui/NoteEditActivity.java index df2a73b..969a712 100644 --- a/src/main/java/net/micode/notes/ui/NoteEditActivity.java +++ b/src/main/java/net/micode/notes/ui/NoteEditActivity.java @@ -61,6 +61,18 @@ import net.micode.notes.data.Notes.NoteColumns; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import android.content.Intent; +import android.provider.MediaStore; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.text.Html; +import android.util.Base64; +import android.os.Bundle; +import android.net.Uri; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import net.micode.notes.R; import net.micode.notes.data.Notes; @@ -199,6 +211,11 @@ public class NoteEditActivity extends Activity implements OnClickListener, /** 模板选择请求码 */ private static final int REQUEST_CODE_TEMPLATE = 1001; + + /** 图片选择请求码 */ + private static final int REQUEST_CODE_IMAGE_PICK = 1002; + /** 拍照请求码 */ + private static final int REQUEST_CODE_CAMERA = 1003; /** 清单模式下的编辑文本列表 */ private LinearLayout mEditTextList; @@ -352,7 +369,32 @@ public class NoteEditActivity extends Activity implements OnClickListener, } else { // 解析HTML格式的富文本内容,添加null检查防止闪退 String content = mWorkingNote.getContent(); - CharSequence htmlContent = Html.fromHtml(content == null ? "" : content); + CharSequence htmlContent = Html.fromHtml(content == null ? "" : content, new Html.ImageGetter() { + @Override + public Drawable getDrawable(String source) { + // 处理data:image格式的图片 + if (source.startsWith("data:image/")) { + try { + // 提取Base64部分 + String base64 = source.substring(source.indexOf(",") + 1); + // 解码Base64 + byte[] data = Base64.decode(base64, Base64.DEFAULT); + // 创建Bitmap + Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); + if (bitmap != null) { + // 创建Drawable + BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap); + // 设置Drawable的边界 + drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight()); + return drawable; + } + } catch (Exception e) { + Log.e(TAG, "Error loading image: " + e.getMessage()); + } + } + return null; + } + }, null); mNoteEditor.setText(getHighlightQueryResult(htmlContent, mUserQuery)); mNoteEditor.setSelection(mNoteEditor.getText().length()); } @@ -784,6 +826,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, case R.id.menu_note_template: openTemplateSelector(); break; + case R.id.menu_insert_image: + showImageSourceDialog(); + break; default: break; } @@ -1609,37 +1654,192 @@ public class NoteEditActivity extends Activity implements OnClickListener, } /** - * 处理从模板选择页面返回的结果 - *

- * 该方法在从TemplateSelectActivity返回时被调用, - * 如果用户选择了模板,则更新当前笔记的内容 - *

- * @param requestCode 请求码 - * @param resultCode 结果码 - * @param data 返回的Intent数据 + * 处理活动结果 */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if (requestCode == REQUEST_CODE_TEMPLATE && resultCode == RESULT_OK) { - if (data != null) { - String templateContent = data.getStringExtra(TemplateSelectActivity.EXTRA_TEMPLATE_CONTENT); - if (!TextUtils.isEmpty(templateContent)) { - // 保存模板内容到WorkingNote,防止onResume时被覆盖 - mWorkingNote.setWorkingText(templateContent); - - // 更新笔记内容 - if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { - switchToListMode(templateContent); - } else { - mNoteEditor.setText(Html.fromHtml(templateContent)); - mNoteEditor.setSelection(mNoteEditor.getText().length()); + if (resultCode == RESULT_OK) { + switch (requestCode) { + case REQUEST_CODE_TEMPLATE: + // 模板选择结果处理 + if (data != null) { + String templateContent = data.getStringExtra(TemplateSelectActivity.EXTRA_TEMPLATE_CONTENT); + if (!TextUtils.isEmpty(templateContent)) { + // 保存模板内容到WorkingNote,防止onResume时被覆盖 + mWorkingNote.setWorkingText(templateContent); + + // 更新笔记内容 + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + switchToListMode(templateContent); + } else { + mNoteEditor.setText(Html.fromHtml(templateContent)); + mNoteEditor.setSelection(mNoteEditor.getText().length()); + } + Toast.makeText(this, R.string.notealert_enter, Toast.LENGTH_SHORT).show(); + } + } + break; + case REQUEST_CODE_IMAGE_PICK: + // 从相册选择图片 + if (data != null && data.getData() != null) { + handleImageSelection(data.getData()); } - Toast.makeText(this, R.string.notealert_enter, Toast.LENGTH_SHORT).show(); + break; + case REQUEST_CODE_CAMERA: + // 拍照结果 + if (data != null && data.getExtras() != null) { + Bitmap bitmap = (Bitmap) data.getExtras().get("data"); + if (bitmap != null) { + insertImageToNote(bitmap); + } + } + break; + } + } + } + + /** + * 显示图片来源选择对话框 + */ + private void showImageSourceDialog() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.menu_insert_image); + builder.setItems(new CharSequence[] { + getString(R.string.menu_take_photo), + getString(R.string.menu_choose_from_gallery) + }, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case 0: + // 拍照 + Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + if (cameraIntent.resolveActivity(getPackageManager()) != null) { + startActivityForResult(cameraIntent, REQUEST_CODE_CAMERA); + } else { + showToast(R.string.error_image_selection); + } + break; + case 1: + // 从相册选择 + Intent galleryIntent = new Intent(Intent.ACTION_PICK, + MediaStore.Images.Media.EXTERNAL_CONTENT_URI); + galleryIntent.setType("image/*"); + startActivityForResult(galleryIntent, REQUEST_CODE_IMAGE_PICK); + break; } } + }); + builder.show(); + } + + /** + * 处理从相册选择的图片 + */ + private void handleImageSelection(Uri uri) { + try { + Bitmap bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), uri); + if (bitmap != null) { + insertImageToNote(bitmap); + } + } catch (IOException e) { + Log.e(TAG, "Error loading image: " + e.getMessage()); + showToast(R.string.error_image_selection); } } + + /** + * 将图片插入到笔记中 + */ + private void insertImageToNote(Bitmap bitmap) { + try { + // 压缩图片 + Bitmap compressedBitmap = compressBitmap(bitmap, 800, 600, 80); + // 转换为Base64 + String base64Image = bitmapToBase64(compressedBitmap); + // 创建HTML图片标签 + String imageTag = ""; + + // 获取当前光标位置 + int cursorPosition = mNoteEditor.getSelectionStart(); + // 获取当前文本 + String currentText = mNoteEditor.getText().toString(); + // 插入图片标签 + String newText = currentText.substring(0, cursorPosition) + imageTag + currentText.substring(cursorPosition); + + // 更新编辑器内容 + mNoteEditor.setText(Html.fromHtml(newText, new Html.ImageGetter() { + @Override + public Drawable getDrawable(String source) { + // 处理data:image格式的图片 + if (source.startsWith("data:image/")) { + try { + // 提取Base64部分 + String base64 = source.substring(source.indexOf(",") + 1); + // 解码Base64 + byte[] data = Base64.decode(base64, Base64.DEFAULT); + // 创建Bitmap + Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); + if (bitmap != null) { + // 创建Drawable + BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap); + // 设置Drawable的边界 + drawable.setBounds(0, 0, bitmap.getWidth(), bitmap.getHeight()); + return drawable; + } + } catch (Exception e) { + Log.e(TAG, "Error loading image from data URL: " + e.getMessage()); + } + } + return null; + } + }, null)); + // 设置光标位置到图片后面 + mNoteEditor.setSelection(mNoteEditor.getText().length()); + + // 保存笔记 + saveNote(); + } catch (Exception e) { + Log.e(TAG, "Error inserting image: " + e.getMessage()); + showToast(R.string.error_image_insertion); + } + } + + /** + * 压缩图片 + */ + private Bitmap compressBitmap(Bitmap bitmap, int maxWidth, int maxHeight, int quality) { + int width = bitmap.getWidth(); + int height = bitmap.getHeight(); + + // 计算缩放比例 + float scaleWidth = ((float) maxWidth) / width; + float scaleHeight = ((float) maxHeight) / height; + float scale = Math.min(scaleWidth, scaleHeight); + + // 如果不需要缩放,直接返回原图片 + if (scale >= 1) { + return bitmap; + } + + // 计算新尺寸 + int newWidth = Math.round(width * scale); + int newHeight = Math.round(height * scale); + + // 创建压缩后的图片 + return Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, true); + } + + /** + * 将Bitmap转换为Base64字符串 + */ + private String bitmapToBase64(Bitmap bitmap) { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.JPEG, 80, baos); + byte[] byteArray = baos.toByteArray(); + return Base64.encodeToString(byteArray, Base64.DEFAULT); + } /** * 显示Toast提示信息 diff --git a/src/main/java/net/micode/notes/ui/NoteEditText.java b/src/main/java/net/micode/notes/ui/NoteEditText.java index cd75b4c..07f2efe 100644 --- a/src/main/java/net/micode/notes/ui/NoteEditText.java +++ b/src/main/java/net/micode/notes/ui/NoteEditText.java @@ -23,6 +23,7 @@ import android.text.Selection; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; +import android.text.Editable; import android.text.TextUtils; import android.text.style.StyleSpan; import android.text.style.URLSpan; @@ -38,6 +39,7 @@ import android.view.MenuItem.OnMenuItemClickListener; import android.view.MotionEvent; import android.widget.EditText; import android.text.TextWatcher; +import android.text.style.ImageSpan; import net.micode.notes.R; @@ -199,6 +201,7 @@ public class NoteEditText extends EditText { * 处理触摸事件 *

* 当用户点击编辑框时,将光标定位到点击位置 + * 当用户点击图片时,显示上下文菜单 *

* @param event 触摸事件 * @return 事件是否被处理 @@ -218,12 +221,34 @@ public class NoteEditText extends EditText { Layout layout = getLayout(); int line = layout.getLineForVertical(y); int off = layout.getOffsetForHorizontal(line, x); + + // 检查是否点击了图片 + if (isClickOnImage(off)) { + // 显示上下文菜单 + this.showContextMenu(); + return true; + } + Selection.setSelection(getText(), off); break; } return super.onTouchEvent(event); } + + /** + * 检查点击位置是否在图片上 + * @param offset 点击位置的文本偏移量 + * @return 是否点击了图片 + */ + private boolean isClickOnImage(int offset) { + if (getText() instanceof Spannable) { + Spannable spannable = (Spannable) getText(); + ImageSpan[] imageSpans = spannable.getSpans(offset, offset, ImageSpan.class); + return imageSpans.length > 0; + } + return false; + } /** * 处理按键按下事件 @@ -321,6 +346,7 @@ public class NoteEditText extends EditText { * 创建上下文菜单 *

* 当选中的文本包含链接时,创建相应的链接操作菜单 + * 当点击图片时,创建删除图片的菜单 *

* @param menu 上下文菜单 */ @@ -333,6 +359,21 @@ public class NoteEditText extends EditText { int min = Math.min(selStart, selEnd); int max = Math.max(selStart, selEnd); + // 检查是否有图片 + final ImageSpan[] imageSpans = ((Spanned) getText()).getSpans(min, max, ImageSpan.class); + if (imageSpans.length > 0) { + menu.add(0, 1, 0, R.string.menu_delete_image).setOnMenuItemClickListener( + new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // 删除图片 + deleteImage(imageSpans[0]); + return true; + } + }); + return; + } + + // 检查是否有链接 final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); if (urls.length == 1) { int defaultResId = 0; @@ -359,6 +400,26 @@ public class NoteEditText extends EditText { } super.onCreateContextMenu(menu); } + + /** + * 删除图片 + * @param imageSpan 要删除的图片 span + */ + private void deleteImage(ImageSpan imageSpan) { + if (getText() instanceof Spannable && getText() instanceof Editable) { + Editable editable = (Editable) getText(); + int start = editable.getSpanStart(imageSpan); + int end = editable.getSpanEnd(imageSpan); + if (start >= 0 && end >= 0) { + // 删除图片 + editable.delete(start, end); + // 通知内容变化 + if (mOnTextViewChangeListener != null) { + mOnTextViewChangeListener.onContentChange(); + } + } + } + } /** * 应用粗体格式到选中文本 diff --git a/src/main/res/menu/note_edit.xml b/src/main/res/menu/note_edit.xml index a1f9976..d396c0e 100644 --- a/src/main/res/menu/note_edit.xml +++ b/src/main/res/menu/note_edit.xml @@ -65,4 +65,8 @@ + + \ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 4a02bd1..f3ae6dc 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -164,5 +164,13 @@ Template deleted successfully Are you sure you want to delete this template? Note Template + + + Insert Image + Take Photo + Choose from Gallery + Delete Image + Failed to select image + Failed to insert image