增加图片出入和删除的逻辑

pull/47/head
陶俊宇 3 months ago
parent 236a22e8fe
commit 76b36afc94

@ -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;
@ -175,6 +187,9 @@ public class NoteEditActivity extends Activity implements OnClickListener,
/** 笔记编辑面板 */
private View mNoteEditorPanel;
/** 字符统计显示文本框 */
private TextView mTvCharCount;
/** 工作笔记对象,用于处理笔记数据 */
private WorkingNote mWorkingNote;
@ -196,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;
@ -349,10 +369,37 @@ 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());
}
// 初始化字符统计显示
updateCharCount();
for (Integer id : sBgSelectorSelectionMap.keySet()) {
findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE);
}
@ -483,7 +530,9 @@ public class NoteEditActivity extends Activity implements OnClickListener,
mNoteHeaderHolder.ibSetBgColor = (ImageButton) findViewById(R.id.btn_set_bg_color);
mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this);
mNoteEditor = (NoteEditText) findViewById(R.id.note_edit_view);
mNoteEditor.setOnTextViewChangeListener(this);
mNoteEditorPanel = findViewById(R.id.sv_note_edit);
mTvCharCount = (TextView) findViewById(R.id.tv_char_count);
mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);
for (int id : sBgSelectorBtnsMap.keySet()) {
ImageView iv = (ImageView) findViewById(id);
@ -771,14 +820,15 @@ public class NoteEditActivity extends Activity implements OnClickListener,
case R.id.menu_unlock:
showPasswordDialogForUnlock();
break;
case R.id.menu_change_password:
showChangePasswordDialog();
break;
case R.id.menu_note_template:
openTemplateSelector();
break;
case R.id.menu_insert_image:
showImageSourceDialog();
break;
default:
break;
}
@ -1386,6 +1436,15 @@ public class NoteEditActivity extends Activity implements OnClickListener,
} else {
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE);
}
// 更新字符统计
updateCharCount();
}
/**
*
*/
public void onContentChange() {
updateCharCount();
}
/**
@ -1539,6 +1598,45 @@ public class NoteEditActivity extends Activity implements OnClickListener,
showToast(resId, Toast.LENGTH_SHORT);
}
/**
*
* @param text
* @return
*/
private int countValidCharacters(String text) {
if (TextUtils.isEmpty(text)) {
return 0;
}
// 去除所有空白字符后计算长度
return text.replaceAll("\\s", "").length();
}
/**
*
*/
private void updateCharCount() {
int charCount = 0;
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
// 列表模式:遍历所有列表项
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())) {
charCount += countValidCharacters(edit.getText().toString());
}
}
} else {
// 普通文本模式:直接计算编辑框内容
if (!TextUtils.isEmpty(mNoteEditor.getText())) {
charCount = countValidCharacters(mNoteEditor.getText().toString());
}
}
// 更新显示
if (mTvCharCount != null) {
mTvCharCount.setText(charCount + " 字符");
}
}
/**
*
* <p>
@ -1556,37 +1654,192 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
/**
*
* <p>
* TemplateSelectActivity
*
* </p>
* @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();
}
}
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());
}
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 = "<img src=\"data:image/jpeg;base64," + base64Image + "\" style=\"max-width:100%;height:auto;\" />";
// 获取当前光标位置
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);
}
/**
* BitmapBase64
*/
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

@ -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;
@ -37,6 +38,8 @@ import android.view.MenuItem;
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;
@ -112,6 +115,11 @@ public class NoteEditText extends EditText {
*
*/
void onTextChange(int index, boolean hasText);
/**
*
*/
void onContentChange();
}
/**
@ -151,6 +159,8 @@ public class NoteEditText extends EditText {
*/
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
mIndex = 0;
initTextWatcher();
}
/**
@ -161,13 +171,37 @@ public class NoteEditText extends EditText {
*/
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
mIndex = 0;
initTextWatcher();
}
/**
*
*/
private void initTextWatcher() {
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) {
}
@Override
public void afterTextChanged(android.text.Editable s) {
if (mOnTextViewChangeListener != null) {
mOnTextViewChangeListener.onContentChange();
}
}
});
}
/**
*
* <p>
*
*
* </p>
* @param event
* @return
@ -187,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;
}
/**
*
@ -207,7 +263,9 @@ public class NoteEditText extends EditText {
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
if (mOnTextViewChangeListener != null) {
// 只有在列表模式下mIndex > 0才阻止默认行为
// 普通文本模式下让系统默认处理回车键
if (mOnTextViewChangeListener != null && mIndex > 0) {
return false;
}
break;
@ -244,10 +302,14 @@ public class NoteEditText extends EditText {
break;
case KeyEvent.KEYCODE_ENTER:
if (mOnTextViewChangeListener != null) {
int selectionStart = getSelectionStart();
String text = getText().subSequence(selectionStart, length()).toString();
setText(getText().subSequence(0, selectionStart));
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);
// 只有在列表模式下mIndex > 0才执行特殊处理
// 普通文本模式下,让系统默认处理回车键
if (mIndex > 0) {
int selectionStart = getSelectionStart();
String text = getText().subSequence(selectionStart, length()).toString();
setText(getText().subSequence(0, selectionStart));
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);
}
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
@ -284,6 +346,7 @@ public class NoteEditText extends EditText {
*
* <p>
*
*
* </p>
* @param menu
*/
@ -296,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;
@ -322,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();
}
}
}
}
/**
*

@ -181,6 +181,17 @@
</LinearLayout>
</ScrollView>
<TextView
android:id="@+id/tv_char_count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|bottom"
android:layout_marginLeft="16dp"
android:layout_marginBottom="8dp"
android:textSize="12sp"
android:textColor="@color/secondary_text_dark"
android:text="0 字符" />
<ImageView
android:layout_width="fill_parent"
android:layout_height="7dip"

@ -65,4 +65,8 @@
<item
android:id="@+id/menu_note_template"
android:title="@string/menu_note_template" />
<item
android:id="@+id/menu_insert_image"
android:title="@string/menu_insert_image" />
</menu>

@ -164,5 +164,13 @@
<string name="message_template_deleted">Template deleted successfully</string>
<string name="alert_message_delete_template">Are you sure you want to delete this template?</string>
<string name="menu_note_template">Note Template</string>
<!-- Image related strings -->
<string name="menu_insert_image">Insert Image</string>
<string name="menu_take_photo">Take Photo</string>
<string name="menu_choose_from_gallery">Choose from Gallery</string>
<string name="menu_delete_image">Delete Image</string>
<string name="error_image_selection">Failed to select image</string>
<string name="error_image_insertion">Failed to insert image</string>
</resources>

Loading…
Cancel
Save