针对富文本功能疑修

实现了加粗、斜体、下划线功能,并与插入图片功能兼容
pull/20/head
p5vjqsb6e 2 months ago
commit 0c076db1e2

Binary file not shown.

@ -67,20 +67,11 @@
android:layout_marginRight="8dip" />
<ImageButton
android:id="@+id/btn_set_bg_color_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/bg_btn_set_color" />
<!-- Insert image button OMO -->
<ImageButton
android:id="@+id/btn_insert_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@android:drawable/ic_menu_gallery"
android:background="@drawable/bg_btn_set_color"
android:layout_marginLeft="8dip" />
</LinearLayout>
<LinearLayout
@ -94,6 +85,41 @@
android:layout_height="7dip"
android:background="@drawable/bg_color_btn_mask" />
<!-- 富文本工具栏 -->
<HorizontalScrollView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#F5F5F5"
android:scrollbars="none">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="4dp">
<ImageButton
android:id="@+id/btn_bold"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_menu_edit"
android:contentDescription="@string/formatting_bold" />
<ImageButton
android:id="@+id/btn_italic"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_menu_edit"
android:contentDescription="@string/formatting_italic" />
<ImageButton
android:id="@+id/btn_underline"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@android:drawable/ic_menu_edit"
android:contentDescription="@string/formatting_underline" />
</LinearLayout>
</HorizontalScrollView>
<ScrollView
android:layout_width="fill_parent"
android:layout_height="0dip"

@ -84,6 +84,8 @@
<string name="error_note_not_exist">要查看的便签不存在</string>
<string name="error_note_empty_for_clock">不能为空便签设置闹钟提醒</string>
<string name="error_note_empty_for_send_to_desktop">不能将空便签发送到桌面</string>
<string name="error_formatting_not_supported_in_list_mode">清单模式下不支持格式化</string>
<string name="error_please_select_text">请先选择文本</string>
<string name="success_sdcard_export">导出成功</string>
<string name="failed_sdcard_export">导出失败</string>
<string name="format_exported_file_location">已将文本文件(%1$s)输出至SD卡(%2$s)目录</string>
@ -137,6 +139,14 @@
<string name="password_set_dialog_message">请输入密码</string>
<string name="password_error">密码错误,请重试</string>
<string name="password_empty">密码不能为空</string>
<!-- Rich text formatting -->
<string name="formatting_bold">加粗</string>
<string name="formatting_italic">斜体</string>
<string name="formatting_underline">下划线</string>
<string name="formatting_bullets">符号列表</string>
<string name="formatting_numbers">编号列表</string>
<string name="formatting_text_color">文字颜色</string>
<string name="formatting_bg_color">背景颜色</string>
<plurals name="search_results_title">
<item quantity="other"><xliff:g id="NUMBER">%1$s</xliff:g> 条符合“<xliff:g id="SEARCH">%2$s</xliff:g>”的搜索结果</item>
</plurals>

@ -88,6 +88,8 @@
<string name="error_note_not_exist">The note is not exist</string>
<string name="error_note_empty_for_clock">Sorry, can not set clock on empty note</string>
<string name="error_note_empty_for_send_to_desktop">Sorry, can not send and empty note to home</string>
<string name="error_formatting_not_supported_in_list_mode">Formatting is not supported in list mode</string>
<string name="error_please_select_text">Please select text first</string>
<string name="success_sdcard_export">Export successful</string>
<string name="failed_sdcard_export">Export fail</string>
<string name="format_exported_file_location">Export text file (%1$s) to SD (%2$s) directory</string>
@ -144,6 +146,14 @@
<string name="password_set_dialog_message">Enter password please</string>
<string name="password_error">Error, try again</string>
<string name="password_empty">The password can not be empty</string>
<!-- Rich text formatting -->
<string name="formatting_bold">Bold</string>
<string name="formatting_italic">Italic</string>
<string name="formatting_underline">Underline</string>
<string name="formatting_bullets">Bullet list</string>
<string name="formatting_numbers">Numbered list</string>
<string name="formatting_text_color">Text color</string>
<string name="formatting_bg_color">Background color</string>
<plurals name="search_results_title">
<item quantity="one"><xliff:g id="number" example="1">%1$s</xliff:g> result for \"<xliff:g id="search" example="???">%2$s</xliff:g>\"</item>
<!-- Case of 0 or 2 or more results. -->

