Merge pull request '新增富文本编辑功能' (#16) from tangbo_branch into master

pull/25/head
p7fulywfa 3 months ago
commit 960eb0b233

@ -520,16 +520,29 @@ public class WorkingNote {
/**
*
* <p>
*
* </p>
*
* @param text Spannable
*/
public void setWorkingText(CharSequence text) {
String textStr = text.toString();
if (!TextUtils.equals(mContent, textStr)) {
mContent = textStr;
mNote.setTextData(DataColumns.CONTENT, mContent);
}
}
/**
*
* <p>
*
* </p>
*
* @param text
*/
public void setWorkingText(String text) {
if (!TextUtils.equals(mContent, text)) {
mContent = text;
mNote.setTextData(DataColumns.CONTENT, mContent);
}
setWorkingText((CharSequence) text);
}
/**

@ -30,6 +30,7 @@ import android.content.SharedPreferences;
import android.graphics.Paint;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Html;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
@ -43,10 +44,12 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.database.Cursor;
import android.content.ContentResolver;
@ -95,7 +98,7 @@ public class NoteEditActivity extends Activity implements OnClickListener,
public TextView tvAlertDate;
public ImageView ibSetBgColor;
public ImageButton ibSetBgColor;
}
/** 背景颜色选择按钮映射表将按钮ID映射到颜色ID */
@ -145,14 +148,25 @@ public class NoteEditActivity extends Activity implements OnClickListener,
/** 头部视图面板 */
private View mHeadViewPanel;
/** 粗体按钮 */
private Button mBtnBold;
/** 斜体按钮 */
private Button mBtnItalic;
/** 下划线按钮 */
private Button mBtnUnderline;
/** 字体颜色按钮 */
private Button mBtnTextColor;
/** 背景颜色选择器视图 */
private View mNoteBgColorSelector;
/** 字体颜色选择器视图 */
private View mNoteTextColorSelector;
/** 字体大小选择器视图 */
private View mFontSizeSelector;
/** 笔记编辑文本框 */
private EditText mNoteEditor;
private NoteEditText mNoteEditor;
/** 笔记编辑面板 */
private View mNoteEditorPanel;
@ -326,7 +340,10 @@ public class NoteEditActivity extends Activity implements OnClickListener,
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
switchToListMode(mWorkingNote.getContent());
} else {
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
// 解析HTML格式的富文本内容添加null检查防止闪退
String content = mWorkingNote.getContent();
CharSequence htmlContent = Html.fromHtml(content == null ? "" : content);
mNoteEditor.setText(getHighlightQueryResult(htmlContent, mUserQuery));
mNoteEditor.setSelection(mNoteEditor.getText().length());
}
for (Integer id : sBgSelectorSelectionMap.keySet()) {
@ -412,6 +429,12 @@ public class NoteEditActivity extends Activity implements OnClickListener,
return true;
}
if (mNoteTextColorSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mNoteTextColorSelector, ev)) {
mNoteTextColorSelector.setVisibility(View.GONE);
return true;
}
if (mFontSizeSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mFontSizeSelector, ev)) {
mFontSizeSelector.setVisibility(View.GONE);
@ -450,9 +473,9 @@ public class NoteEditActivity extends Activity implements OnClickListener,
mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date);
mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon);
mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date);
mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color);
mNoteHeaderHolder.ibSetBgColor = (ImageButton) findViewById(R.id.btn_set_bg_color);
mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this);
mNoteEditor = (EditText) findViewById(R.id.note_edit_view);
mNoteEditor = (NoteEditText) findViewById(R.id.note_edit_view);
mNoteEditorPanel = findViewById(R.id.sv_note_edit);
mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector);
for (int id : sBgSelectorBtnsMap.keySet()) {
@ -460,7 +483,28 @@ public class NoteEditActivity extends Activity implements OnClickListener,
iv.setOnClickListener(this);
}
// 初始化富文本按钮
mBtnBold = findViewById(R.id.btn_bold);
mBtnItalic = findViewById(R.id.btn_italic);
mBtnUnderline = findViewById(R.id.btn_underline);
mBtnTextColor = findViewById(R.id.btn_set_text_color);
mBtnBold.setOnClickListener(this);
mBtnItalic.setOnClickListener(this);
mBtnUnderline.setOnClickListener(this);
mBtnTextColor.setOnClickListener(this);
mFontSizeSelector = findViewById(R.id.font_size_selector);
mNoteTextColorSelector = findViewById(R.id.note_text_color_selector);
// 初始化字体颜色选择器的颜色选项
ImageView ivTextBlack = findViewById(R.id.iv_text_black);
ImageView ivTextWhite = findViewById(R.id.iv_text_white);
ImageView ivTextRed = findViewById(R.id.iv_text_red);
ImageView ivTextBlue = findViewById(R.id.iv_text_blue);
ivTextBlack.setOnClickListener(this);
ivTextWhite.setOnClickListener(this);
ivTextRed.setOnClickListener(this);
ivTextBlue.setOnClickListener(this);
for (int id : sFontSizeBtnsMap.keySet()) {
View view = findViewById(id);
view.setOnClickListener(this);
@ -476,6 +520,9 @@ public class NoteEditActivity extends Activity implements OnClickListener,
mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
}
mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);
// 将光标改为文本选中光标
mNoteEditor.setTextIsSelectable(true);
}
/**
@ -520,15 +567,39 @@ public class NoteEditActivity extends Activity implements OnClickListener,
public void onClick(View v) {
int id = v.getId();
if (id == R.id.btn_set_bg_color) {
// 显示背景颜色选择器,隐藏其他选择器
mNoteBgColorSelector.setVisibility(View.VISIBLE);
mNoteTextColorSelector.setVisibility(View.GONE);
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- View.VISIBLE);
View.VISIBLE);
} else if (id == R.id.btn_set_text_color) {
// 显示字体颜色选择器,隐藏其他选择器
mNoteTextColorSelector.setVisibility(View.VISIBLE);
mNoteBgColorSelector.setVisibility(View.GONE);
} else if (sBgSelectorBtnsMap.containsKey(id)) {
// 处理背景颜色选择
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.GONE);
mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id));
mNoteBgColorSelector.setVisibility(View.GONE);
} else if (id == R.id.iv_text_black) {
// 应用黑色字体
mNoteEditor.applyTextColor(0xFF000000);
mNoteTextColorSelector.setVisibility(View.GONE);
} else if (id == R.id.iv_text_white) {
// 应用白色字体
mNoteEditor.applyTextColor(0xFFFFFFFF);
mNoteTextColorSelector.setVisibility(View.GONE);
} else if (id == R.id.iv_text_red) {
// 应用红色字体
mNoteEditor.applyTextColor(0xFFFF0000);
mNoteTextColorSelector.setVisibility(View.GONE);
} else if (id == R.id.iv_text_blue) {
// 应用蓝色字体
mNoteEditor.applyTextColor(0xFF0000FF);
mNoteTextColorSelector.setVisibility(View.GONE);
} else if (sFontSizeBtnsMap.containsKey(id)) {
// 处理字体大小选择
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE);
mFontSizeId = sFontSizeBtnsMap.get(id);
mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit();
@ -541,6 +612,15 @@ public class NoteEditActivity extends Activity implements OnClickListener,
TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
}
mFontSizeSelector.setVisibility(View.GONE);
} else if (id == R.id.btn_bold) {
// 应用粗体
mNoteEditor.applyBold();
} else if (id == R.id.btn_italic) {
// 应用斜体
mNoteEditor.applyItalic();
} else if (id == R.id.btn_underline) {
// 应用下划线
mNoteEditor.applyUnderline();
}
}
@ -569,6 +649,9 @@ public class NoteEditActivity extends Activity implements OnClickListener,
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {
mNoteBgColorSelector.setVisibility(View.GONE);
return true;
} else if (mNoteTextColorSelector.getVisibility() == View.VISIBLE) {
mNoteTextColorSelector.setVisibility(View.GONE);
return true;
} else if (mFontSizeSelector.getVisibility() == View.VISIBLE) {
mFontSizeSelector.setVisibility(View.GONE);
return true;
@ -1053,11 +1136,11 @@ public class NoteEditActivity extends Activity implements OnClickListener,
* <p>
*
* </p>
* @param fullText
* @param fullText CharSequence
* @param userQuery
* @return Spannable
*/
private Spannable getHighlightQueryResult(String fullText, String userQuery) {
private Spannable getHighlightQueryResult(CharSequence fullText, String userQuery) {
SpannableString spannable = new SpannableString(fullText == null ? "" : fullText);
if (!TextUtils.isEmpty(userQuery)) {
mPattern = Pattern.compile(userQuery);
@ -1191,7 +1274,8 @@ public class NoteEditActivity extends Activity implements OnClickListener,
}
mWorkingNote.setWorkingText(sb.toString());
} else {
mWorkingNote.setWorkingText(mNoteEditor.getText().toString());
// 保留富文本格式,将 Spanned 转换为 HTML 字符串保存
mWorkingNote.setWorkingText(Html.toHtml(mNoteEditor.getText()));
}
return hasChecked;
}

