From 97d4a685bac5eec37f92965826264d98b6cc00a3 Mon Sep 17 00:00:00 2001 From: chroe <454604461@qq.com> Date: Sun, 25 Jan 2026 19:08:48 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=AF=8C=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E7=BC=96=E8=BE=91=E5=92=8C=E6=A0=87=E7=AD=BE=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/net/micode/notes/data/Notes.java | 6 + .../notes/data/NotesDatabaseHelper.java | 21 +- .../net/micode/notes/ui/NoteEditActivity.java | 247 +++++++++++++-- .../net/micode/notes/ui/NoteEditText.java | 195 ++++++++++++ .../net/micode/notes/ui/NoteItemData.java | 18 ++ .../micode/notes/ui/NotesListActivity.java | 281 ++++++++++++++++-- .../net/micode/notes/ui/NotesListItem.java | 26 +- src/main/res/drawable/ic_format_bold.xml | 3 + src/main/res/drawable/ic_format_clear.xml | 9 + .../res/drawable/ic_format_color_fill.xml | 4 + .../res/drawable/ic_format_color_text.xml | 4 + src/main/res/drawable/ic_format_italic.xml | 3 + .../res/drawable/ic_format_underlined.xml | 3 + src/main/res/layout/note_edit.xml | 69 +++++ src/main/res/layout/note_list.xml | 11 + src/main/res/menu/note_list_options.xml | 6 + src/main/res/values/strings.xml | 6 + 17 files changed, 860 insertions(+), 52 deletions(-) create mode 100644 src/main/res/drawable/ic_format_bold.xml create mode 100644 src/main/res/drawable/ic_format_clear.xml create mode 100644 src/main/res/drawable/ic_format_color_fill.xml create mode 100644 src/main/res/drawable/ic_format_color_text.xml create mode 100644 src/main/res/drawable/ic_format_italic.xml create mode 100644 src/main/res/drawable/ic_format_underlined.xml diff --git a/src/main/java/net/micode/notes/data/Notes.java b/src/main/java/net/micode/notes/data/Notes.java index 901fd1f..2475b1a 100644 --- a/src/main/java/net/micode/notes/data/Notes.java +++ b/src/main/java/net/micode/notes/data/Notes.java @@ -259,6 +259,12 @@ public class Notes { *
类型 : INTEGER
*/ public static final int TITLE_MAX_LENGTH = 50; + + /** + * 便签标签 + *类型 : TEXT
+ */ + public static final String TAGS = "tags"; } /** diff --git a/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java b/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java index 7600a45..0d6067d 100644 --- a/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java +++ b/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java @@ -36,7 +36,7 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { private static final String DB_NAME = "note.db"; // 数据库版本号 - private static final int DB_VERSION = 8; + private static final int DB_VERSION = 9; // 数据库表名定义 public interface TABLE { @@ -94,7 +94,8 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { NoteColumns.IS_PINNED + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.PIN_PRIORITY + " INTEGER NOT NULL DEFAULT 0," + NoteColumns.IS_LOCKED + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.LOCK_PASSWORD + " TEXT NOT NULL DEFAULT ''" + + NoteColumns.LOCK_PASSWORD + " TEXT NOT NULL DEFAULT ''," + + NoteColumns.TAGS + " TEXT NOT NULL DEFAULT ''" + ")"; // 创建数据表的SQL语句 @@ -436,6 +437,11 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { upgradeToV8(db); oldVersion++; } + + if (oldVersion == 8) { + upgradeToV9(db); + oldVersion++; + } if (reCreateTriggers) { reCreateNoteTableTriggers(db); @@ -546,4 +552,15 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.TITLE + " TEXT NOT NULL DEFAULT ''"); } + + /** + * 将数据库从v8升级到v9 + * 此版本升级添加了标签字段,用于支持便签标签功能 + * @param db SQLite数据库实例 + */ + private void upgradeToV9(SQLiteDatabase db) { + // 为笔记表添加标签字段 + db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.TAGS + + " TEXT NOT NULL DEFAULT ''"); + } } diff --git a/src/main/java/net/micode/notes/ui/NoteEditActivity.java b/src/main/java/net/micode/notes/ui/NoteEditActivity.java index 402f220..97fa85e 100644 --- a/src/main/java/net/micode/notes/ui/NoteEditActivity.java +++ b/src/main/java/net/micode/notes/ui/NoteEditActivity.java @@ -27,12 +27,17 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; +import android.text.Html; import android.text.Spannable; import android.text.SpannableString; import android.text.SpannableStringBuilder; @@ -120,6 +125,11 @@ public class NoteEditActivity extends Activity implements OnClickListener, public View titleArea; // 标题区域 public TextView tvWordCountLabel; // 字数统计标签 public TextView tvWordCount; // 字数统计数字 + public ImageButton ibBold; // 加粗按钮 + public ImageButton ibItalic; // 斜体按钮 + public ImageButton ibUnderline; // 下划线按钮 + public ImageButton ibHighlight; // 高亮按钮 + public ImageButton ibTextColor; // 文本颜色按钮 } /** @@ -174,7 +184,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, private View mHeadViewPanel; // 头部视图面板 private View mNoteBgColorSelector; // 背景颜色选择器 private View mFontSizeSelector; // 字体大小选择器 - private EditText mNoteEditor; // 便签编辑器 + private NoteEditText mNoteEditor; // 便签编辑器 private View mNoteEditorPanel; // 编辑器面板 private WorkingNote mWorkingNote; // 当前工作便签 private SharedPreferences mSharedPrefs; // 共享偏好设置 @@ -402,7 +412,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, switchToListMode(mWorkingNote.getContent()); } else { // 普通文本模式,显示高亮搜索结果 - mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); + String content = mWorkingNote.getContent(); + + // 直接使用getHighlightQueryResult方法获取处理后的Spannable对象 + Spannable spannedContent = getHighlightQueryResult(content, mUserQuery); + mNoteEditor.setText(spannedContent); + // 将光标定位到文本末尾 mNoteEditor.setSelection(mNoteEditor.getText().length()); } @@ -598,9 +613,21 @@ public class NoteEditActivity extends Activity implements OnClickListener, mNoteHeaderHolder.titleArea = findViewById(R.id.note_title_area); mNoteHeaderHolder.tvWordCountLabel = (TextView) findViewById(R.id.tv_word_count_label); mNoteHeaderHolder.tvWordCount = (TextView) findViewById(R.id.tv_word_count); + + // 初始化富文本工具栏按钮 + mNoteHeaderHolder.ibBold = (ImageButton) findViewById(R.id.btn_bold); + mNoteHeaderHolder.ibBold.setOnClickListener(this); + mNoteHeaderHolder.ibItalic = (ImageButton) findViewById(R.id.btn_italic); + mNoteHeaderHolder.ibItalic.setOnClickListener(this); + mNoteHeaderHolder.ibUnderline = (ImageButton) findViewById(R.id.btn_underline); + mNoteHeaderHolder.ibUnderline.setOnClickListener(this); + mNoteHeaderHolder.ibHighlight = (ImageButton) findViewById(R.id.btn_highlight); + mNoteHeaderHolder.ibHighlight.setOnClickListener(this); + mNoteHeaderHolder.ibTextColor = (ImageButton) findViewById(R.id.btn_text_color); + mNoteHeaderHolder.ibTextColor.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); // 初始化背景颜色选择器 @@ -718,7 +745,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, /** * 点击事件处理 *- * 处理UI组件的点击事件,包括设置背景色、选择背景色、选择字体大小 + * 处理UI组件的点击事件,包括设置背景色、选择背景色、选择字体大小、富文本格式化等 *
* * @param v 被点击的视图 @@ -738,7 +765,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, } else if (id == R.id.btn_set_bg_color) { mNoteBgColorSelector.setVisibility(View.VISIBLE); findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( - - View.VISIBLE); + View.VISIBLE); } else if (sBgSelectorBtnsMap.containsKey(id)) { findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( View.GONE); @@ -759,6 +786,21 @@ public class NoteEditActivity extends Activity implements OnClickListener, mFontSizeSelector.setVisibility(View.GONE); } else if (id == R.id.add_img_btn) { insertImage(); + } else if (id == R.id.btn_bold) { + // 处理加粗按钮点击 + mNoteEditor.toggleBold(); + } else if (id == R.id.btn_italic) { + // 处理斜体按钮点击 + mNoteEditor.toggleItalic(); + } else if (id == R.id.btn_underline) { + // 处理下划线按钮点击 + mNoteEditor.toggleUnderline(); + } else if (id == R.id.btn_highlight) { + // 处理高亮按钮点击 + showColorPickerDialog(true); + } else if (id == R.id.btn_text_color) { + // 处理文本颜色按钮点击,显示颜色选择对话框 + showColorPickerDialog(false); } } @@ -1160,21 +1202,55 @@ public class NoteEditActivity extends Activity implements OnClickListener, } private Spannable getHighlightQueryResult(String fullText, String userQuery) { - SpannableStringBuilder builder = new SpannableStringBuilder(fullText == null ? "" : fullText); + // 确保fullText不为null + if (fullText == null) { + return new SpannableString(""); + } + + // 先将纯文本转换为SpannableString + SpannableStringBuilder builder = new SpannableStringBuilder(fullText); + + // 只处理HTML格式的内容,避免重复处理 + if (fullText.contains("<") && fullText.contains(">")) { + try { + // 如果是HTML格式,先转换为Spanned对象 + Spanned spannedText = Html.fromHtml(fullText); + builder = new SpannableStringBuilder(spannedText); + } catch (Exception e) { + Log.e(TAG, "Error parsing HTML: " + e.getMessage()); + // 如果HTML解析失败,使用纯文本 + builder = new SpannableStringBuilder(fullText); + } + } + + // 处理搜索高亮 if (!TextUtils.isEmpty(userQuery)) { - mPattern = Pattern.compile(userQuery); - Matcher m = mPattern.matcher(fullText); - int start = 0; - while (m.find(start)) { - builder.setSpan( - new BackgroundColorSpan(this.getResources().getColor( - R.color.user_query_highlight)), m.start(), m.end(), - Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - start = m.end(); + try { + mPattern = Pattern.compile(userQuery, Pattern.CASE_INSENSITIVE); + Matcher m = mPattern.matcher(builder.toString()); + int start = 0; + while (m.find(start)) { + builder.setSpan( + new BackgroundColorSpan(this.getResources().getColor( + R.color.user_query_highlight)), m.start(), m.end(), + Spannable.SPAN_INCLUSIVE_EXCLUSIVE); + start = m.end(); + } + } catch (Exception e) { + Log.e(TAG, "Error highlighting search query: " + e.getMessage()); + } + } + + // 只在有图片标记时才处理图片转换 + if (fullText.contains("[IMAGE:")) { + try { + // 使用统一的图片处理逻辑 + processImageConversion(builder, builder.toString()); + } catch (Exception e) { + Log.e(TAG, "Error processing images: " + e.getMessage()); } } - // 使用统一的图片处理逻辑 - processImageConversion(builder, builder.toString()); + return builder; } @@ -1203,9 +1279,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); } + // 对于checklist模式,将HTML转换为纯文本 + String plainText = Html.fromHtml(item).toString(); + edit.setOnTextViewChangeListener(this); edit.setIndex(index); - edit.setText(getHighlightQueryResult(item, mUserQuery)); + edit.setText(getHighlightQueryResult(plainText, mUserQuery)); return view; } @@ -1223,12 +1302,15 @@ public class NoteEditActivity extends Activity implements OnClickListener, public void onCheckListModeChanged(int oldMode, int newMode) { if (newMode == TextNote.MODE_CHECK_LIST) { - switchToListMode(mNoteEditor.getText().toString()); + // 从富文本模式切换到checklist模式,将HTML转换为纯文本 + String plainText = Html.fromHtml(mNoteEditor.getText().toString()).toString(); + switchToListMode(plainText); } else { if (!getWorkingText()) { mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ", "")); } + // 从checklist模式切换回富文本模式,将纯文本转换为HTML mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); mEditTextList.setVisibility(View.GONE); mNoteEditor.setVisibility(View.VISIBLE); @@ -1253,7 +1335,26 @@ public class NoteEditActivity extends Activity implements OnClickListener, } mWorkingNote.setWorkingText(sb.toString()); } else { - mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); + // 处理富文本内容,特殊处理ImageSpan + Editable editable = mNoteEditor.getEditableText(); + if (editable != null && editable instanceof Spanned) { + Spanned spanned = (Spanned) editable; + + // 检查是否包含ImageSpan + ImageSpan[] imageSpans = spanned.getSpans(0, spanned.length(), ImageSpan.class); + if (imageSpans.length > 0) { + // 对于包含图片的内容,使用原始文本格式存储,保留[IMAGE:path]标记 + mWorkingNote.setWorkingText(spanned.toString()); + } else { + // 对于不包含图片的内容,转换为HTML格式存储 + String htmlContent = Html.toHtml(spanned); + mWorkingNote.setWorkingText(htmlContent); + } + } else { + // 对于普通文本,转换为HTML格式存储 + String htmlContent = Html.toHtml(mNoteEditor.getText()); + mWorkingNote.setWorkingText(htmlContent); + } } updateWordCount(); return hasChecked; @@ -1272,7 +1373,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, } content = sb.toString(); } else { - content = mNoteEditor.getText().toString(); + // 对于富文本模式,获取纯文本内容进行字数统计 + Spanned spannedContent = mNoteEditor.getText(); + content = spannedContent.toString(); } // 过滤掉所有 [IMAGE:图片路径] 格式的图片标记,只统计纯文本字符数 @@ -1487,6 +1590,108 @@ public class NoteEditActivity extends Activity implements OnClickListener, } openImagePicker(); } + + /** + * 显示颜色选择对话框 + * @param isHighlight 是否为高亮模式,true为高亮,false为文本颜色 + */ + private void showColorPickerDialog(final boolean isHighlight) { + // 定义常用颜色数组 + final int[] colors = new int[] { + Color.BLACK, Color.RED, Color.BLUE, Color.GREEN, Color.YELLOW, + Color.CYAN, Color.MAGENTA, Color.GRAY, Color.DKGRAY, Color.LTGRAY, + Color.WHITE, Color.rgb(255, 165, 0), // Orange + Color.rgb(128, 0, 128), // Purple + Color.rgb(0, 128, 128), // Teal + Color.rgb(255, 192, 203) // Pink + }; + + // 创建颜色选择器对话框 + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(isHighlight ? R.string.menu_highlight : R.string.menu_text_color); + + // 创建颜色选择网格布局 + LinearLayout colorPickerLayout = new LinearLayout(this); + colorPickerLayout.setOrientation(LinearLayout.VERTICAL); + colorPickerLayout.setPadding(20, 20, 20, 20); + + // 创建3行5列的颜色选择网格 + int columns = 5; + int rows = (int) Math.ceil((double) colors.length / columns); + + for (int i = 0; i < rows; i++) { + LinearLayout rowLayout = new LinearLayout(this); + rowLayout.setOrientation(LinearLayout.HORIZONTAL); + rowLayout.setWeightSum(columns); + + for (int j = 0; j < columns; j++) { + final int index = i * columns + j; + if (index < colors.length) { + ImageView colorButton = new ImageView(this); + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + 0, 60, 1); + params.setMargins(5, 5, 5, 5); + colorButton.setLayoutParams(params); + + // 创建带边框的颜色选择按钮 + // 内层是颜色填充,外层是黑色边框 + GradientDrawable colorDrawable = new GradientDrawable(); + colorDrawable.setColor(colors[index]); + + GradientDrawable borderDrawable = new GradientDrawable(); + borderDrawable.setColor(Color.TRANSPARENT); + borderDrawable.setStroke(2, Color.BLACK); + borderDrawable.setShape(GradientDrawable.RECTANGLE); + + // 创建LayerDrawable来叠加两个Drawable + Drawable[] layers = {borderDrawable, colorDrawable}; + LayerDrawable layerDrawable = new LayerDrawable(layers); + + // 设置内边距,留出边框空间 + int padding = 2; + layerDrawable.setLayerInset(1, padding, padding, padding, padding); + + colorButton.setBackground(layerDrawable); + colorButton.setScaleType(ImageView.ScaleType.CENTER); + colorButton.setClickable(true); + colorButton.setFocusable(true); + + // 添加点击事件 + colorButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + // 应用选择的颜色 + if (isHighlight) { + // 高亮模式 + mNoteEditor.toggleHighlight(colors[index]); + } else { + // 文本颜色模式 + mNoteEditor.toggleTextColor(colors[index]); + } + } + }); + + rowLayout.addView(colorButton); + } + } + + colorPickerLayout.addView(rowLayout); + } + + builder.setView(colorPickerLayout); + builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }); + + // 创建对话框并设置为不可取消,只能通过取消按钮关闭 + AlertDialog dialog = builder.create(); + dialog.setCancelable(false); + dialog.setCanceledOnTouchOutside(false); + dialog.show(); + } private void openImagePicker() { Intent intent = new Intent(Intent.ACTION_GET_CONTENT); diff --git a/src/main/java/net/micode/notes/ui/NoteEditText.java b/src/main/java/net/micode/notes/ui/NoteEditText.java index 610623c..abc88cf 100644 --- a/src/main/java/net/micode/notes/ui/NoteEditText.java +++ b/src/main/java/net/micode/notes/ui/NoteEditText.java @@ -17,12 +17,19 @@ package net.micode.notes.ui; import android.content.Context; +import android.graphics.Color; import android.graphics.Rect; import android.text.Layout; import android.text.Selection; +import android.text.Spannable; +import android.text.SpannableString; import android.text.Spanned; import android.text.TextUtils; +import android.text.style.BackgroundColorSpan; +import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; import android.text.style.URLSpan; +import android.text.style.UnderlineSpan; import android.util.AttributeSet; import android.util.Log; import android.view.ContextMenu; @@ -303,4 +310,192 @@ public class NoteEditText extends EditText { } super.onCreateContextMenu(menu); } + + /** + * 切换文本的加粗格式 + */ + public void toggleBold() { + applyStyle(android.graphics.Typeface.BOLD); + } + + /** + * 切换文本的斜体格式 + */ + public void toggleItalic() { + applyStyle(android.graphics.Typeface.ITALIC); + } + + /** + * 切换文本的下划线格式 + */ + public void toggleUnderline() { + int start = getSelectionStart(); + int end = getSelectionEnd(); + + if (start == end) { + return; + } + + Spannable str = getText(); + UnderlineSpan[] spans = str.getSpans(start, end, UnderlineSpan.class); + + if (spans.length > 0) { + // 如果已经有下划线,移除 + for (UnderlineSpan span : spans) { + str.removeSpan(span); + } + } else { + // 如果没有下划线,添加 + str.setSpan(new UnderlineSpan(), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); + } + + setText(str); + setSelection(end); + } + + /** + * 切换文本的高亮格式 + */ + public void toggleHighlight() { + toggleHighlight(Color.YELLOW); + } + + /** + * 切换文本的高亮格式 + * @param color 高亮颜色值 + */ + public void toggleHighlight(int color) { + int start = getSelectionStart(); + int end = getSelectionEnd(); + + if (start == end) { + return; + } + + Spannable str = getText(); + BackgroundColorSpan[] spans = str.getSpans(start, end, BackgroundColorSpan.class); + + // 检查是否已经应用了相同颜色的高亮 + boolean hasSameColor = false; + for (BackgroundColorSpan span : spans) { + if (span.getBackgroundColor() == color) { + hasSameColor = true; + break; + } + } + + // 移除所有高亮 + for (BackgroundColorSpan span : spans) { + str.removeSpan(span); + } + + // 如果没有相同颜色的高亮,则添加新颜色的高亮 + if (!hasSameColor) { + str.setSpan(new BackgroundColorSpan(color), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); + } + } + + /** + * 切换文本颜色 + * @param color 文本颜色值 + */ + public void toggleTextColor(int color) { + int start = getSelectionStart(); + int end = getSelectionEnd(); + + if (start == end) { + return; + } + + Spannable str = getText(); + ForegroundColorSpan[] spans = str.getSpans(start, end, ForegroundColorSpan.class); + + // 检查是否已经应用了相同颜色 + boolean hasSameColor = false; + for (ForegroundColorSpan span : spans) { + if (span.getForegroundColor() == color) { + hasSameColor = true; + break; + } + } + + // 移除所有文本颜色 + for (ForegroundColorSpan span : spans) { + str.removeSpan(span); + } + + // 如果没有相同颜色,则添加新颜色 + if (!hasSameColor) { + str.setSpan(new ForegroundColorSpan(color), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); + } + } + + /** + * 清除文本格式 + */ + public void clearFormatting() { + int start = getSelectionStart(); + int end = getSelectionEnd(); + + if (start == end) { + return; + } + + Spannable str = getText(); + + // 移除所有格式 + StyleSpan[] styleSpans = str.getSpans(start, end, StyleSpan.class); + for (StyleSpan span : styleSpans) { + str.removeSpan(span); + } + + UnderlineSpan[] underlineSpans = str.getSpans(start, end, UnderlineSpan.class); + for (UnderlineSpan span : underlineSpans) { + str.removeSpan(span); + } + + BackgroundColorSpan[] backgroundSpans = str.getSpans(start, end, BackgroundColorSpan.class); + for (BackgroundColorSpan span : backgroundSpans) { + str.removeSpan(span); + } + + ForegroundColorSpan[] foregroundSpans = str.getSpans(start, end, ForegroundColorSpan.class); + for (ForegroundColorSpan span : foregroundSpans) { + str.removeSpan(span); + } + + setText(str); + setSelection(end); + } + + /** + * 应用文本样式 + * @param style 样式常量 + */ + private void applyStyle(int style) { + int start = getSelectionStart(); + int end = getSelectionEnd(); + + if (start == end) { + return; + } + + Spannable str = getText(); + StyleSpan[] spans = str.getSpans(start, end, StyleSpan.class); + + boolean hasStyle = false; + for (StyleSpan span : spans) { + if (span.getStyle() == style) { + str.removeSpan(span); + hasStyle = true; + } + } + + if (!hasStyle) { + str.setSpan(new StyleSpan(style), start, end, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); + } + + setText(str); + setSelection(end); + } } diff --git a/src/main/java/net/micode/notes/ui/NoteItemData.java b/src/main/java/net/micode/notes/ui/NoteItemData.java index 7d2876f..0c6442b 100644 --- a/src/main/java/net/micode/notes/ui/NoteItemData.java +++ b/src/main/java/net/micode/notes/ui/NoteItemData.java @@ -50,6 +50,7 @@ public class NoteItemData { NoteColumns.WIDGET_TYPE, NoteColumns.IS_LOCKED, NoteColumns.TITLE, + NoteColumns.TAGS, }; /** @@ -116,6 +117,10 @@ public class NoteItemData { * 标题列索引 */ private static final int TITLE_COLUMN = 15; + /** + * 标签列索引 + */ + private static final int TAGS_COLUMN = 16; /** * 笔记ID @@ -181,6 +186,10 @@ public class NoteItemData { * 标题 */ private String mTitle; + /** + * 标签 + */ + private String mTags; /** * 联系人姓名 */ @@ -236,6 +245,7 @@ public class NoteItemData { mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); mIsLocked = (cursor.getInt(IS_LOCKED_COLUMN) > 0); mTitle = cursor.getString(TITLE_COLUMN); + mTags = cursor.getString(TAGS_COLUMN); mPhoneNumber = ""; // 如果是通话记录文件夹,获取电话号码和联系人姓名 @@ -497,4 +507,12 @@ public class NoteItemData { public static int getNoteType(Cursor cursor) { return cursor.getInt(TYPE_COLUMN); } + + /** + * 获取标签 + * @return 标签 + */ + public String getTags() { + return mTags; + } } diff --git a/src/main/java/net/micode/notes/ui/NotesListActivity.java b/src/main/java/net/micode/notes/ui/NotesListActivity.java index 7b221d3..7c7df77 100644 --- a/src/main/java/net/micode/notes/ui/NotesListActivity.java +++ b/src/main/java/net/micode/notes/ui/NotesListActivity.java @@ -291,6 +291,15 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt mSearchEditText = (EditText) findViewById(R.id.search_edit_text); mSearchClear = (ImageView) findViewById(R.id.search_clear); mSearchButton = (ImageView) findViewById(R.id.search_button); + ImageView mSearchTagsButton = (ImageView) findViewById(R.id.search_tags_button); + + // 设置标签选择按钮点击事件 + mSearchTagsButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showTagsSelectorDialog(); + } + }); // 设置搜索文本变化监听,恢复实时搜索功能 mSearchEditText.addTextChangedListener(new TextWatcher() { @@ -392,6 +401,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } pinMenu.setOnMenuItemClickListener(this); } + // 添加标签设置菜单项 + MenuItem tagsMenu = menu.findItem(R.id.tags); + if (tagsMenu != null) { + tagsMenu.setOnMenuItemClickListener(this); + } mActionMode = mode; mNotesListAdapter.setChoiceMode(true); mNotesListView.setLongClickable(false); @@ -483,6 +497,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt startQueryDestinationFolders(); } else if (item.getItemId() == R.id.pin) { togglePinStatus(); + } else if (item.getItemId() == R.id.tags) { + showTagsDialog(); } else { return false; } @@ -552,39 +568,93 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt }; + // 当前选中的标签,用于筛选 + private String mSelectedTag = ""; + private void startAsyncNotesListQuery() { String selection; String[] selectionArgs; + // 构建基础查询条件 + String baseSelection = ""; + ArrayList