@ -33,8 +33,12 @@ import android.preference.PreferenceManager;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.Html;
import android.text.format.DateUtils;
import android.text.style.BackgroundColorSpan;
import android.text.style.StyleSpan;
import android.text.style.UnderlineSpan;
import android.graphics.Typeface;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
@ -168,6 +172,11 @@ public class NoteEditActivity extends Activity implements OnClickListener,
private SharedPreferences mSharedPrefs; // 共享偏好设置
private int mFontSizeId; // 字体大小ID
private ImageButton mBtnInsertImage; // 插入图片按钮 OMO
// 富文本工具栏按钮
private ImageButton mBtnBold; // 加粗按钮
private ImageButton mBtnItalic; // 斜体按钮
private ImageButton mBtnUnderline; // 下划线按钮
private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; // 字体大小偏好设置键
private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; // 快捷图标标题最大长度
@ -351,11 +360,84 @@ public class NoteEditActivity extends Activity implements OnClickListener,
String content = mWorkingNote.getContent();
Log.d(TAG, "Initializing note content: " + content);
// 将文本内容转换为包含ImageSpan的SpannableString
SpannableString spannableString = convertTextToSpannableWithImages(content);
mNoteEditor.setText(spannableString);
// 将光标定位到文本末尾
mNoteEditor.setSelection(spannableString.length());
if (TextUtils.isEmpty(content)) {
mNoteEditor.setText("");
mNoteEditor.setSelection(0);
} else {
Spannable spannable;
// 检查是否是HTML格式包含HTML标签
if (content.contains("<") && content.contains(">")) {
try {
// 从HTML格式恢复SpannableString保留样式加粗、斜体、下划线
// Html.fromHtml会自动将HTML标签转换为对应的Span
spannable = (Spannable) Html.fromHtml(content);
} catch (Exception e) {
// 如果HTML解析失败使用普通文本
Log.e(TAG, "Error parsing HTML content: " + e.getMessage());
spannable = new SpannableString(content);
}
} else {
// 普通文本格式直接创建SpannableString
spannable = new SpannableString(content);
}
// 处理图片:将包含[IMAGE]标签的文本转换为包含ImageSpan的SpannableString
// 注意convertTextToSpannableWithImages会处理[IMAGE]标记,但会保留文本内容
String textContent = spannable.toString();
SpannableString finalSpannable = convertTextToSpannableWithImages(textContent);
// 将样式从原始Spannable复制到处理后的SpannableString
// 由于convertTextToSpannableWithImages可能改变了文本长度用空格替换了[IMAGE]标签),
// 我们需要基于文本内容来映射样式位置
if (textContent.length() == finalSpannable.length()) {
// 文本长度相同,直接复制样式
StyleSpan[] styleSpans = spannable.getSpans(0, spannable.length(), StyleSpan.class);
UnderlineSpan[] underlineSpans = spannable.getSpans(0, spannable.length(), UnderlineSpan.class);
for (StyleSpan span : styleSpans) {
int start = spannable.getSpanStart(span);
int end = spannable.getSpanEnd(span);
if (start >= 0 && end <= finalSpannable.length() && start < end) {
finalSpannable.setSpan(new StyleSpan(span.getStyle()), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
for (UnderlineSpan span : underlineSpans) {
int start = spannable.getSpanStart(span);
int end = spannable.getSpanEnd(span);
if (start >= 0 && end <= finalSpannable.length() && start < end) {
finalSpannable.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
} else {
// 文本长度不同,说明有图片被处理了
// 这种情况下,样式位置需要重新计算
// 为了简化,我们基于原始文本位置映射样式(可能会有偏差,但应该能处理大部分情况)
StyleSpan[] styleSpans = spannable.getSpans(0, spannable.length(), StyleSpan.class);
UnderlineSpan[] underlineSpans = spannable.getSpans(0, spannable.length(), UnderlineSpan.class);
for (StyleSpan span : styleSpans) {
int start = Math.max(0, Math.min(spannable.getSpanStart(span), finalSpannable.length()));
int end = Math.max(start, Math.min(spannable.getSpanEnd(span), finalSpannable.length()));
if (start < end) {
finalSpannable.setSpan(new StyleSpan(span.getStyle()), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
for (UnderlineSpan span : underlineSpans) {
int start = Math.max(0, Math.min(spannable.getSpanStart(span), finalSpannable.length()));
int end = Math.max(start, Math.min(spannable.getSpanEnd(span), finalSpannable.length()));
if (start < end) {
finalSpannable.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
mNoteEditor.setText(finalSpannable);
// 将光标定位到文本末尾
mNoteEditor.setSelection(finalSpannable.length());
}
}
// 隐藏所有背景选择的选中状态
@ -597,6 +679,22 @@ public class NoteEditActivity extends Activity implements OnClickListener,
if (mBtnInsertImage != null) {
mBtnInsertImage.setOnClickListener(this);
}
// 初始化富文本工具栏按钮
mBtnBold = (ImageButton) findViewById(R.id.btn_bold);
mBtnItalic = (ImageButton) findViewById(R.id.btn_italic);
mBtnUnderline = (ImageButton) findViewById(R.id.btn_underline);
// 为富文本工具栏按钮设置点击监听器
if (mBtnBold != null) {
mBtnBold.setOnClickListener(this);
}
if (mBtnItalic != null) {
mBtnItalic.setOnClickListener(this);
}
if (mBtnUnderline != null) {
mBtnUnderline.setOnClickListener(this);
}
}
/**
@ -674,6 +772,15 @@ public class NoteEditActivity extends Activity implements OnClickListener,
} else if (id == R.id.btn_insert_image) {
// 处理插入图片按钮点击事件 OMO
pickImageFromGallery();
} else if (id == R.id.btn_bold) {
// 富文本工具栏 - 加粗按钮
applyBoldStyle();
} else if (id == R.id.btn_italic) {
// 富文本工具栏 - 斜体按钮
applyItalicStyle();
} else if (id == R.id.btn_underline) {
// 富文本工具栏 - 下划线按钮
applyUnderlineStyle();
}
}
@ -1159,7 +1266,16 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
mWorkingNote.setWorkingText(sb.toString());
} else {
mWorkingNote.setWorkingText(mNoteEditor.getText().toString());
// 保存时将SpannableString转换为HTML格式以保留样式信息
Spannable spannable = mNoteEditor.getText();
if (spannable != null && spannable.length() > 0) {
// 将Spannable转换为HTML格式保留加粗、斜体、下划线等样式
// Html.toHtml会自动处理StyleSpan和UnderlineSpan
String htmlContent = Html.toHtml(spannable);
mWorkingNote.setWorkingText(htmlContent);
} else {
mWorkingNote.setWorkingText("");
}
}
return hasChecked;
}
@ -1298,4 +1414,173 @@ public class NoteEditActivity extends Activity implements OnClickListener,
Log.d(TAG, "Image selection canceled or failed, resultCode: " + resultCode);
}
}
/**
* 线
* @param styleType Typeface.BOLD, Typeface.ITALIC, -1 线
*/
private void applyTextStyle(int styleType) {
if (mNoteEditor == null) {
return;
}
// 检查是否在清单模式,如果是则提示用户切换到普通模式
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
showToast(R.string.error_formatting_not_supported_in_list_mode);
return;
}
Editable editable = mNoteEditor.getText();
if (editable == null) {
return;
}
int selectionStart = mNoteEditor.getSelectionStart();
int selectionEnd = mNoteEditor.getSelectionEnd();
// 如果没有选中文本,则选中光标所在的单词或当前字符
if (selectionStart == selectionEnd) {
// 尝试选中光标所在的单词
String text = editable.toString();
int start = selectionStart;
int end = selectionEnd;
// 向前查找单词开始位置
while (start > 0 && Character.isLetterOrDigit(text.charAt(start - 1))) {
start--;
}
// 向后查找单词结束位置
while (end < text.length() && Character.isLetterOrDigit(text.charAt(end))) {
end++;
}
// 如果找到了单词,则选中它;否则选中当前字符
if (start < end) {
selectionStart = start;
selectionEnd = end;
mNoteEditor.setSelection(selectionStart, selectionEnd);
} else {
// 如果没有找到单词,提示用户先选择文本
showToast(R.string.error_please_select_text);
return;
}
}
// 确保选择范围有效
if (selectionStart < 0 || selectionEnd > editable.length() || selectionStart >= selectionEnd) {
showToast(R.string.error_please_select_text);
return;
}
// 应用样式
if (styleType == -1) {
// 下划线样式
UnderlineSpan[] underlineSpans = editable.getSpans(selectionStart, selectionEnd, UnderlineSpan.class);
if (underlineSpans.length > 0) {
// 移除下划线
for (UnderlineSpan span : underlineSpans) {
int spanStart = editable.getSpanStart(span);
int spanEnd = editable.getSpanEnd(span);
editable.removeSpan(span);
// 如果移除的span范围大于选中范围需要重新应用样式到剩余部分
if (spanStart < selectionStart) {
editable.setSpan(new UnderlineSpan(), spanStart, selectionStart, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (spanEnd > selectionEnd) {
editable.setSpan(new UnderlineSpan(), selectionEnd, spanEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
} else {
// 添加下划线
editable.setSpan(new UnderlineSpan(), selectionStart, selectionEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
} else {
// 加粗或斜体样式
StyleSpan[] styleSpans = editable.getSpans(selectionStart, selectionEnd, StyleSpan.class);
// 检查选中范围内是否已经有该样式
boolean hasStyle = false;
int existingStyle = 0;
// 收集选中范围内的所有样式
for (StyleSpan span : styleSpans) {
int spanStart = editable.getSpanStart(span);
int spanEnd = editable.getSpanEnd(span);
// 如果span与选中范围重叠
if (spanStart < selectionEnd && spanEnd > selectionStart) {
int spanStyle = span.getStyle();
existingStyle |= spanStyle;
// 检查是否包含目标样式
if ((spanStyle & styleType) == styleType) {
hasStyle = true;
}
}
}
// 移除选中范围内的所有StyleSpan以便重新应用
for (int i = styleSpans.length - 1; i >= 0; i--) {
StyleSpan span = styleSpans[i];
int spanStart = editable.getSpanStart(span);
int spanEnd = editable.getSpanEnd(span);
// 如果span与选中范围重叠
if (spanStart < selectionEnd && spanEnd > selectionStart) {
editable.removeSpan(span);
// 处理span范围大于选中范围的情况保留未选中部分的样式
int spanStyle = span.getStyle();
if (spanStart < selectionStart) {
// 保留选中范围之前的样式
editable.setSpan(new StyleSpan(spanStyle), spanStart, selectionStart, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
if (spanEnd > selectionEnd) {
// 保留选中范围之后的样式
editable.setSpan(new StyleSpan(spanStyle), selectionEnd, spanEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
}
// 切换样式:如果有则移除,如果没有则添加
int finalStyle = existingStyle;
if (hasStyle) {
// 移除目标样式
finalStyle &= ~styleType;
} else {
// 添加目标样式
finalStyle |= styleType;
}
// 应用最终样式
if (finalStyle != 0) {
editable.setSpan(new StyleSpan(finalStyle), selectionStart, selectionEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
// 保持选中状态
mNoteEditor.setSelection(selectionStart, selectionEnd);
}
/**
*
*/
private void applyBoldStyle() {
applyTextStyle(Typeface.BOLD);
}
/**
*
*/
private void applyItalicStyle() {
applyTextStyle(Typeface.ITALIC);
}
/**
* 线
*/
private void applyUnderlineStyle() {
applyTextStyle(-1); // -1 表示下划线
}
}

Loading…
Cancel
Save