@ -20,9 +20,15 @@ import android.content.Context;
import android.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.StyleSpan;
import android.text.style.URLSpan;
import android.text.style.UnderlineSpan;
import android.text.style.ForegroundColorSpan;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
@ -316,4 +322,108 @@ public class NoteEditText extends EditText {
}
super.onCreateContextMenu(menu);
}
/**
*
*/
public void applyBold() {
applyStyle(Typeface.BOLD);
}
/**
*
*/
public void applyItalic() {
applyStyle(Typeface.ITALIC);
}
/**
* 线
*/
public void applyUnderline() {
SpannableStringBuilder ssb = new SpannableStringBuilder(getText());
int start = getSelectionStart();
int end = getSelectionEnd();
if (start != end) {
// 检查是否已存在下划线样式
UnderlineSpan[] existingUnderlines = ssb.getSpans(start, end, UnderlineSpan.class);
if (existingUnderlines.length > 0) {
// 移除下划线
for (UnderlineSpan span : existingUnderlines) {
ssb.removeSpan(span);
}
} else {
// 添加下划线
ssb.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
}
setText(ssb);
setSelection(start, end);
}
}
/**
*
* @param color
*/
public void applyTextColor(int color) {
SpannableStringBuilder ssb = new SpannableStringBuilder(getText());
int start = getSelectionStart();
int end = getSelectionEnd();
if (start != end) {
// 移除已有的文字颜色
ForegroundColorSpan[] existingSpans = ssb.getSpans(start, end, ForegroundColorSpan.class);
for (ForegroundColorSpan span : existingSpans) {
ssb.removeSpan(span);
}
// 添加新的文字颜色
ssb.setSpan(new ForegroundColorSpan(color), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
setText(ssb);
setSelection(start, end);
}
}
/**
*
*/
private void applyStyle(int style) {
SpannableStringBuilder ssb = new SpannableStringBuilder(getText());
int start = getSelectionStart();
int end = getSelectionEnd();
if (start != end) {
// 获取当前选中文本的所有样式
StyleSpan[] existingSpans = ssb.getSpans(start, end, StyleSpan.class);
// 计算当前选中文本的总样式
int currentStyle = Typeface.NORMAL;
if (existingSpans.length > 0) {
// 如果有样式,取第一个样式(假设所有样式一致)
currentStyle = existingSpans[0].getStyle();
}
// 计算新的字体样式
int newStyle;
if ((currentStyle & style) != 0) {
// 移除该样式
newStyle = currentStyle & ~style;
} else {
// 添加该样式
newStyle = currentStyle | style;
}
// 移除旧的样式
for (StyleSpan span : existingSpans) {
ssb.removeSpan(span);
}
// 添加新的样式
ssb.setSpan(new StyleSpan(newStyle), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
setText(ssb);
setSelection(start, end);
}
}
}

@ -269,6 +269,7 @@ public class NoteItemData {
mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN);
mParentId = cursor.getLong(PARENT_ID_COLUMN);
mSnippet = cursor.getString(SNIPPET_COLUMN);
// 先替换标签保留HTML格式以便在列表中显示富文本
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace(
NoteEditActivity.TAG_UNCHECKED, "");
mType = cursor.getInt(TYPE_COLUMN);

@ -289,15 +289,29 @@ public class NotesListAdapter extends CursorAdapter {
*/
private void calcNotesCount() {
mNotesCount = 0;
for (int i = 0; i < getCount(); i++) {
Cursor c = (Cursor) getItem(i);
if (c != null) {
if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) {
mNotesCount++;
Cursor cursor = getCursor();
if (cursor != null && !cursor.isClosed()) {
// 使用 try-finally 块确保 cursor 位置正确恢复
int originalPosition = cursor.getPosition();
try {
if (cursor.moveToFirst()) {
do {
try {
if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) {
mNotesCount++;
}
} catch (Exception e) {
// 捕获可能的异常,避免崩溃
Log.e(TAG, "Error calculating note count: " + e.getMessage());
break;
}
} while (cursor.moveToNext());
}
} finally {
// 恢复 cursor 到原始位置
if (!cursor.isClosed()) {
cursor.moveToPosition(originalPosition);
}
} else {
Log.e(TAG, "Invalid cursor");
return;
}
}
}

@ -17,6 +17,7 @@
package net.micode.notes.ui;
import android.content.Context;
import android.text.Html;
import android.text.format.DateUtils;
import android.view.View;
import android.widget.CheckBox;
@ -103,7 +104,7 @@ public class NotesListItem extends LinearLayout {
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName());
mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem);
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
mTitle.setText(Html.fromHtml(DataUtils.getFormattedSnippet(data.getSnippet())));
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
@ -120,7 +121,7 @@ public class NotesListItem extends LinearLayout {
data.getNotesCount()));
mAlert.setVisibility(View.GONE);
} else {
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
mTitle.setText(Html.fromHtml(DataUtils.getFormattedSnippet(data.getSnippet())));
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);

