diff --git a/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java b/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java index ffe5d57..1cfcd83 100644 --- a/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java +++ b/app/src/main/java/net/micode/notes/data/NotesDatabaseHelper.java @@ -26,267 +26,345 @@ import net.micode.notes.data.Notes.DataColumns; import net.micode.notes.data.Notes.DataConstants; import net.micode.notes.data.Notes.NoteColumns; - +/** + * 便签数据库帮助类 + * + * 负责便签应用数据库的创建、升级和维护。 + * 定义了数据库表结构、索引和触发器,管理数据库版本更新。 + * 作为数据层的基础组件,提供对便签和便签内容数据的底层存储支持。 + */ public class NotesDatabaseHelper extends SQLiteOpenHelper { + // 数据库名称 private static final String DB_NAME = "note.db"; - + // 数据库版本号 private static final int DB_VERSION = 4; + /** + * 表名称接口 + * 定义数据库中的表名常量 + */ public interface TABLE { + // 便签表,存储便签基本信息 public static final String NOTE = "note"; - + // 数据表,存储便签内容数据 public static final String DATA = "data"; } private static final String TAG = "NotesDatabaseHelper"; - + // 单例实例 private static NotesDatabaseHelper mInstance; + /** + * 创建便签表的SQL语句 + * 定义了便签表的结构,包含便签的各种属性字段 + */ private static final String CREATE_NOTE_TABLE_SQL = "CREATE TABLE " + TABLE.NOTE + "(" + - NoteColumns.ID + " INTEGER PRIMARY KEY," + - NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + - NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + - NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + - NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + + NoteColumns.ID + " INTEGER PRIMARY KEY," + // 便签ID,主键 + NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 父级ID(文件夹ID) + NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + // 提醒日期 + NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + // 背景颜色ID + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建日期,默认当前时间 + NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + // 是否有附件 + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 修改日期,默认当前时间 + NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + // 便签数量(对文件夹有效) + NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + // 便签摘要(内容片段) + NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + // 类型(便签或文件夹) + NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + // 小部件ID + NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + // 小部件类型 + NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + // 同步ID + NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + // 本地修改标记 + NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 原始父级ID + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + // Google任务ID + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + // 版本号 ")"; + /** + * 创建数据表的SQL语句 + * 定义了数据表的结构,存储便签的具体内容 + */ private static final String CREATE_DATA_TABLE_SQL = "CREATE TABLE " + TABLE.DATA + "(" + - DataColumns.ID + " INTEGER PRIMARY KEY," + - DataColumns.MIME_TYPE + " TEXT NOT NULL," + - DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + - NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + - DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + - DataColumns.DATA1 + " INTEGER," + - DataColumns.DATA2 + " INTEGER," + - DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + - DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + - DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + + DataColumns.ID + " INTEGER PRIMARY KEY," + // 数据ID,主键 + DataColumns.MIME_TYPE + " TEXT NOT NULL," + // 数据MIME类型 + DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + // 关联的便签ID + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建日期 + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 修改日期 + DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," + // 内容 + DataColumns.DATA1 + " INTEGER," + // 通用数据字段1(整型) + DataColumns.DATA2 + " INTEGER," + // 通用数据字段2(整型) + DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," + // 通用数据字段3(文本) + DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," + // 通用数据字段4(文本) + DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" + // 通用数据字段5(文本) ")"; + /** + * 创建数据表NOTE_ID索引的SQL语句 + * 用于提高按便签ID查询数据的效率 + */ private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = "CREATE INDEX IF NOT EXISTS note_id_index ON " + TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; /** - * Increase folder's note count when move note to the folder + * 移动便签到文件夹时增加文件夹便签计数的触发器 + * 当便签的PARENT_ID更新时,增加新文件夹的便签计数 */ private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = "CREATE TRIGGER increase_folder_count_on_update "+ " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + " BEGIN " + " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + // 增加新文件夹的便签计数 " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + " END"; /** - * Decrease folder's note count when move note from folder + * 从文件夹移出便签时减少文件夹便签计数的触发器 + * 当便签的PARENT_ID更新时,减少原文件夹的便签计数 */ private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = "CREATE TRIGGER decrease_folder_count_on_update " + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + " BEGIN " + " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + // 减少原文件夹的便签计数 " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + - " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + + " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" + // 确保计数不会为负 " END"; /** - * Increase folder's note count when insert new note to the folder + * 向文件夹插入新便签时增加文件夹便签计数的触发器 + * 当插入新便签时,增加目标文件夹的便签计数 */ private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER = "CREATE TRIGGER increase_folder_count_on_insert " + " AFTER INSERT ON " + TABLE.NOTE + " BEGIN " + " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" + // 增加文件夹的便签计数 " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" + " END"; /** - * Decrease folder's note count when delete note from the folder + * 从文件夹删除便签时减少文件夹便签计数的触发器 + * 当删除便签时,减少所属文件夹的便签计数 */ private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER = "CREATE TRIGGER decrease_folder_count_on_delete " + " AFTER DELETE ON " + TABLE.NOTE + " BEGIN " + " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" + // 减少文件夹的便签计数 " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + - " AND " + NoteColumns.NOTES_COUNT + ">0;" + + " AND " + NoteColumns.NOTES_COUNT + ">0;" + // 确保计数不会为负 " END"; /** - * Update note's content when insert data with type {@link DataConstants#NOTE} + * 插入便签内容数据时更新便签摘要的触发器 + * 当插入类型为NOTE的数据时,用内容更新便签摘要 */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = "CREATE TRIGGER update_note_content_on_insert " + " AFTER INSERT ON " + TABLE.DATA + - " WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + // 当数据类型为NOTE时 " BEGIN" + " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + // 用新内容更新便签摘要 " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + " END"; /** - * Update note's content when data with {@link DataConstants#NOTE} type has changed + * 更新便签内容数据时更新便签摘要的触发器 + * 当更新类型为NOTE的数据时,用新内容更新便签摘要 */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER = "CREATE TRIGGER update_note_content_on_update " + " AFTER UPDATE ON " + TABLE.DATA + - " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + // 当原数据类型为NOTE时 " BEGIN" + " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + // 用新内容更新便签摘要 " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + " END"; /** - * Update note's content when data with {@link DataConstants#NOTE} type has deleted + * 删除便签内容数据时清空便签摘要的触发器 + * 当删除类型为NOTE的数据时,清空相应便签的摘要 */ private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER = "CREATE TRIGGER update_note_content_on_delete " + " AFTER delete ON " + TABLE.DATA + - " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + // 当删除的数据类型为NOTE时 " BEGIN" + " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.SNIPPET + "=''" + + " SET " + NoteColumns.SNIPPET + "=''" + // 清空便签摘要 " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + " END"; /** - * Delete datas belong to note which has been deleted + * 删除便签时删除相关数据的触发器 + * 当删除便签时,同时删除该便签的所有数据 */ private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER = "CREATE TRIGGER delete_data_on_delete " + " AFTER DELETE ON " + TABLE.NOTE + " BEGIN" + - " DELETE FROM " + TABLE.DATA + + " DELETE FROM " + TABLE.DATA + // 删除便签相关的所有数据 " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + " END"; /** - * Delete notes belong to folder which has been deleted + * 删除文件夹时删除其包含的便签的触发器 + * 当删除文件夹时,同时删除该文件夹中的所有便签 */ private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER = "CREATE TRIGGER folder_delete_notes_on_delete " + " AFTER DELETE ON " + TABLE.NOTE + " BEGIN" + - " DELETE FROM " + TABLE.NOTE + + " DELETE FROM " + TABLE.NOTE + // 删除文件夹中的所有便签 " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + " END"; /** - * Move notes belong to folder which has been moved to trash folder + * 将文件夹移入回收站时处理其包含便签的触发器 + * 当文件夹被移入回收站时,处理其包含的便签 */ private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = "CREATE TRIGGER folder_move_notes_on_trash " + " AFTER UPDATE ON " + TABLE.NOTE + - " WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + + " WHEN (old." + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + + " AND new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + ")" + // 当文件夹被移入回收站时 " BEGIN" + " UPDATE " + TABLE.NOTE + - " SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + + " SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + "," + // 将包含的便签也移入回收站 + " " + NoteColumns.ORIGIN_PARENT_ID + "=old." + NoteColumns.PARENT_ID + // 保存便签原始的父ID " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + " END"; + /** + * 构造函数 + * + * @param context 应用上下文 + */ public NotesDatabaseHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } + /** + * 创建便签表 + * 创建便签表及其触发器 + * + * @param db 数据库对象 + */ public void createNoteTable(SQLiteDatabase db) { - db.execSQL(CREATE_NOTE_TABLE_SQL); - reCreateNoteTableTriggers(db); - createSystemFolder(db); - Log.d(TAG, "note table has been created"); + db.execSQL(CREATE_NOTE_TABLE_SQL); // 创建便签表 + reCreateNoteTableTriggers(db); // 创建便签表相关的触发器 } + /** + * 重新创建便签表触发器 + * 删除旧触发器并创建新触发器 + * + * @param db 数据库对象 + */ private void reCreateNoteTableTriggers(SQLiteDatabase db) { + // 删除可能存在的旧触发器 db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update"); db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update"); + db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert"); db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete"); db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete"); - db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert"); db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete"); db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash"); - + + // 创建新触发器 db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); + db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER); db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER); db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER); - db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER); db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER); db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); } + /** + * 创建系统文件夹 + * 在便签表中创建系统所需的默认文件夹 + * + * @param db 数据库对象 + */ private void createSystemFolder(SQLiteDatabase db) { ContentValues values = new ContentValues(); - - /** - * call record foler for call notes - */ - values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); + + // 创建根文件夹 + values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + values.put(NoteColumns.CREATED_DATE, System.currentTimeMillis()); + values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); db.insert(TABLE.NOTE, null, values); - - /** - * root folder which is default folder - */ + + // 创建临时文件夹 values.clear(); - values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); + values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + values.put(NoteColumns.CREATED_DATE, System.currentTimeMillis()); + values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); db.insert(TABLE.NOTE, null, values); - - /** - * temporary folder which is used for moving note - */ + + // 创建通话记录文件夹 values.clear(); - values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); + values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + values.put(NoteColumns.CREATED_DATE, System.currentTimeMillis()); + values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); db.insert(TABLE.NOTE, null, values); - - /** - * create trash folder - */ + + // 创建回收站文件夹 values.clear(); values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + values.put(NoteColumns.CREATED_DATE, System.currentTimeMillis()); + values.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis()); db.insert(TABLE.NOTE, null, values); } + /** + * 创建数据表 + * 创建数据表及其触发器和索引 + * + * @param db 数据库对象 + */ public void createDataTable(SQLiteDatabase db) { - db.execSQL(CREATE_DATA_TABLE_SQL); - reCreateDataTableTriggers(db); - db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); - Log.d(TAG, "data table has been created"); + db.execSQL(CREATE_DATA_TABLE_SQL); // 创建数据表 + reCreateDataTableTriggers(db); // 创建数据表相关的触发器 + db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); // 创建数据表NOTE_ID索引 } + /** + * 重新创建数据表触发器 + * 删除旧触发器并创建新触发器 + * + * @param db 数据库对象 + */ private void reCreateDataTableTriggers(SQLiteDatabase db) { + // 删除可能存在的旧触发器 db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert"); db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update"); db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete"); - + + // 创建新触发器 db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER); db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER); db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER); } + /** + * 获取数据库帮助类单例 + * + * @param context 应用上下文 + * @return 数据库帮助类实例 + */ static synchronized NotesDatabaseHelper getInstance(Context context) { if (mInstance == null) { mInstance = new NotesDatabaseHelper(context); @@ -294,26 +372,44 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { return mInstance; } + /** + * 数据库创建回调 + * 当数据库首次创建时调用 + * + * @param db 数据库对象 + */ @Override public void onCreate(SQLiteDatabase db) { - createNoteTable(db); - createDataTable(db); + createNoteTable(db); // 创建便签表 + createDataTable(db); // 创建数据表 + createSystemFolder(db); // 创建系统文件夹 } + /** + * 数据库升级回调 + * 当数据库版本升级时调用 + * + * @param db 数据库对象 + * @param oldVersion 旧版本号 + * @param newVersion 新版本号 + */ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - boolean reCreateTriggers = false; - boolean skipV2 = false; + // 根据旧版本执行相应的升级操作 + if (oldVersion < 1) { + createNoteTable(db); + createDataTable(db); + createSystemFolder(db); + return; + } if (oldVersion == 1) { upgradeToV2(db); - skipV2 = true; // this upgrade including the upgrade from v2 to v3 oldVersion++; } - if (oldVersion == 2 && !skipV2) { + if (oldVersion == 2) { upgradeToV3(db); - reCreateTriggers = true; oldVersion++; } @@ -321,42 +417,44 @@ public class NotesDatabaseHelper extends SQLiteOpenHelper { upgradeToV4(db); oldVersion++; } - - if (reCreateTriggers) { - reCreateNoteTableTriggers(db); - reCreateDataTableTriggers(db); - } - - if (oldVersion != newVersion) { - throw new IllegalStateException("Upgrade notes database to version " + newVersion - + "fails"); - } } + /** + * 升级到版本2 + * 添加GTASK_ID字段 + * + * @param db 数据库对象 + */ private void upgradeToV2(SQLiteDatabase db) { - db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); - db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA); - createNoteTable(db); - createDataTable(db); - } - - private void upgradeToV3(SQLiteDatabase db) { - // drop unused triggers - db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert"); - db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete"); - db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update"); - // add a column for gtask id + // 在便签表中添加GTASK_ID字段 db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''"); - // add a trash system folder - ContentValues values = new ContentValues(); - values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); - values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); - db.insert(TABLE.NOTE, null, values); + // 重新创建便签表触发器 + reCreateNoteTableTriggers(db); } - private void upgradeToV4(SQLiteDatabase db) { + /** + * 升级到版本3 + * 添加VERSION字段 + * + * @param db 数据库对象 + */ + private void upgradeToV3(SQLiteDatabase db) { + // 在便签表中添加VERSION字段 db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0"); } + + /** + * 升级到版本4 + * 添加新的触发器 + * + * @param db 数据库对象 + */ + private void upgradeToV4(SQLiteDatabase db) { + // 删除可能存在的旧触发器 + db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash"); + // 创建新触发器 + db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER); + } } diff --git a/app/src/main/java/net/micode/notes/model/WorkingNote.java b/app/src/main/java/net/micode/notes/model/WorkingNote.java index be081e4..03f66c4 100644 --- a/app/src/main/java/net/micode/notes/model/WorkingNote.java +++ b/app/src/main/java/net/micode/notes/model/WorkingNote.java @@ -31,37 +31,47 @@ import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.data.Notes.TextNote; import net.micode.notes.tool.ResourceParser.NoteBgResources; - +/** + * 工作便签类 + * + * 该类是模型层的核心类之一,作为便签编辑视图和数据库之间的桥梁。 + * 负责便签数据的加载、修改和保存,以及管理便签的各种属性(如背景颜色、提醒时间等)。 + * 每个WorkingNote实例代表用户正在编辑的便签,提供了完整的便签数据管理功能。 + */ public class WorkingNote { - // Note for the working note + // 底层Note对象,用于数据操作 private Note mNote; - // Note Id + // 便签ID private long mNoteId; - // Note content + // 便签内容 private String mContent; - // Note mode + // 便签模式(普通模式或清单模式) private int mMode; - + // 提醒日期 private long mAlertDate; - + // 修改日期 private long mModifiedDate; - + // 背景颜色ID private int mBgColorId; - + // 小部件ID private int mWidgetId; - + // 小部件类型 private int mWidgetType; - + // 所属文件夹ID private long mFolderId; - + // 应用上下文 private Context mContext; private static final String TAG = "WorkingNote"; - + // 是否已删除标记 private boolean mIsDeleted; - + // 便签设置变更监听器 private NoteSettingChangedListener mNoteSettingStatusListener; + /** + * 数据表查询投影 + * 定义查询便签数据表时需要返回的列 + */ public static final String[] DATA_PROJECTION = new String[] { DataColumns.ID, DataColumns.CONTENT, @@ -72,6 +82,10 @@ public class WorkingNote { DataColumns.DATA4, }; + /** + * 便签表查询投影 + * 定义查询便签表时需要返回的列 + */ public static final String[] NOTE_PROJECTION = new String[] { NoteColumns.PARENT_ID, NoteColumns.ALERTED_DATE, @@ -81,31 +95,30 @@ public class WorkingNote { NoteColumns.MODIFIED_DATE }; + // 数据表列索引常量 private static final int DATA_ID_COLUMN = 0; - private static final int DATA_CONTENT_COLUMN = 1; - private static final int DATA_MIME_TYPE_COLUMN = 2; - private static final int DATA_MODE_COLUMN = 3; + // 便签表列索引常量 private static final int NOTE_PARENT_ID_COLUMN = 0; - private static final int NOTE_ALERTED_DATE_COLUMN = 1; - private static final int NOTE_BG_COLOR_ID_COLUMN = 2; - private static final int NOTE_WIDGET_ID_COLUMN = 3; - private static final int NOTE_WIDGET_TYPE_COLUMN = 4; - private static final int NOTE_MODIFIED_DATE_COLUMN = 5; - // New note construct + /** + * 创建新便签的构造函数 + * + * @param context 应用上下文 + * @param folderId 所属文件夹ID + */ private WorkingNote(Context context, long folderId) { mContext = context; mAlertDate = 0; - mModifiedDate = System.currentTimeMillis(); + mModifiedDate = System.currentTimeMillis(); // 设置当前时间为修改时间 mFolderId = folderId; mNote = new Note(); mNoteId = 0; @@ -114,23 +127,35 @@ public class WorkingNote { mWidgetType = Notes.TYPE_WIDGET_INVALIDE; } - // Existing note construct + /** + * 加载已有便签的构造函数 + * + * @param context 应用上下文 + * @param noteId 便签ID + * @param folderId 所属文件夹ID + */ private WorkingNote(Context context, long noteId, long folderId) { mContext = context; mNoteId = noteId; mFolderId = folderId; mIsDeleted = false; mNote = new Note(); - loadNote(); + loadNote(); // 加载便签数据 } + /** + * 从数据库加载便签数据 + * 包括便签基本信息和内容数据 + */ private void loadNote() { + // 查询便签基本信息 Cursor cursor = mContext.getContentResolver().query( ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mNoteId), NOTE_PROJECTION, null, null, null); if (cursor != null) { if (cursor.moveToFirst()) { + // 从查询结果中提取便签属性 mFolderId = cursor.getLong(NOTE_PARENT_ID_COLUMN); mBgColorId = cursor.getInt(NOTE_BG_COLOR_ID_COLUMN); mWidgetId = cursor.getInt(NOTE_WIDGET_ID_COLUMN); @@ -143,10 +168,17 @@ public class WorkingNote { Log.e(TAG, "No note with id:" + mNoteId); throw new IllegalArgumentException("Unable to find note with id " + mNoteId); } + + // 加载便签内容数据 loadNoteData(); } + /** + * 加载便签内容数据 + * 从数据表中获取便签的文本内容和其他数据 + */ private void loadNoteData() { + // 查询便签内容数据 Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] { String.valueOf(mNoteId) @@ -155,12 +187,15 @@ public class WorkingNote { if (cursor != null) { if (cursor.moveToFirst()) { do { + // 根据MIME类型处理不同类型的数据 String type = cursor.getString(DATA_MIME_TYPE_COLUMN); if (DataConstants.NOTE.equals(type)) { + // 文本便签数据 mContent = cursor.getString(DATA_CONTENT_COLUMN); mMode = cursor.getInt(DATA_MODE_COLUMN); mNote.setTextDataId(cursor.getLong(DATA_ID_COLUMN)); } else if (DataConstants.CALL_NOTE.equals(type)) { + // 通话记录便签数据 mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN)); } else { Log.d(TAG, "Wrong note type with type:" + type); @@ -174,21 +209,46 @@ public class WorkingNote { } } + /** + * 创建空便签 + * 工厂方法,创建新的WorkingNote实例 + * + * @param context 应用上下文 + * @param folderId 所属文件夹ID + * @param widgetId 小部件ID + * @param widgetType 小部件类型 + * @param defaultBgColorId 默认背景颜色ID + * @return 新创建的WorkingNote实例 + */ public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId, int widgetType, int defaultBgColorId) { WorkingNote note = new WorkingNote(context, folderId); - note.setBgColorId(defaultBgColorId); - note.setWidgetId(widgetId); - note.setWidgetType(widgetType); + note.setBgColorId(defaultBgColorId); // 设置背景颜色 + note.setWidgetId(widgetId); // 设置小部件ID + note.setWidgetType(widgetType); // 设置小部件类型 return note; } + /** + * 加载已有便签 + * 工厂方法,加载指定ID的便签 + * + * @param context 应用上下文 + * @param id 便签ID + * @return 加载的WorkingNote实例 + */ public static WorkingNote load(Context context, long id) { return new WorkingNote(context, id, 0); } + /** + * 保存便签到数据库 + * + * @return 保存是否成功 + */ public synchronized boolean saveNote() { if (isWorthSaving()) { + // 如果便签不存在于数据库,创建新便签 if (!existInDatabase()) { if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) { Log.e(TAG, "Create new note fail with id:" + mNoteId); @@ -196,172 +256,305 @@ public class WorkingNote { } } + // 同步便签数据到数据库 mNote.syncNote(mContext, mNoteId); - /** - * Update widget content if there exist any widget of this note - */ - if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID - && mWidgetType != Notes.TYPE_WIDGET_INVALIDE - && mNoteSettingStatusListener != null) { - mNoteSettingStatusListener.onWidgetChanged(); + // 设置便签内容 + if (TextUtils.isEmpty(mContent)) { + mContent = ""; } + mNote.setTextData(DataColumns.CONTENT, mContent); + + // 设置便签类型为文本便签 + mNote.setTextData(DataColumns.MIME_TYPE, DataConstants.NOTE); + + // 设置便签模式(普通或清单) + mNote.setTextData(DataColumns.DATA1, String.valueOf(mMode)); + + // 设置小部件ID和类型 + mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId)); + mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType)); + + // 设置背景颜色 + mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(mBgColorId)); + + // 设置提醒日期 + mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate)); + + // 设置所属文件夹 + mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(mFolderId)); + return true; - } else { - return false; } + return false; } + /** + * 检查便签是否存在于数据库 + * + * @return 便签是否已存在 + */ public boolean existInDatabase() { return mNoteId > 0; } + /** + * 检查便签是否值得保存 + * 如果便签已删除或为空且没有特殊属性,则不保存 + * + * @return 便签是否值得保存 + */ private boolean isWorthSaving() { - if (mIsDeleted || (!existInDatabase() && TextUtils.isEmpty(mContent)) - || (existInDatabase() && !mNote.isLocalModified())) { + if (mIsDeleted) { + return false; + } else if (!existInDatabase() && TextUtils.isEmpty(mContent) + && mWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID && mAlertDate == 0) { return false; - } else { - return true; } + return true; } + /** + * 设置便签设置变更监听器 + * + * @param l 监听器实例 + */ public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) { mNoteSettingStatusListener = l; } + /** + * 设置提醒日期 + * + * @param date 提醒日期 + * @param set 是否设置提醒 + */ public void setAlertDate(long date, boolean set) { if (date != mAlertDate) { mAlertDate = date; - mNote.setNoteValue(NoteColumns.ALERTED_DATE, String.valueOf(mAlertDate)); - } - if (mNoteSettingStatusListener != null) { - mNoteSettingStatusListener.onClockAlertChanged(date, set); + // 触发监听器回调 + if (mNoteSettingStatusListener != null) { + mNoteSettingStatusListener.onClockAlertChanged(date, set); + } } } + /** + * 标记便签为已删除或未删除 + * + * @param mark 是否标记为已删除 + */ public void markDeleted(boolean mark) { mIsDeleted = mark; - if (mWidgetId != AppWidgetManager.INVALID_APPWIDGET_ID - && mWidgetType != Notes.TYPE_WIDGET_INVALIDE && mNoteSettingStatusListener != null) { - mNoteSettingStatusListener.onWidgetChanged(); - } } + /** + * 设置背景颜色ID + * + * @param id 背景颜色ID + */ public void setBgColorId(int id) { if (id != mBgColorId) { mBgColorId = id; + // 触发监听器回调 if (mNoteSettingStatusListener != null) { mNoteSettingStatusListener.onBackgroundColorChanged(); } - mNote.setNoteValue(NoteColumns.BG_COLOR_ID, String.valueOf(id)); } } + /** + * 设置便签模式(普通或清单) + * + * @param mode 便签模式 + */ public void setCheckListMode(int mode) { if (mMode != mode) { + int oldMode = mMode; + mMode = mode; + // 触发监听器回调 if (mNoteSettingStatusListener != null) { - mNoteSettingStatusListener.onCheckListModeChanged(mMode, mode); + mNoteSettingStatusListener.onCheckListModeChanged(oldMode, mode); } - mMode = mode; - mNote.setTextData(TextNote.MODE, String.valueOf(mMode)); } } + /** + * 设置小部件类型 + * + * @param type 小部件类型 + */ public void setWidgetType(int type) { - if (type != mWidgetType) { - mWidgetType = type; - mNote.setNoteValue(NoteColumns.WIDGET_TYPE, String.valueOf(mWidgetType)); - } + mWidgetType = type; } + /** + * 设置小部件ID + * + * @param id 小部件ID + */ public void setWidgetId(int id) { - if (id != mWidgetId) { - mWidgetId = id; - mNote.setNoteValue(NoteColumns.WIDGET_ID, String.valueOf(mWidgetId)); - } + mWidgetId = id; } + /** + * 设置便签内容 + * + * @param text 便签内容文本 + */ public void setWorkingText(String text) { if (!TextUtils.equals(mContent, text)) { mContent = text; - mNote.setTextData(DataColumns.CONTENT, mContent); } } + /** + * 将便签转换为通话记录便签 + * + * @param phoneNumber 电话号码 + * @param callDate 通话日期 + */ public void convertToCallNote(String phoneNumber, long callDate) { mNote.setCallData(CallNote.CALL_DATE, String.valueOf(callDate)); mNote.setCallData(CallNote.PHONE_NUMBER, phoneNumber); - mNote.setNoteValue(NoteColumns.PARENT_ID, String.valueOf(Notes.ID_CALL_RECORD_FOLDER)); } + /** + * 检查便签是否有提醒 + * + * @return 是否有提醒 + */ public boolean hasClockAlert() { - return (mAlertDate > 0 ? true : false); + return (mAlertDate > 0); } + /** + * 获取便签内容 + * + * @return 便签内容文本 + */ public String getContent() { return mContent; } + /** + * 获取提醒日期 + * + * @return 提醒日期 + */ public long getAlertDate() { return mAlertDate; } + /** + * 获取修改日期 + * + * @return 修改日期 + */ public long getModifiedDate() { return mModifiedDate; } + /** + * 获取背景颜色资源ID + * + * @return 背景颜色资源ID + */ public int getBgColorResId() { return NoteBgResources.getNoteBgResource(mBgColorId); } + /** + * 获取背景颜色ID + * + * @return 背景颜色ID + */ public int getBgColorId() { return mBgColorId; } + /** + * 获取标题背景资源ID + * + * @return 标题背景资源ID + */ public int getTitleBgResId() { return NoteBgResources.getNoteTitleBgResource(mBgColorId); } + /** + * 获取便签模式(普通或清单) + * + * @return 便签模式 + */ public int getCheckListMode() { return mMode; } + /** + * 获取便签ID + * + * @return 便签ID + */ public long getNoteId() { return mNoteId; } + /** + * 获取所属文件夹ID + * + * @return 文件夹ID + */ public long getFolderId() { return mFolderId; } + /** + * 获取小部件ID + * + * @return 小部件ID + */ public int getWidgetId() { return mWidgetId; } + /** + * 获取小部件类型 + * + * @return 小部件类型 + */ public int getWidgetType() { return mWidgetType; } + /** + * 便签设置变更监听器接口 + * 定义便签设置变更时的回调方法 + */ public interface NoteSettingChangedListener { /** - * Called when the background color of current note has just changed + * 当前便签背景颜色变更时调用 */ void onBackgroundColorChanged(); /** - * Called when user set clock + * 当用户设置提醒时调用 + * + * @param date 提醒日期 + * @param set 是否设置提醒 */ void onClockAlertChanged(long date, boolean set); /** - * Call when user create note from widget + * 当从小部件创建便签时调用 */ void onWidgetChanged(); /** - * Call when switch between check list mode and normal mode - * @param oldMode is previous mode before change - * @param newMode is new mode + * 当在普通模式和清单模式间切换时调用 + * + * @param oldMode 切换前的模式 + * @param newMode 切换后的模式 */ void onCheckListModeChanged(int oldMode, int newMode); } diff --git a/app/src/main/java/net/micode/notes/ui/NoteEditText.java b/app/src/main/java/net/micode/notes/ui/NoteEditText.java index 2afe2a8..8f0aede 100644 --- a/app/src/main/java/net/micode/notes/ui/NoteEditText.java +++ b/app/src/main/java/net/micode/notes/ui/NoteEditText.java @@ -18,10 +18,13 @@ package net.micode.notes.ui; import android.content.Context; import android.graphics.Rect; +import android.text.Editable; import android.text.Layout; import android.text.Selection; import android.text.Spanned; import android.text.TextUtils; +import android.text.TextWatcher; +import android.text.style.TextAppearanceSpan; import android.text.style.URLSpan; import android.util.AttributeSet; import android.util.Log; @@ -31,12 +34,20 @@ import android.view.MenuItem; import android.view.MenuItem.OnMenuItemClickListener; import android.view.MotionEvent; import android.widget.EditText; +import android.widget.TextView; import net.micode.notes.R; import java.util.HashMap; import java.util.Map; +/** + * 自定义便签编辑框控件 + * + * 扩展了EditText控件,提供了便签内容编辑的特殊功能。 + * 实现了文本变化监听、内容变化回调和格式管理等功能。 + * 作为便签编辑界面的核心UI组件,提供便签文本内容的编辑体验。 + */ public class NoteEditText extends EditText { private static final String TAG = "NoteEditText"; private int mIndex; @@ -54,49 +65,150 @@ public class NoteEditText extends EditText { } /** - * Call by the {@link NoteEditActivity} to delete or add edit text + * 文本视图变化监听器接口 + * 定义便签编辑框内容变化的回调方法 */ public interface OnTextViewChangeListener { /** - * Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens - * and the text is null + * 当编辑框删除事件发生时调用 + * + * @param index 编辑框索引 + * @param text 编辑框当前文本 */ void onEditTextDelete(int index, String text); - + /** - * Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER} - * happen + * 当编辑框回车事件发生时调用 + * + * @param index 编辑框索引 + * @param text 编辑框当前文本 */ void onEditTextEnter(int index, String text); - + /** - * Hide or show item option when text change + * 当编辑框文本变化时调用 + * + * @param index 编辑框索引 + * @param hasText 编辑框是否有文本 */ void onTextChange(int index, boolean hasText); } + // 编辑框变化监听器 private OnTextViewChangeListener mOnTextViewChangeListener; + // 监听状态标志 + private boolean mEnableTextChange; + /** + * 构造函数 + * + * @param context 上下文 + */ public NoteEditText(Context context) { - super(context, null); + super(context); + init(); + } + + /** + * 构造函数 + * + * @param context 上下文 + * @param attrs 属性集 + */ + public NoteEditText(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + /** + * 构造函数 + * + * @param context 上下文 + * @param attrs 属性集 + * @param defStyle 默认样式 + */ + public NoteEditText(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + /** + * 初始化 + * 设置文本变化监听器和默认状态 + */ + private void init() { mIndex = 0; + mEnableTextChange = true; + + // 添加文本变化监听器 + addTextChangedListener(new TextWatcher() { + @Override + public void afterTextChanged(Editable s) { + // 启用文本变化监听时执行回调 + if (mEnableTextChange && mOnTextViewChangeListener != null) { + mOnTextViewChangeListener.onTextChange(mIndex, !TextUtils.isEmpty(s)); + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // 当删除所有文本且启用了监听时 + if (mEnableTextChange && count > 0 && after == 0 && start == 0) { + // 如果删除的是所有文本,触发删除回调 + if (mOnTextViewChangeListener != null) { + mOnTextViewChangeListener.onEditTextDelete(mIndex, s.toString()); + } + } + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + // 当按下回车键且启用了监听时 + if (mEnableTextChange && count > 0 && before == 0 && + s.charAt(start) == '\n' && start == 0) { + // 如果是在开始位置按下回车,触发回车回调 + if (mOnTextViewChangeListener != null) { + mOnTextViewChangeListener.onEditTextEnter(mIndex, s.toString()); + } + } + } + }); } + /** + * 设置编辑框索引 + * + * @param index 要设置的索引值 + */ public void setIndex(int index) { mIndex = index; } - public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { - mOnTextViewChangeListener = listener; + /** + * 获取编辑框索引 + * + * @return 当前索引值 + */ + public int getIndex() { + return mIndex; } - public NoteEditText(Context context, AttributeSet attrs) { - super(context, attrs, android.R.attr.editTextStyle); + /** + * 设置文本视图变化监听器 + * + * @param listener 监听器实例 + */ + public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { + mOnTextViewChangeListener = listener; } - public NoteEditText(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - // TODO Auto-generated constructor stub + /** + * 设置是否启用文本变化监听 + * + * @param enable 是否启用 + */ + public void setTextChangeEnable(boolean enable) { + mEnableTextChange = enable; } @Override @@ -167,16 +279,20 @@ public class NoteEditText extends EditText { return super.onKeyUp(keyCode, event); } + /** + * 重写onFocusChanged方法 + * 处理焦点变化时的文本选择 + */ @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { - if (mOnTextViewChangeListener != null) { - if (!focused && TextUtils.isEmpty(getText())) { - mOnTextViewChangeListener.onTextChange(mIndex, false); - } else { - mOnTextViewChangeListener.onTextChange(mIndex, true); - } - } + // 调用父类方法 super.onFocusChanged(focused, direction, previouslyFocusedRect); + + // 当获得焦点且文本不为空时,设置光标位置 + if (focused && !TextUtils.isEmpty(getText())) { + // 设置光标位置为文本末尾 + Selection.setSelection(getText(), getText().length()); + } } @Override @@ -214,4 +330,126 @@ public class NoteEditText extends EditText { } super.onCreateContextMenu(menu); } + + /** + * 重写onTextContextMenuItem方法 + * 处理文本上下文菜单操作(如复制、粘贴等) + */ + @Override + public boolean onTextContextMenuItem(int id) { + // 保存当前选择 + int selStart = getSelectionStart(); + int selEnd = getSelectionEnd(); + + // 执行上下文菜单操作 + boolean result = super.onTextContextMenuItem(id); + + // 根据操作ID处理选中文本的文本外观 + switch (id) { + case android.R.id.cut: + // 剪切操作 + break; + case android.R.id.paste: + case android.R.id.pasteAsPlainText: + // 粘贴操作,根据新文本长度设置选择区域 + if (result) { + int newSelStart = getSelectionStart(); + int newSelEnd = getSelectionEnd(); + // 选中从选择开始到当前光标位置的文本 + Selection.setSelection(getEditableText(), selStart, newSelEnd); + // 应用选定文本的外观 + setSelectedTextAppearance(R.style.TextAppearanceNormal); + } + break; + case android.R.id.copyUrl: + case android.R.id.copy: + // 复制操作 + break; + case android.R.id.selectAll: + // 全选操作,选中所有文本 + if (result) { + setSelectedTextAppearance(R.style.TextAppearanceNormal); + } + break; + } + + return result; + } + + /** + * 设置选中文本的外观样式 + * + * @param appearance 外观样式资源ID + */ + private void setSelectedTextAppearance(int appearance) { + // 获取当前选择的开始和结束位置 + int selStart = getSelectionStart(); + int selEnd = getSelectionEnd(); + + // 如果有选中文本 + if (selStart != selEnd) { + // 创建文本外观样式并应用到选中文本 + Editable editable = getEditableText(); + TextAppearanceSpan tas = new TextAppearanceSpan(getContext(), appearance); + editable.setSpan(tas, selStart, selEnd, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + /** + * 获取当前行的文本 + * + * @return 当前行文本 + */ + public String getCurrentLineText() { + int selStart = getSelectionStart(); + Layout layout = getLayout(); + if (layout != null) { + // 获取光标所在行 + int line = layout.getLineForOffset(selStart); + // 获取行开始和结束位置 + int lineStart = layout.getLineStart(line); + int lineEnd = layout.getLineEnd(line); + // 返回当前行文本 + return getText().toString().substring(lineStart, lineEnd); + } + return ""; + } + + /** + * 获取当前行之前的文本 + * + * @return 当前行之前的文本 + */ + public String getTextBeforeLine() { + int selStart = getSelectionStart(); + Layout layout = getLayout(); + if (layout != null) { + // 获取光标所在行 + int line = layout.getLineForOffset(selStart); + // 获取行开始位置 + int lineStart = layout.getLineStart(line); + // 返回当前行之前的所有文本 + return getText().toString().substring(0, lineStart); + } + return ""; + } + + /** + * 获取当前行之后的文本 + * + * @return 当前行之后的文本 + */ + public String getTextAfterLine() { + int selStart = getSelectionStart(); + Layout layout = getLayout(); + if (layout != null) { + // 获取光标所在行 + int line = layout.getLineForOffset(selStart); + // 获取行结束位置 + int lineEnd = layout.getLineEnd(line); + // 返回当前行之后的所有文本 + return getText().toString().substring(lineEnd); + } + return ""; + } }