@ -57,11 +57,81 @@
android:layout_marginRight="8dip"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
<!-- 富文本编辑按钮 -->
<Button
android:id="@+id/btn_bold"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginRight="4dip"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:text="B"
android:textSize="16sp"
android:textStyle="bold"
android:padding="0dp"
android:minWidth="0dp"
android:minHeight="0dp"
android:clickable="true"
android:focusable="true" />
<Button
android:id="@+id/btn_italic"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginRight="4dip"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:text="I"
android:textSize="16sp"
android:textStyle="italic"
android:padding="0dp"
android:minWidth="0dp"
android:minHeight="0dp"
android:clickable="true"
android:focusable="true" />
<Button
android:id="@+id/btn_underline"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginRight="4dip"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:text="U"
android:textSize="16sp"
android:textStyle="normal"
android:padding="0dp"
android:minWidth="0dp"
android:minHeight="0dp"
android:clickable="true"
android:focusable="true" />
<!-- 字体颜色选择按钮 -->
<Button
android:id="@+id/btn_set_text_color"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginRight="8dip"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:text="A"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="#FF0000"
android:padding="0dp"
android:minWidth="0dp"
android:minHeight="0dp"
android:clickable="true"
android:focusable="true" />
<ImageButton
android:id="@+id/btn_set_bg_color"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/bg_btn_set_color" />
android:background="@drawable/bg_btn_set_color"
android:clickable="true"
android:focusable="true" />
</LinearLayout>
<LinearLayout
@ -118,12 +188,7 @@
</LinearLayout>
</LinearLayout>
<ImageView
android:id="@+id/btn_set_bg_color"
android:layout_height="43dip"
android:layout_width="wrap_content"
android:background="@drawable/bg_color_btn_mask"
android:layout_gravity="top|right" />
<LinearLayout
android:id="@+id/note_bg_color_selector"
@ -239,6 +304,113 @@
</FrameLayout>
</LinearLayout>
<!-- 字体颜色选择器 -->
<LinearLayout
android:id="@+id/note_text_color_selector"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:background="@drawable/note_edit_color_selector_panel"
android:layout_marginTop="30dip"
android:layout_marginRight="8dip"
android:layout_gravity="top|right"
android:visibility="gone">
<FrameLayout
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_text_black"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000"
android:clickable="true"
android:focusable="true" />
<ImageView
android:id="@+id/iv_text_black_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginRight="5dip"
android:focusable="false"
android:visibility="gone"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_text_white"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
android:clickable="true"
android:focusable="true" />
<ImageView
android:id="@+id/iv_text_white_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginRight="3dip"
android:focusable="false"
android:visibility="gone"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_text_red"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FF0000"
android:clickable="true"
android:focusable="true" />
<ImageView
android:id="@+id/iv_text_red_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginRight="2dip"
android:focusable="false"
android:visibility="gone"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_text_blue"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0000FF"
android:clickable="true"
android:focusable="true" />
<ImageView
android:id="@+id/iv_text_blue_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
android:id="@+id/font_size_selector"
android:layout_width="fill_parent"

Loading…
Cancel
Save