合并 #6

Open
poyt3a5hz wants to merge 0 commits from develop into xiaomibianqian-maxaingru

@ -1,78 +1,73 @@
/*//如果查询结果不为空且有数据//
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)//如果查询结果不为空且有数据//
*//如果查询结果不为空且有数据//
* Licensed under the Apache License, Version 2.0 (the "License");//如果查询结果不为空且有数据//
* you may not use this file except in compliance with the License.//如果查询结果不为空且有数据//
* You may obtain a copy of the License at//如果查询结果不为空且有数据//如果查询结果不为空且有数据//
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* http://www.apache.org/licenses/LICENSE-2.0//如果查询结果不为空且有数据//
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* Unless required by applicable law or agreed to in writing, software//如果查询结果不为空且有数据//
* distributed under the License is distributed on an "AS IS" BASIS,//如果查询结果不为空且有数据//
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.///
* See the License for the specific language governing permissions and//如果查询结果不为空且有数据//
* limitations under the License.//如果查询结果不为空且有数据//
*/// //如果查询结果不为空且有数据//
// 如果查询结果不为空且有数据//
package net.micode.notes.data; // 定义包名
//如果查询结果不为空且有数据//
import android.content.Context; // 导入Context类
import android.database.Cursor; // 导入Cursor类
import android.provider.ContactsContract.CommonDataKinds.Phone; // 导入Phone类
import android.provider.ContactsContract.Data; // 导入Data类
import android.telephony.PhoneNumberUtils; // 导入PhoneNumberUtils类
import android.util.Log; // 导入Log类
// 如果查询结果不为空且有数据
import java.util.HashMap; // 导入HashMap类
// 如果查询结果不为空且有数据
public class Contact {//如果查询结果不为空且有数据
private static HashMap<String, String> sContactCache; // 定义一个静态HashMap用于缓存联系人信息
private static final String TAG = "Contact"; // 定义日志标签// 如果查询结果不为空且有数据
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.data;
import android.content.Context;
import android.database.Cursor;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.Data;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import java.util.HashMap;
public class Contact {
private static HashMap<String, String> sContactCache;
private static final String TAG = "Contact";
private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER
+ ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'"
+ " AND " + Data.RAW_CONTACT_ID + " IN "
+ "(SELECT raw_contact_id "
+ " FROM phone_lookup"
+ " WHERE min_match = '+')";
public static String getContact(Context context, String phoneNumber) {
if(sContactCache == null) {
sContactCache = new HashMap<String, String>();
}
if(sContactCache.containsKey(phoneNumber)) {
return sContactCache.get(phoneNumber);
}
String selection = CALLER_ID_SELECTION.replace("+",
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));
Cursor cursor = context.getContentResolver().query(
Data.CONTENT_URI,
new String [] { Phone.DISPLAY_NAME },
selection,
new String[] { phoneNumber },
null);
// 定义查询条件字符串,用于匹配电话号码// 如果查询结果不为空且有数据
private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER// 如果查询结果不为空且有数据
+ ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'"// 如果查询结果不为空且有数据
+ " AND " + Data.RAW_CONTACT_ID + " IN "// 如果查询结果不为空且有数据
+ "(SELECT raw_contact_id "// 如果查询结果不为空且有数据
+ " FROM phone_lookup"// 如果查询结果不为空且有数据
+ " WHERE min_match = '+')";// 如果查询结果不为空且有数据
//如果查询结果不为空且有数据
// 获取联系人信息的方法// 如果查询结果不为空且有数据
public static String getContact(Context context, String phoneNumber) {// 如果查询结果不为空且有数据
if(sContactCache == null) { // 如果缓存为空,则初始化
sContactCache = new HashMap<String, String>();// 如果查询结果不为空且有数据
}// 如果查询结果不为空且有数据
// 如果查询结果不为空且有数据
if(sContactCache.containsKey(phoneNumber)) { // 如果缓存中已存在该电话号码,直接返回缓存的联系人姓名
return sContactCache.get(phoneNumber);// 如果查询结果不为空且有数据
}// 如果查询结果不为空且有数据
// 如果查询结果不为空且有数据
// 替换查询条// 如果查询结果不为空且有数据件中的占位符
String selection = CALLER_ID_SELECTION.replace("+",// 如果查询结果不为空且有数据
PhoneNumberUtils.toCallerIDMinMatch(phoneNumber));// 如果查询结果不为空且有数据
// 查询联系人信息
Cursor cursor = context.getContentResolver().query(// 如果查询结果不为空且有数据
Data.CONTENT_URI,// 如果查询结果不为空且有数据
new String [] { Phone.DISPLAY_NAME },// 如果查询结果不为空且有数据
selection,// 如果查询结果不为空且有数据
new String[] { phoneNumber },// 如果查询结果不为空且有数据
null);// 如果查询结果不为空且有数据
//如果查询结果不为空且有数据
if (cursor != null && cursor.moveToFirst()) { // 如果查询结果不为空且有数据
try {//如果查询结果不为空且有数据
String name = cursor.getString(0); // 获取联系人姓名
sContactCache.put(phoneNumber, name); // 将联系人信息缓存
return name; // 返回联系人姓名
} catch (IndexOutOfBoundsException e) { // 捕获异常
Log.e(TAG, " Cursor get string error " + e.toString()); // 打印错误日志
return null; // 返回空
} finally {// 返回空
cursor.close(); // 关闭游标
}// 返回空
} else {// 返回空
Log.d(TAG, "No contact matched with number:" + phoneNumber); // 打印未匹配到联系人日志
return null; // 返回空
}// 返回空
}// 返回空
}// 返回空
//如果查询结果不为空且有数据
if (cursor != null && cursor.moveToFirst()) {
try {
String name = cursor.getString(0);
sContactCache.put(phoneNumber, name);
return name;
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, " Cursor get string error " + e.toString());
return null;
} finally {
cursor.close();
}
} else {
Log.d(TAG, "No contact matched with number:" + phoneNumber);
return null;
}
}
}

@ -1,26 +1,40 @@
package net.micode.notes.data; // 定义该类所在的包名
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.net.Uri; // 导入Android的Uri类
package net.micode.notes.data;
// 定义一个公共类 Notes用于管理笔记应用中的常量和数据结构
import android.net.Uri;
public class Notes {
// 定义权限名,用于内容提供者的访问
public static final String AUTHORITY = "micode_notes";
// 定义日志标签名,便于调试
public static final String TAG = "Notes";
public static final int TYPE_NOTE = 0;
public static final int TYPE_FOLDER = 1;
public static final int TYPE_SYSTEM = 2;
// 定义笔记类型:普通笔记、文件夹、系统类型
public static final int TYPE_NOTE = 0; // 普通笔记类型
public static final int TYPE_FOLDER = 1; // 文件夹类型
public static final int TYPE_SYSTEM = 2; // 系统类型
/**
* Following IDs are system folders' identifiers
* {@link Notes#ID_ROOT_FOLDER } is default folder
* {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder
* {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records
*/
public static final int ID_ROOT_FOLDER = 0;
public static final int ID_TEMPARAY_FOLDER = -1;
public static final int ID_CALL_RECORD_FOLDER = -2;
public static final int ID_TRASH_FOLER = -3;
// 系统文件夹标识符
public static final int ID_ROOT_FOLDER = 0; // 根文件夹的ID
public static final int ID_TEMPARAY_FOLDER = -1; // 临时文件夹的ID
public static final int ID_CALL_RECORD_FOLDER = -2; // 通话记录文件夹的ID
public static final int ID_TRASH_FOLER = -3; // 垃圾箱文件夹的ID
// 定义各种Intent额外数据的key
public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date";
public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id";
public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id";
@ -28,74 +42,238 @@ public class Notes {
public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id";
public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date";
// 定义Widget类型
public static final int TYPE_WIDGET_INVALIDE = -1; // 无效Widget类型
public static final int TYPE_WIDGET_2X = 0; // 2x的Widget类型
public static final int TYPE_WIDGET_4X = 1; // 4x的Widget类型
public static final int TYPE_WIDGET_INVALIDE = -1;
public static final int TYPE_WIDGET_2X = 0;
public static final int TYPE_WIDGET_4X = 1;
// 定义数据常量类
public static class DataConstants {
// 定义文本笔记类型和通话笔记类型
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE; // 文本笔记类型
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE; // 通话笔记类型
public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;
public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;
}
// 定义访问所有笔记和文件夹的Uri
/**
* Uri to query all notes and folders
*/
public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note");
// 定义访问数据的Uri
/**
* Uri to query data
*/
public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data");
// 定义笔记的列名接口
public interface NoteColumns {
public static final String ID = "_id"; // 每行的唯一ID
public static final String PARENT_ID = "parent_id"; // 父ID文件夹的ID或上级ID
public static final String CREATED_DATE = "created_date"; // 创建日期
public static final String MODIFIED_DATE = "modified_date"; // 最后修改日期
public static final String ALERTED_DATE = "alert_date"; // 提醒日期
public static final String SNIPPET = "snippet"; // 文件夹名称或笔记文本内容
public static final String WIDGET_ID = "widget_id"; // Widget的ID
public static final String WIDGET_TYPE = "widget_type"; // Widget的类型
public static final String BG_COLOR_ID = "bg_color_id"; // 背景颜色ID
public static final String HAS_ATTACHMENT = "has_attachment"; // 是否有附件
public static final String NOTES_COUNT = "notes_count"; // 文件夹内笔记数量
public static final String TYPE = "type"; // 文件类型(笔记或文件夹)
public static final String SYNC_ID = "sync_id"; // 同步ID
public static final String LOCAL_MODIFIED = "local_modified"; // 是否本地修改
public static final String ORIGIN_PARENT_ID = "origin_parent_id"; // 移动前的父ID
public static final String GTASK_ID = "gtask_id"; // 任务ID
public static final String VERSION = "version"; // 版本号
/**
* The unique ID for a row
* <P> Type: INTEGER (long) </P>
*/
public static final String ID = "_id";
/**
* The parent's id for note or folder
* <P> Type: INTEGER (long) </P>
*/
public static final String PARENT_ID = "parent_id";
/**
* Created data for note or folder
* <P> Type: INTEGER (long) </P>
*/
public static final String CREATED_DATE = "created_date";
/**
* Latest modified date
* <P> Type: INTEGER (long) </P>
*/
public static final String MODIFIED_DATE = "modified_date";
/**
* Alert date
* <P> Type: INTEGER (long) </P>
*/
public static final String ALERTED_DATE = "alert_date";
/**
* Folder's name or text content of note
* <P> Type: TEXT </P>
*/
public static final String SNIPPET = "snippet";
/**
* Note's widget id
* <P> Type: INTEGER (long) </P>
*/
public static final String WIDGET_ID = "widget_id";
/**
* Note's widget type
* <P> Type: INTEGER (long) </P>
*/
public static final String WIDGET_TYPE = "widget_type";
/**
* Note's background color's id
* <P> Type: INTEGER (long) </P>
*/
public static final String BG_COLOR_ID = "bg_color_id";
/**
* For text note, it doesn't has attachment, for multi-media
* note, it has at least one attachment
* <P> Type: INTEGER </P>
*/
public static final String HAS_ATTACHMENT = "has_attachment";
/**
* Folder's count of notes
* <P> Type: INTEGER (long) </P>
*/
public static final String NOTES_COUNT = "notes_count";
/**
* The file type: folder or note
* <P> Type: INTEGER </P>
*/
public static final String TYPE = "type";
/**
* The last sync id
* <P> Type: INTEGER (long) </P>
*/
public static final String SYNC_ID = "sync_id";
/**
* Sign to indicate local modified or not
* <P> Type: INTEGER </P>
*/
public static final String LOCAL_MODIFIED = "local_modified";
/**
* Original parent id before moving into temporary folder
* <P> Type : INTEGER </P>
*/
public static final String ORIGIN_PARENT_ID = "origin_parent_id";
/**
* The gtask id
* <P> Type : TEXT </P>
*/
public static final String GTASK_ID = "gtask_id";
/**
* The version code
* <P> Type : INTEGER (long) </P>
*/
public static final String VERSION = "version";
}
// 定义数据列的接口
public interface DataColumns {
public static final String ID = "_id"; // 数据的唯一ID
public static final String MIME_TYPE = "mime_type"; // 数据的MIME类型
public static final String NOTE_ID = "note_id"; // 数据所属的笔记ID
public static final String CREATED_DATE = "created_date"; // 数据创建日期
public static final String MODIFIED_DATE = "modified_date"; // 数据最后修改日期
public static final String CONTENT = "content"; // 数据内容
public static final String DATA1 = "data1"; // 通用数据列1
public static final String DATA2 = "data2"; // 通用数据列2
public static final String DATA3 = "data3"; // 通用数据列3
public static final String DATA4 = "data4"; // 通用数据列4
public static final String DATA5 = "data5"; // 通用数据列5
/**
* The unique ID for a row
* <P> Type: INTEGER (long) </P>
*/
public static final String ID = "_id";
/**
* The MIME type of the item represented by this row.
* <P> Type: Text </P>
*/
public static final String MIME_TYPE = "mime_type";
/**
* The reference id to note that this data belongs to
* <P> Type: INTEGER (long) </P>
*/
public static final String NOTE_ID = "note_id";
/**
* Created data for note or folder
* <P> Type: INTEGER (long) </P>
*/
public static final String CREATED_DATE = "created_date";
/**
* Latest modified date
* <P> Type: INTEGER (long) </P>
*/
public static final String MODIFIED_DATE = "modified_date";
/**
* Data's content
* <P> Type: TEXT </P>
*/
public static final String CONTENT = "content";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type
* <P> Type: INTEGER </P>
*/
public static final String DATA1 = "data1";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* integer data type
* <P> Type: INTEGER </P>
*/
public static final String DATA2 = "data2";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
public static final String DATA3 = "data3";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
public static final String DATA4 = "data4";
/**
* Generic data column, the meaning is {@link #MIMETYPE} specific, used for
* TEXT data type
* <P> Type: TEXT </P>
*/
public static final String DATA5 = "data5";
}
// 定义文本笔记类
public static final class TextNote implements DataColumns {
public static final String MODE = DATA1; // 模式字段:是否为检查列表模式
public static final int MODE_CHECK_LIST = 1; // 检查列表模式
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; // 内容类型:多个文本笔记
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; // 内容类型:单个文本笔记
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); // 文本笔记的访问Uri
/**
* Mode to indicate the text in check list mode or not
* <P> Type: Integer 1:check list mode 0: normal mode </P>
*/
public static final String MODE = DATA1;
public static final int MODE_CHECK_LIST = 1;
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note");
}
// 定义通话笔记类
public static final class CallNote implements DataColumns {
public static final String CALL_DATE = DATA1; // 通话日期字段
public static final String PHONE_NUMBER = DATA3; // 电话号码字段
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note"; // 内容类型:多个通话笔记
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note"; // 内容类型:单个通话笔记
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note"); // 通话笔记的访问Uri
/**
* Call date for this record
* <P> Type: INTEGER (long) </P>
*/
public static final String CALL_DATE = DATA1;
/**
* Phone number for this record
* <P> Type: TEXT </P>
*/
public static final String PHONE_NUMBER = DATA3;
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");
}
}

@ -1,287 +1,362 @@
package net.micode.notes.data; // 包声明
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.content.ContentValues; // 导入ContentValues类
import android.content.Context; // 导入Context类
import android.database.sqlite.SQLiteDatabase; // 导入SQLiteDatabase类
import android.database.sqlite.SQLiteOpenHelper; // 导入SQLiteOpenHelper类
import android.util.Log; // 导入Log类
package net.micode.notes.data;
import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.Notes.DataColumns; // 导入DataColumns类
import net.micode.notes.data.Notes.DataConstants; // 导入DataConstants类
import net.micode.notes.data.Notes.NoteColumns; // 导入NoteColumns类
// Notes数据库帮助类
public class NotesDatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "note.db"; // 定义数据库名称
private static final int DB_VERSION = 4; // 定义数据库版本号
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"; // 数据表名
public static final String NOTE = "note";
public static final String DATA = "data";
}
private static final String TAG = "NotesDatabaseHelper"; // 用于日志输出的TAG
private static final String TAG = "NotesDatabaseHelper";
private static NotesDatabaseHelper mInstance; // 定义数据库帮助类的实例
private static NotesDatabaseHelper mInstance;
// 创建笔记表的SQL语句
private static final String CREATE_NOTE_TABLE_SQL =
"CREATE TABLE " + TABLE.NOTE + "(" + // 创建笔记表
NoteColumns.ID + " INTEGER PRIMARY KEY," + // 笔记ID主键
NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 父级笔记ID默认为0
NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," + // 提醒日期默认为0
NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," + // 背景色ID默认为0
NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 创建日期,默认为当前时间戳
NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + // 是否有附件默认为0
NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," + // 修改日期,默认为当前时间戳
NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," + // 笔记数量默认为0
NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," + // 笔记摘要,默认为空字符串
NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + // 笔记类型默认为0
NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," + // 小部件ID默认为0
NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + // 小部件类型,默认为-1
NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + // 同步ID默认为0
NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + // 本地修改标志默认为0
NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," + // 原父级ID默认为0
NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + // GTASK ID默认为空字符串
NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + // 笔记版本号默认为0
")"; // 结束创建表的SQL语句
// 创建触发器SQL
// 创建数据表的SQL语句
private static final String CREATE_DATA_TABLE_SQL =// 创建触发器SQL
"CREATE TABLE " + TABLE.DATA + "(" + // 创建数据表
DataColumns.ID + " INTEGER PRIMARY KEY," + // 数据ID主键
DataColumns.MIME_TYPE + " TEXT NOT NULL," + // MIME类型
DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," + // 笔记ID默认为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," + // 数据字段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默认为空字符串
")"; // 结束创建数据表的SQL语句// 创建触发器SQL
// 创建触发器SQL
// 创建数据表的NOTE_ID索引// 创建触发器SQL
private static final String CREATE_DATA_NOTE_ID_INDEX_SQL =// 创建触发器SQL
"CREATE INDEX IF NOT EXISTS note_id_index ON " +// 创建触发器SQL
TABLE.DATA + "(" + DataColumns.NOTE_ID + ");"; // 创建数据表的NOTE_ID索引
// 创建触发器SQL
// 创建触发器更新笔记的父级ID时增加父文件夹的笔记数// 创建触发器SQL
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =// 创建触发器SQL
"CREATE TRIGGER increase_folder_count_on_update " +// 创建触发器SQL
" AFTER UPDATE OF " + eColumns.PARENT_ID + " ON " + TABLE.NOTE +// 创建触发器SQL
" BEGIN " +// 创建触发器SQL
" UPDATE " + TABLE.NOTE +// 创建触发器SQL// 创建触发器SQL// 创建触发器SQL
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +// 创建触发器SQL
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +// 创建触发器SQL// 创建触发器SQL
" END"; // 创建触发器SQL
// 创建触发器SQL
// 创建触发器更新笔记的父级ID时减少父文件夹的笔记数
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER =// 创建触发器SQL
"CREATE TRIGGER decrease_folder_count_on_update " +// 创建触发器SQL
" AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE +// 创建触发器SQL
" BEGIN " +// 创建触发器SQL// 创建触发器SQL
" UPDATE " + TABLE.NOTE +// 创建触发器SQL
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +// 创建触发器SQL
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +// 创建触发器SQL
" AND " + NoteColumns.NOTES_COUNT + ">0" + ";" +// 创建触发器SQL
" END"; // 创建触发器SQL
// 创建触发器:插入笔记时增加父文件夹的笔记数
private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER =// 创建触发器SQL
"CREATE TRIGGER increase_folder_count_on_insert " +// 创建触发器SQL
" AFTER INSERT ON " + TABLE.NOTE +// 创建触发器SQL
" BEGIN " +// 创建触发器SQL
" UPDATE " + TABLE.NOTE +// 创建触发器SQL
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +// 创建触发器SQL
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +// 创建触发器SQL
" END"; // 创建触发器SQL
// 创建触发器:删除笔记时减少父文件夹的笔记数
private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER =// 创建触发器SQL
"CREATE TRIGGER decrease_folder_count_on_delete " +// 创建触发器SQL
" AFTER DELETE ON " + TABLE.NOTE +// 创建触发器SQL
" BEGIN " +// 创建触发器SQL
" UPDATE " + TABLE.NOTE +// 创建触发器SQL
" SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +// 创建触发器SQL
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +// 创建触发器SQL
" AND " + NoteColumns.NOTES_COUNT + ">0;" +// 创建触发器SQL
" END"; // 创建触发器SQL
// 创建触发器:插入数据时更新笔记的摘要
private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER =// 创建触发器SQL
"CREATE TRIGGER update_note_content_on_insert " +// 创建触发器SQL
" AFTER INSERT ON " + TABLE.DATA +// 创建触发器SQL
" WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +// 创建触发器SQL
" BEGIN" +// 创建触发器SQL
" UPDATE " + TABLE.NOTE +// 创建触发器SQL
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +// 创建触发器SQL
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +// 创建触发器SQL
" END"; // 创建触发器SQL
// 创建触发器:更新数据时更新笔记的摘要
private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER =// 创建触发器SQL
"CREATE TRIGGER update_note_content_on_update " +// 创建触发器SQL// 创建触发器SQL
" AFTER UPDATE ON " + TABLE.DATA +
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +// 创建触发器SQL
" BEGIN" +// 创建触发器SQL
" UPDATE " + TABLE.NOTE +// 创建触发器SQL
" SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +// 创建触发器SQL
" WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +// 创建触发器SQL
" END"; // 创建触发器SQL
// 创建触发器:删除数据时更新笔记的摘要
private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER =// 创建触发器SQL
"CREATE TRIGGER update_note_content_on_delete " +// 创建触发器SQL
" AFTER delete ON " + TABLE.DATA +// 创建触发器SQL
" WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" +// 创建触发器SQL
" BEGIN" +
" UPDATE " + TABLE.NOTE +// 创建触发器SQL
" SET " + NoteColumns.SNIPPET + "=''" +// 创建触发器SQL
" WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" +// 创建触发器SQL
" END"; // 创建触发器SQL
// 创建触发器:删除笔记时删除相关的数据
private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER =// 创建触发器SQL
"CREATE TRIGGER delete_data_on_delete " +// 创建触发器SQL
" AFTER DELETE ON " + TABLE.NOTE +// 创建触发器SQL
" BEGIN" +// 创建触发器SQL
" DELETE FROM " + TABLE.DATA +// 创建触发器SQL
" WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" +// 创建触发器SQL
" END"; // 创建触发器SQL
// 创建触发器:删除文件夹时删除该文件夹中的所有笔记
private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER =// 创建触发器SQL
"CREATE TRIGGER folder_delete_notes_on_delete " +// 创建触发器SQL
" AFTER DELETE ON " + TABLE.NOTE +// 创建触发器SQL
" BEGIN" +// 创建触发器SQL
" DELETE FROM " + TABLE.NOTE +// 创建触发器SQL
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +// 创建触发器SQL
" END"; // 创建触发器SQL
// 创建触发器:移动文件夹到回收站时移动所有笔记到回收站
private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER =// 创建触发器SQL
"CREATE TRIGGER folder_move_notes_on_trash " +// 创建触发器SQL
" AFTER UPDATE ON " + TABLE.NOTE +// 创建触发器SQL
" WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +// 创建触发器SQL
" BEGIN" +// 创建触发器SQL
" UPDATE " + TABLE.NOTE +// 创建触发器SQL
" SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +// 创建触发器SQL
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +// 创建触发器SQL
" END"; // 创建触发器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" +
")";
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 ''" +
")";
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
*/
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" +
" WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +
" END";
/**
* Decrease folder's note count when move note from folder
*/
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" +
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
" 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" +
" 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" +
" WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID +
" AND " + NoteColumns.NOTES_COUNT + ">0;" +
" END";
/**
* Update note's content when insert data with type {@link DataConstants#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 + "'" +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" 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
*/
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 + "'" +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" 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
*/
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 + "'" +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" 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 +
" 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 +
" 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 +
" BEGIN" +
" UPDATE " + TABLE.NOTE +
" SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER +
" WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +
" END";
public NotesDatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION); // 调用父类的构造函数,指定数据库名称和版本
super(context, DB_NAME, null, DB_VERSION);
}
// 创建笔记表
public void createNoteTable(SQLiteDatabase db) {
db.execSQL(CREATE_NOTE_TABLE_SQL); // 执行创建笔记表的SQL语句
reCreateNoteTableTriggers(db); // 创建触发器
db.execSQL(CREATE_NOTE_TABLE_SQL);
reCreateNoteTableTriggers(db);
createSystemFolder(db);
Log.d(TAG, "note table has been created");
}
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 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_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);
}
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.TYPE, Notes.TYPE_SYSTEM);
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.TYPE, Notes.TYPE_SYSTEM);
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.TYPE, Notes.TYPE_SYSTEM);
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);
db.insert(TABLE.NOTE, null, values);
}
// 创建数据表
public void createDataTable(SQLiteDatabase db) {
db.execSQL(CREATE_DATA_TABLE_SQL); // 执行创建数据表的SQL语句
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);
Log.d(TAG, "data table has been created");
}
// 执行数据库创建操作
@Override
public void onCreate(SQLiteDatabase db) {
createNoteTable(db); // 创建笔记表
createDataTable(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);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.d(TAG, "onUpgrade: " + oldVersion + " --> " + newVersion); // 打印日志
// 根据版本号升级数据库
if (oldVersion == 1) {
db.execSQL(CREATE_DATA_TABLE_SQL); // 创建数据表
db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL); // 创建索引
db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); // 创建触发器
db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER); // 创建触发器
}
if (oldVersion <= 3) {
createNoteTable(db); // 创建笔记表
reCreateNoteTableTriggers(db); // 创建触发器
static synchronized NotesDatabaseHelper getInstance(Context context) {
if (mInstance == null) {
mInstance = new NotesDatabaseHelper(context);
}
return mInstance;
}
// 执行数据库升级操作
@Override
public void onCreate(SQLiteDatabase db) {
createNoteTable(db);
createDataTable(db);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
boolean reCreateTriggers = false; // 是否需要重新创建触发器的标志
boolean skipV2 = false; // 是否跳过V2升级的标志
boolean reCreateTriggers = false;
boolean skipV2 = false;
// 如果当前数据库版本是1进行从V1升级到V2的操作
if (oldVersion == 1) {
upgradeToV2(db); // 执行V1到V2的升级
skipV2 = true; // 跳过V2到V3的升级
oldVersion++; // 更新数据库版本为2
upgradeToV2(db);
skipV2 = true; // this upgrade including the upgrade from v2 to v3
oldVersion++;
}
// 如果当前数据库版本是2且没有跳过V2升级
if (oldVersion == 2 && !skipV2) {
upgradeToV3(db); // 执行V2到V3的升级
reCreateTriggers = true; // 标记需要重新创建触发器
oldVersion++; // 更新数据库版本为3
upgradeToV3(db);
reCreateTriggers = true;
oldVersion++;
}
// 如果当前数据库版本是3执行V3到V4的升级
if (oldVersion == 3) {
upgradeToV4(db); // 执行V3到V4的升级
oldVersion++; // 更新数据库版本为4
}// 添加VERSION列默认为0
upgradeToV4(db);
oldVersion++;
}
// 如果需要重新创建触发器,执行重新创建触发器的操作
if (reCreateTriggers) {
reCreateNoteTableTriggers(db); // 重新创建笔记表的触发器
reCreateDataTableTriggers(db); // 重新创建数据表的触发器
reCreateNoteTableTriggers(db);
reCreateDataTableTriggers(db);
}
// 如果升级后的版本不等于目标版本,抛出异常
if (oldVersion != newVersion) {
throw new IllegalStateException("Upgrade notes database to version " + newVersion// 添加VERSION列默认为0
+ " fails"); // 升级失败抛出异常
}// 添加VERSION列默认为0
}// 添加VERSION列默认为0
// 添加VERSION列默认为0
// 从V1升级到V2
throw new IllegalStateException("Upgrade notes database to version " + newVersion
+ "fails");
}
}
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); // 重新创建数据表
}// 添加VERSION列默认为0
// 添加VERSION列默认为0
// 从V2升级到V3
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) {
// 删除不再使用的触发器
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"); // 删除更新时更新时间触发器
// 在笔记表中添加一个新的列用于存储gtask ID
// 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
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID
+ " TEXT NOT NULL DEFAULT ''"); // 添加GTASK_ID列默认为空字符串
// 在笔记表中添加一个垃圾系统文件夹
ContentValues values = new ContentValues(); // 创建一个ContentValues对象
values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); // 设置垃圾文件夹ID
values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); // 设置文件夹类型为系统文件夹
db.insert(TABLE.NOTE, null, values); // 插入新的垃圾文件夹记录
}// 添加VERSION列默认为0
// 添加VERSION列默认为0
// 从V3升级到V4
+ " 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);
}
private void upgradeToV4(SQLiteDatabase db) {
// 在笔记表中添加一个新的列,用于存储版本号
db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION
+ " INTEGER NOT NULL DEFAULT 0"); // 添加VERSION列默认为0
}// 添加VERSION列默认为0
// 添加VERSION列默认为0
// 添加VERSION列默认为0
}// 添加VERSION列默认为0
// 添加VERSION列默认为0
+ " INTEGER NOT NULL DEFAULT 0");
}
}

@ -1,229 +1,305 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* See LICENSE file for full license details.
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.data;//导入
import android.app.SearchManager;//导入
import android.content.ContentProvider;//导入
import android.content.ContentUris;//导入
import android.content.ContentValues;//导入
import android.content.Intent;//导入
import android.content.UriMatcher;//导入
import android.database.Cursor;//导入
import android.database.sqlite.SQLiteDatabase;//导入
import android.net.Uri;//导入
import android.text.TextUtils;//导入
import android.util.Log;//导入
//导入
import net.micode.notes.R;//导入
import net.micode.notes.data.Notes.DataColumns;//导入
import net.micode.notes.data.Notes.NoteColumns;//导入
import net.micode.notes.data.NotesDatabaseHelper.TABLE;//导入
public class NotesProvider extends ContentProvider {//导入
// 日志标签,用于日志输出
private static final String TAG = "NotesProvider";//导入
// Uri匹配器负责匹配传入的Uri
private static final UriMatcher mMatcher;// 插入方法
// 数据库帮助类对象
private NotesDatabaseHelper mHelper;// 插入方法
// 常量定义匹配不同Uri
private static final int URI_NOTE = 1; // 匹配整个笔记表// 常量定义匹配不同Uri
private static final int URI_NOTE_ITEM = 2; // 匹配单个笔记项// 常量定义匹配不同Uri
private static final int URI_DATA = 3; // 匹配数据表// 常量定义匹配不同Uri
private static final int URI_DATA_ITEM = 4; // 匹配单个数据项// 常量定义匹配不同Uri
private static final int URI_SEARCH = 5; // 匹配搜索功能// 常量定义匹配不同Uri
private static final int URI_SEARCH_SUGGEST = 6; // 匹配搜索建议// 常量定义匹配不同Uri
package net.micode.notes.data;
import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Intent;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
public class NotesProvider extends ContentProvider {
private static final UriMatcher mMatcher;
private NotesDatabaseHelper mHelper;
private static final String TAG = "NotesProvider";
private static final int URI_NOTE = 1;
private static final int URI_NOTE_ITEM = 2;
private static final int URI_DATA = 3;
private static final int URI_DATA_ITEM = 4;
private static final int URI_SEARCH = 5;
private static final int URI_SEARCH_SUGGEST = 6;
static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);// 常量定义匹配不同Uri
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE); // 匹配笔记表Uri
mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM); // 匹配单个笔记
mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA); // 匹配数据表Uri
mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM); // 匹配单个数据项
mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH); // 匹配搜索功能
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST); // 匹配搜索建议
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); // 带参数的搜索建议
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE);
mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM);
mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA);
mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM);
mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH);
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST);
mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST);
}
// 查询笔记表的列,去除换行符并用于搜索建议结果显示
/**
* x'0A' represents the '\n' character in sqlite. For title and content in the search result,
* we will trim '\n' and white space in order to show more information.
*/
private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + ","
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + ","
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + ","// 常量定义匹配不同Uri
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + ","// 常量定义匹配不同Uri
+ R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + ","// 常量定义匹配不同Uri
+ "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + ","// 常量定义匹配不同Uri
+ "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA;// 常量定义匹配不同Uri
// SQL查询模糊搜索笔记内容
+ NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + ","
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + ","
+ "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + ","
+ R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + ","
+ "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + ","
+ "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA;
private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION
+ " FROM " + TABLE.NOTE // SQL查询模糊搜索笔记内容
+ " WHERE " + NoteColumns.SNIPPET + " LIKE ?" // SQL查询模糊搜索笔记内容
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER // SQL查询模糊搜索笔记内容
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; // SQL查询模糊搜索笔记内容
+ " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.SNIPPET + " LIKE ?"
+ " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER
+ " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE;
// 初始化ContentProvider时调用
@Override
public boolean onCreate() { // SQL查询模糊搜索笔记内容
mHelper = NotesDatabaseHelper.getInstance(getContext()); // 获取数据库帮助类实例
return true; // SQL查询模糊搜索笔记内容
} // SQL查询模糊搜索笔记内容
public boolean onCreate() {
mHelper = NotesDatabaseHelper.getInstance(getContext());
return true;
}
/**
* Uri
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {// 插入方法
Cursor c = null;// 插入方法
SQLiteDatabase db = mHelper.getReadableDatabase(); // 获取可读数据库
String id = null;// 插入方法
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
Cursor c = null;
SQLiteDatabase db = mHelper.getReadableDatabase();
String id = null;
switch (mMatcher.match(uri)) {
case URI_NOTE:
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null,
sortOrder);
break;
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1);
c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
case URI_DATA:
c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null,
sortOrder);
break;
case URI_DATA_ITEM:
id = uri.getPathSegments().get(1);
c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs, null, null, sortOrder);
break;
case URI_SEARCH:
case URI_SEARCH_SUGGEST:
if (sortOrder != null || projection != null) {
throw new IllegalArgumentException(
"do not specify sortOrder, selection, selectionArgs, or projection" + "with this query");
}
String searchString = null;
if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) {
if (uri.getPathSegments().size() > 1) {
searchString = uri.getPathSegments().get(1);
}
} else {
searchString = uri.getQueryParameter("pattern");
}
if (TextUtils.isEmpty(searchString)) {
return null;
}
try {
searchString = String.format("%%%s%%", searchString);
c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY,
new String[] { searchString });
} catch (IllegalStateException ex) {
Log.e(TAG, "got exception: " + ex.toString());
}
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), uri);
}
return c;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = mHelper.getWritableDatabase();
long dataId = 0, noteId = 0, insertedId = 0;
switch (mMatcher.match(uri)) {
case URI_NOTE:
// 查询笔记表
c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, sortOrder);// 插入方法
break;// 插入方法
// SQL查询模糊搜索笔记内容
case URI_NOTE_ITEM:// 插入方法
// 查询特定的笔记
id = uri.getPathSegments().get(1);// 插入方法
c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs, null, null, sortOrder);
break; // SQL查询模糊搜索笔记内容
// SQL查询模糊搜索笔记内容
case URI_DATA: // SQL查询模糊搜索笔记内容
// 查询数据表
c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, sortOrder);
break; // SQL查询模糊搜索笔记内容
case URI_DATA_ITEM: // SQL查询模糊搜索笔记内容
// 查询特定的数据项
id = uri.getPathSegments().get(1); // SQL查询模糊搜索笔记内容
c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs, null, null, sortOrder);
break; // SQL查询模糊搜索笔记内容
case URI_SEARCH: // SQL查询模糊搜索笔记内容
case URI_SEARCH_SUGGEST: // SQL查询模糊搜索笔记内容
// 执行搜索查询
if (sortOrder != null || projection != null) { // SQL查询模糊搜索笔记内容
throw new IllegalArgumentException("Invalid query parameters for search"); // SQL查询模糊搜索笔记内容
} // SQL查询模糊搜索笔记内容
String searchString = null; // SQL查询模糊搜索笔记内容
if (mMatcher.match(uri) == URI_SEARCH_SUGGEST && uri.getPathSegments().size() > 1) { // SQL查询模糊搜索笔记内容
searchString = uri.getPathSegments().get(1); // SQL查询模糊搜索笔记内容
} else { // SQL查询模糊搜索笔记内容
searchString = uri.getQueryParameter("pattern"); // SQL查询模糊搜索笔记内容
} // SQL查询模糊搜索笔记内容
if (TextUtils.isEmpty(searchString)) { // SQL查询模糊搜索笔记内容
return null; // SQL查询模糊搜索笔记内容
} // SQL查询模糊搜索笔记内容
try { // SQL查询模糊搜索笔记内容
searchString = String.format("%%%s%%", searchString); // SQL查询模糊搜索笔记内容
c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, new String[] { searchString }); // SQL查询模糊搜索笔记内容
} catch (IllegalStateException ex) { // SQL查询模糊搜索笔记内容
Log.e(TAG, "Exception during search query: " + ex.toString()); // SQL查询模糊搜索笔记内容
} // SQL查询模糊搜索笔记内容
break; // SQL查询模糊搜索笔记内容
default: // SQL查询模糊搜索笔记内容
throw new IllegalArgumentException("Unknown URI: " + uri); // SQL查询模糊搜索笔记内容
} // SQL查询模糊搜索笔记内容
// SQL查询模糊搜索笔记内容
if (c != null) { // SQL查询模糊搜索笔记内容
c.setNotificationUri(getContext().getContentResolver(), uri); // SQL查询模糊搜索笔记内容
} // SQL查询模糊搜索笔记内容
return c; // SQL查询模糊搜索笔记内容
} // SQL查询模糊搜索笔记内容
// 插入方法
insertedId = noteId = db.insert(TABLE.NOTE, null, values);
break;
case URI_DATA:
if (values.containsKey(DataColumns.NOTE_ID)) {
noteId = values.getAsLong(DataColumns.NOTE_ID);
} else {
Log.d(TAG, "Wrong data format without note id:" + values.toString());
}
insertedId = dataId = db.insert(TABLE.DATA, null, values);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
// Notify the note uri
if (noteId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null);
}
// Notify the data uri
if (dataId > 0) {
getContext().getContentResolver().notifyChange(
ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null);
}
return ContentUris.withAppendedId(uri, insertedId);
}
@Override
public Uri insert(Uri uri, ContentValues values) {// 插入方法
SQLiteDatabase db = mHelper.getWritableDatabase();// 插入方法
long insertedId = 0;// 插入方法
switch (mMatcher.match(uri)) {// 插入方法
case URI_NOTE:// 插入方法
insertedId = db.insert(TABLE.NOTE, null, values); // 插入到笔记表
break;// 插入方法
case URI_DATA:// 插入方法
insertedId = db.insert(TABLE.DATA, null, values); // 插入到数据表
break;// 插入方法
default:// 插入方法
throw new IllegalArgumentException("Unknown URI: " + uri);// 插入方法
}// 插入方法
getContext().getContentResolver().notifyChange(uri, null); // 通知数据变更
return ContentUris.withAppendedId(uri, insertedId);// 插入方法
} // SQL查询模糊搜索笔记内容
// 删除方法
public int delete(Uri uri, String selection, String[] selectionArgs) {
int count = 0;
String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase();
boolean deleteData = false;
switch (mMatcher.match(uri)) {
case URI_NOTE:
selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 ";
count = db.delete(TABLE.NOTE, selection, selectionArgs);
break;
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1);
/**
* ID that smaller than 0 is system folder which is not allowed to
* trash
*/
long noteId = Long.valueOf(id);
if (noteId <= 0) {
break;
}
count = db.delete(TABLE.NOTE,
NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
break;
case URI_DATA:
count = db.delete(TABLE.DATA, selection, selectionArgs);
deleteData = true;
break;
case URI_DATA_ITEM:
id = uri.getPathSegments().get(1);
count = db.delete(TABLE.DATA,
DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs);
deleteData = true;
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (count > 0) {
if (deleteData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) { // SQL查询模糊搜索笔记内容
SQLiteDatabase db = mHelper.getWritableDatabase(); // SQL查询模糊搜索笔记内容
int count = 0; // SQL查询模糊搜索笔记内容
switch (mMatcher.match(uri)) { // SQL查询模糊搜索笔记内容
case URI_NOTE: // SQL查询模糊搜索笔记内容
count = db.delete(TABLE.NOTE, selection, selectionArgs); // SQL查询模糊搜索笔记内容
break; // SQL查询模糊搜索笔记内容
// SQL查询模糊搜索笔记内容
case URI_NOTE_ITEM: // SQL查询模糊搜索笔记内容
count = db.delete(TABLE.NOTE, NoteColumns.ID // SQL查询模糊搜索笔记内容+ "=" + id + parseSelection(selection), selectionArgs);
String id = uri.getPathSegments().get(1);
// count = db.delete(TABLE.NOTE, NoteColumns.ID // SQL查询模糊搜索笔记内容+ "=" + id + parseSelection(selection), selectionArgs);
count = db.delete(TABLE.NOTE, NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs);// SQL查询模糊搜索笔记内容
break;// SQL查询模糊搜索笔记内容
default:// SQL查询模糊搜索笔记内容
throw new IllegalArgumentException("Unknown URI: " + uri);// SQL查询模糊搜索笔记内容
}// SQL查询模糊搜索笔记内容
if (count > 0) {// SQL查询模糊搜索笔记内容
getContext().getContentResolver().notifyChange(uri, null); // 数据变更通知
}// SQL查询模糊搜索笔记内容
return count;// SQL查询模糊搜索笔记内容
}// SQL查询模糊搜索笔记内容
// 更新方法
@Override// SQL查询模糊搜索笔记内容
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {// SQL查询模糊搜索笔记内容
SQLiteDatabase db = mHelper.getWritableDatabase();// SQL查询模糊搜索笔记内容
int count = 0;// SQL查询模糊搜索笔记内容
// SQL查询模糊搜索笔记内容
switch (mMatcher.match(uri)) {// SQL查询模糊搜索笔记内容
case URI_NOTE:// SQL查询模糊搜索笔记内容
count = db.update(TABLE.NOTE, values, selection, selectionArgs);// SQL查询模糊搜索笔记内容
break;// SQL查询模糊搜索笔记内容
default:// SQL查询模糊搜索笔记内容
throw new IllegalArgumentException("Unknown URI: " + uri);// SQL查询模糊搜索笔记内容
}// SQL查询模糊搜索笔记内容
if (count > 0) {// SQL查询模糊搜索笔记内容
getContext().getContentResolver().notifyChange(uri, null);// SQL查询模糊搜索笔记内容
}// SQL查询模糊搜索笔记内容
return count;// SQL查询模糊搜索笔记内容
}// SQL查询模糊搜索笔记内容
// 辅助方法解析selection条件
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int count = 0;
String id = null;
SQLiteDatabase db = mHelper.getWritableDatabase();
boolean updateData = false;
switch (mMatcher.match(uri)) {
case URI_NOTE:
increaseNoteVersion(-1, selection, selectionArgs);
count = db.update(TABLE.NOTE, values, selection, selectionArgs);
break;
case URI_NOTE_ITEM:
id = uri.getPathSegments().get(1);
increaseNoteVersion(Long.valueOf(id), selection, selectionArgs);
count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
break;
case URI_DATA:
count = db.update(TABLE.DATA, values, selection, selectionArgs);
updateData = true;
break;
case URI_DATA_ITEM:
id = uri.getPathSegments().get(1);
count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id
+ parseSelection(selection), selectionArgs);
updateData = true;
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (count > 0) {
if (updateData) {
getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null);
}
getContext().getContentResolver().notifyChange(uri, null);
}
return count;
}
private String parseSelection(String selection) {
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");// SQL查询模糊搜索笔记内容
}// SQL查询模糊搜索笔记内容
// SQL查询模糊搜索笔记内容
return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : "");
}
private void increaseNoteVersion(long id, String selection, String[] selectionArgs) {
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE ");
sql.append(TABLE.NOTE);
sql.append(" SET ");
sql.append(NoteColumns.VERSION);
sql.append("=" + NoteColumns.VERSION + "+1 ");
if (id > 0 || !TextUtils.isEmpty(selection)) {
sql.append(" WHERE ");
}
if (id > 0) {
sql.append(NoteColumns.ID + "=" + String.valueOf(id));
}
if (!TextUtils.isEmpty(selection)) {
String selectString = id > 0 ? parseSelection(selection) : selection;
for (String args : selectionArgs) {
selectString = selectString.replaceFirst("\\?", args);
}
sql.append(selectString);
}
mHelper.getWritableDatabase().execSQL(sql.toString());
}
@Override
public String getType(Uri uri) {// SQL查询模糊搜索笔记内容
return null; // 不使用MIME类型// SQL查询模糊搜索笔记内容
}// SQL查询模糊搜索笔记内容
}// SQL查询模糊搜索笔记内容
public String getType(Uri uri) {
// TODO Auto-generated method stub
return null;
}
}

@ -1,83 +1,81 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)// 定义包名
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");// 定义包名
* you may not use this file except in compliance with the License.// 定义包名
* You may obtain a copy of the License at// 定义包名
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0// 定义包名
*
* Unless required by applicable law or agreed to in writing, software// 定义包名
* distributed under the License is distributed on an "AS IS" BASIS,// 定义包名
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// 定义包名
* See the License for the specific language governing permissions and// 定义包名
* limitations under the License.// 定义包名
*/// 定义包名
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONException;
import org.json.JSONObject;
public class MetaData extends Task {
private final static String TAG = MetaData.class.getSimpleName();
private String mRelatedGid = null;
public void setMeta(String gid, JSONObject metaInfo) {
try {
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid);
} catch (JSONException e) {
Log.e(TAG, "failed to put related gid");
}
setNotes(metaInfo.toString());
setName(GTaskStringUtils.META_NOTE_NAME);
}
public String getRelatedGid() {
return mRelatedGid;
}
@Override
public boolean isWorthSaving() {
return getNotes() != null;
}
@Override
public void setContentByRemoteJSON(JSONObject js) {
super.setContentByRemoteJSON(js);
if (getNotes() != null) {
try {
JSONObject metaInfo = new JSONObject(getNotes().trim());
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID);
} catch (JSONException e) {
Log.w(TAG, "failed to get related gid");
mRelatedGid = null;
}
}
}
@Override
public void setContentByLocalJSON(JSONObject js) {
// this function should not be called
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");
}
@Override
public JSONObject getLocalJSONFromContent() {
throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called");
}
@Override
public int getSyncAction(Cursor c) {
throw new IllegalAccessError("MetaData:getSyncAction should not be called");
}
package net.micode.notes.gtask.data; // 定义包名
// 定义包名
import android.database.Cursor; // 导入Cursor类
import android.util.Log; // 导入Log类
// 定义包名
import net.micode.notes.tool.GTaskStringUtils; // 导入GTaskStringUtils类
// 定义包名
import org.json.JSONException; // 导入JSONException类
import org.json.JSONObject; // 导入JSONObject类
// 定义包名
public class MetaData extends Task { // 定义MetaData类继承自Task类
private final static String TAG = MetaData.class.getSimpleName(); // 定义日志标签
// 定义包名
private String mRelatedGid = null; // 定义一个字符串变量用于存储相关的GID
// 定义包名
// 设置元数据的方法
public void setMeta(String gid, JSONObject metaInfo) {// 定义包名
try {// 定义包名
metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); // 将GID放入metaInfo中// 定义包名
} catch (JSONException e) {// 定义包名
Log.e(TAG, "failed to put related gid"); // 捕获异常并记录错误日志// 定义包名
}// 定义包名
setNotes(metaInfo.toString()); // 将metaInfo转换为字符串并设置为笔记内容// 定义包名
setName(GTaskStringUtils.META_NOTE_NAME); // 设置笔记名称// 定义包名
}// 定义包名
// 定义包名
// 获取相关GID的方法
public String getRelatedGid() {// 定义包名
return mRelatedGid; // 返回相关GID// 定义包名
}// 定义包名
// 定义包名
@Override// 定义包名
public boolean isWorthSaving() {// 定义包名
return getNotes() != null; // 判断笔记内容是否不为空// 定义包名
}// 定义包名
// 定义包名
@Override// 定义包名
public void setContentByRemoteJSON(JSONObject js) {// 定义包名
super.setContentByRemoteJSON(js); // 调用父类方法设置内容// 定义包名
if (getNotes() != null) { // 如果笔记内容不为空// 定义包名
try {// 定义包名
JSONObject metaInfo = new JSONObject(getNotes().trim()); // 将笔记内容转换为JSONObject// 定义包名
mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); // 获取相关GID// 定义包名
} catch (JSONException e) {// 定义包名
Log.w(TAG, "failed to get related gid"); // 捕获异常并记录警告日志// 定义包名
mRelatedGid = null; // 将相关GID设置为空// 定义包名
}// 定义包名
}// 定义包名
}// 定义包名
// 定义包名
@Override// 定义包名
public void setContentByLocalJSON(JSONObject js) {// 定义包名
// 这个方法不应该被调用
throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called");// 定义包名
}// 定义包名
// 定义包名
@Override// 定义包名
public JSONObject getLocalJSONFromContent() {// 定义包名
throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called");// 定义包名
}// 定义包名
// 定义包名
@Override// 定义包名
public int getSyncAction(Cursor c) {// 定义包名
throw new IllegalAccessError("MetaData:getSyncAction should not be called");// 定义包名
}// 定义包名
// 定义包名
}// 定义包名
}

@ -1,101 +1,101 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)//
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");//
* you may not use this file except in compliance with the License.//
* You may obtain a copy of the License at//
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0//
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software//
* distributed under the License is distributed on an "AS IS" BASIS,//
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.//
* See the License for the specific language governing permissions and//
* limitations under the License.//
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data; // 定义包名
package net.micode.notes.gtask.data;
import android.database.Cursor; // 导入Cursor类
import android.database.Cursor;
import org.json.JSONObject; // 导入JSONObject类
import org.json.JSONObject;
public abstract class Node { // 定义抽象类Node
public static final int SYNC_ACTION_NONE = 0; // 定义常量,表示无同步操作
public abstract class Node {
public static final int SYNC_ACTION_NONE = 0;
public static final int SYNC_ACTION_ADD_REMOTE = 1; // 定义常量,表示添加到远程
public static final int SYNC_ACTION_ADD_REMOTE = 1;
public static final int SYNC_ACTION_ADD_LOCAL = 2; // 定义常量,表示添加到本地
public static final int SYNC_ACTION_ADD_LOCAL = 2;
public static final int SYNC_ACTION_DEL_REMOTE = 3; // 定义常量,表示从远程删除
public static final int SYNC_ACTION_DEL_REMOTE = 3;
public static final int SYNC_ACTION_DEL_LOCAL = 4; // 定义常量,表示从本地删除
public static final int SYNC_ACTION_DEL_LOCAL = 4;
public static final int SYNC_ACTION_UPDATE_REMOTE = 5; // 定义常量,表示更新到远程
public static final int SYNC_ACTION_UPDATE_REMOTE = 5;
public static final int SYNC_ACTION_UPDATE_LOCAL = 6; // 定义常量,表示更新到本地
public static final int SYNC_ACTION_UPDATE_LOCAL = 6;
public static final int SYNC_ACTION_UPDATE_CONFLICT = 7; // 定义常量,表示更新冲突
public static final int SYNC_ACTION_UPDATE_CONFLICT = 7;
public static final int SYNC_ACTION_ERROR = 8; // 定义常量,表示同步错误
public static final int SYNC_ACTION_ERROR = 8;
private String mGid; // 定义私有变量mGid存储GID
private String mGid;
private String mName; // 定义私有变量mName存储名称
private String mName;
private long mLastModified; // 定义私有变量mLastModified存储最后修改时间
private long mLastModified;
private boolean mDeleted; // 定义私有变量mDeleted存储删除状态
private boolean mDeleted;
public Node() { // 构造函数,初始化变量
mGid = null; // 初始化mGid为null
mName = ""; // 初始化mName为空字符串
mLastModified = 0; // 初始化mLastModified为0
mDeleted = false; // 初始化mDeleted为false
public Node() {
mGid = null;
mName = "";
mLastModified = 0;
mDeleted = false;
}
public abstract JSONObject getCreateAction(int actionId); // 抽象方法获取创建操作的JSON对象
public abstract JSONObject getCreateAction(int actionId);
public abstract JSONObject getUpdateAction(int actionId); // 抽象方法获取更新操作的JSON对象
public abstract JSONObject getUpdateAction(int actionId);
public abstract void setContentByRemoteJSON(JSONObject js); // 抽象方法通过远程JSON设置内容
public abstract void setContentByRemoteJSON(JSONObject js);
public abstract void setContentByLocalJSON(JSONObject js); // 抽象方法通过本地JSON设置内容
public abstract void setContentByLocalJSON(JSONObject js);
public abstract JSONObject getLocalJSONFromContent(); // 抽象方法从内容获取本地JSON对象
public abstract JSONObject getLocalJSONFromContent();
public abstract int getSyncAction(Cursor c); // 抽象方法,获取同步操作
public abstract int getSyncAction(Cursor c);
public void setGid(String gid) { // 设置GID的方法
public void setGid(String gid) {
this.mGid = gid;
}
public void setName(String name) { // 获取删除状态的方法
public void setName(String name) {
this.mName = name;
}/
/
public void setLastModified(long lastModified) { // 设置最后修改时间的方法
this.mLastModified = lastModified;/
}/
/
public void setDeleted(boolean deleted) { /
}
public void setLastModified(long lastModified) {
this.mLastModified = lastModified;
}
public void setDeleted(boolean deleted) {
this.mDeleted = deleted;
}/
/
public String getGid() { // 获取GID的方法
return this.mGid;/
}/
/
public String getName() { // 获取名称的方法
return this.mName;/
}/
/
public long getLastModified() { // 获取最后修改时间的方法
return this.mLastModified;/
}/ /
/
public boolean getDeleted() { // 获取删除状态的方法
return this.mDeleted;/
}/
/
}/
}
public String getGid() {
return this.mGid;
}
public String getName() {
return this.mName;
}
public long getLastModified() {
return this.mLastModified;
}
public boolean getDeleted() {
return this.mDeleted;
}
}

@ -1,186 +1,189 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)//
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");//
* you may not use this file except in compliance with the License.//
* You may obtain a copy of the License at//
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0//
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software//
* distributed under the License is distributed on an "AS IS" BASIS,//
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.//
* See the License for the specific language governing permissions and//
* limitations under the License.//
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.data;//
import android.content.ContentResolver;//
import android.content.ContentUris;//
import android.content.ContentValues;//
import android.content.Context;//
import android.database.Cursor;//
import android.net.Uri;//
import android.util.Log;//
import net.micode.notes.data.Notes;//
import net.micode.notes.data.Notes.DataColumns;//
import net.micode.notes.data.Notes.DataConstants;//
import net.micode.notes.data.Notes.NoteColumns;//
import net.micode.notes.data.NotesDatabaseHelper.TABLE;//
import net.micode.notes.gtask.exception.ActionFailureException;//
//
import org.json.JSONException;//
import org.json.JSONObject;//
//
package net.micode.notes.gtask.data;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
import net.micode.notes.gtask.exception.ActionFailureException;
import org.json.JSONException;
import org.json.JSONObject;
public class SqlData {
private static final String TAG = SqlData.class.getSimpleName(); // 定义日志标签
//
private static final int INVALID_ID = -99999; // 无效ID常量
//
// 定义查询数据库时所需要的字段
private static final String TAG = SqlData.class.getSimpleName();
private static final int INVALID_ID = -99999;
public static final String[] PROJECTION_DATA = new String[] {
DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1,//
DataColumns.DATA3//
};//
//
// 数据库列的索引
public static final int DATA_ID_COLUMN = 0;//
public static final int DATA_MIME_TYPE_COLUMN = 1;//
public static final int DATA_CONTENT_COLUMN = 2;//
public static final int DATA_CONTENT_DATA_1_COLUMN = 3;//
public static final int DATA_CONTENT_DATA_3_COLUMN = 4;//
//
private ContentResolver mContentResolver; // 用于访问ContentProvider的ContentResolver
private boolean mIsCreate; // 标记当前对象是否是新创建的
private long mDataId; // 数据ID
private String mDataMimeType; // 数据类型
private String mDataContent; // 数据内容
private long mDataContentData1; // 数据内容的附加信息1
private String mDataContentData3; // 数据内容的附加信息3
private ContentValues mDiffDataValues; // 用于存储更改的数据内容
// 构造函数初始化SqlData对象
DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1,
DataColumns.DATA3
};
public static final int DATA_ID_COLUMN = 0;
public static final int DATA_MIME_TYPE_COLUMN = 1;
public static final int DATA_CONTENT_COLUMN = 2;
public static final int DATA_CONTENT_DATA_1_COLUMN = 3;
public static final int DATA_CONTENT_DATA_3_COLUMN = 4;
private ContentResolver mContentResolver;
private boolean mIsCreate;
private long mDataId;
private String mDataMimeType;
private String mDataContent;
private long mDataContentData1;
private String mDataContentData3;
private ContentValues mDiffDataValues;
public SqlData(Context context) {
mContentResolver = context.getContentResolver(); // 获取ContentResolver
mIsCreate = true; // 标记为新创建
mDataId = INVALID_ID; // 初始化为无效ID
mDataMimeType = DataConstants.NOTE; // 默认数据类型为NOTE
mDataContent = ""; // 默认内容为空
mDataContentData1 = 0; // 默认附加信息1为0
mDataContentData3 = ""; // 默认附加信息3为空
mDiffDataValues = new ContentValues(); // 初始化更改数据的ContentValues
}//
//
// 构造函数通过Cur/sor加载数据
mContentResolver = context.getContentResolver();
mIsCreate = true;
mDataId = INVALID_ID;
mDataMimeType = DataConstants.NOTE;
mDataContent = "";
mDataContentData1 = 0;
mDataContentData3 = "";
mDiffDataValues = new ContentValues();
}
public SqlData(Context context, Cursor c) {
mContentResolver = context.getContentResolver(); // 获取ContentResolver
mIsCreate = false; // 标记为已存在数据
loadFromCursor(c); // 从Cursor中加载数据
mDiffDataValues = new ContentValues(); // 初始化更改数据的ContentValues
}//
//
// 从Cursor加载数据
private void loadFromCursor(Cursor c) {//
mDataId = c.getLong(DATA_ID_COLUMN); // 获取数据ID
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); // 获取数据类型
mDataContent = c.getString(DATA_CONTENT_COLUMN); // 获取数据内容
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN); // 获取附加信息1
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); // 获取附加信息3
}//
//
// 设置数据内容
mContentResolver = context.getContentResolver();
mIsCreate = false;
loadFromCursor(c);
mDiffDataValues = new ContentValues();
}
private void loadFromCursor(Cursor c) {
mDataId = c.getLong(DATA_ID_COLUMN);
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN);
mDataContent = c.getString(DATA_CONTENT_COLUMN);
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN);
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN);
}
public void setContent(JSONObject js) throws JSONException {
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID; // 获取ID如果没有则设置为无效ID
if (mIsCreate || mDataId != dataId) { // 如果是新创建的或者ID不相同
mDiffDataValues.put(DataColumns.ID, dataId); // 将ID放入更改数据中
}//
mDataId = dataId; // 更新数据ID
//
String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE)//
: DataConstants.NOTE; // 获取数据类型默认是NOTE
if (mIsCreate || !mDataMimeType.equals(dataMimeType)) { // 如果是新创建的或者类型不相同
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType); // 将数据类型放入更改数据中
}//
mDataMimeType = dataMimeType; // 更新数据类型
//
String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : ""; // 获取内容
if (mIsCreate || !mDataContent.equals(dataContent)) { // 如果是新创建的或者内容不相同
mDiffDataValues.put(DataColumns.CONTENT, dataContent); // 将内容放入更改数据中
}//
mDataContent = dataContent; // 更新内容 // 清空更改的数据
// 清空更改的数据
long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0; // 获取附加信息1
if (mIsCreate || mDataContentData1 != dataContentData1) { // 如果是新创建的或者附加信息1不同
mDiffDataValues.put(DataColumns.DATA1, dataContentData1); // 将附加信息1放入更改数据中
} // 清空更改的数据
mDataContentData1 = dataContentData1; // 更新附加信息1
// 清空更改的数据
String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : ""; // 获取附加信息3
if (mIsCreate || !mDataContentData3.equals(dataContentData3)) { // 如果是新创建的或者附加信息3不同
mDiffDataValues.put(DataColumns.DATA3, dataContentData3); // 将附加信息3放入更改数据中
} // 清空更改的数据
mDataContentData3 = dataContentData3; // 更新附加信息3
} // 清空更改的数据
// 清空更改的数据
// 获取数据内容
public JSONObject getContent() throws JSONException {//
if (mIsCreate) { // 如果是新创建的
Log.e(TAG, "it seems that we haven't created this in database yet"); // 打印错误日志
return null; // 返回null
}// 返回null
JSONObject js = new JSONObject(); // 创建一个新的JSONObject
js.put(DataColumns.ID, mDataId); // 将ID放入JSONObject中
js.put(DataColumns.MIME_TYPE, mDataMimeType); // 将数据类型放入JSONObject中
js.put(DataColumns.CONTENT, mDataContent); // 将内容放入JSONObject中
js.put(DataColumns.DATA1, mDataContentData1); // 将附加信息1放入JSONObject中
js.put(DataColumns.DATA3, mDataContentData3); // 将附加信息3放入JSONObject中
return js; // 返回JSONObject
} // 清空更改的数据
// 清空更改的数据
// 提交更改
public void commit(long noteId, boolean validateVersion, long version) {//
if (mIsCreate) { // 如果是新创建的
if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) { // 如果ID无效并且有ID更改
mDiffDataValues.remove(DataColumns.ID); // 移除ID字段
} // 清空更改的数据
// 清空更改的数据
mDiffDataValues.put(DataColumns.NOTE_ID, noteId); // 将笔记ID放入更改数据中
Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues); // 插入数据到ContentProvider
try { // 清空更改的数据
mDataId = Long.valueOf(uri.getPathSegments().get(1)); // 获取插入数据的ID
} catch (NumberFormatException e) { // 清空更改的数据
Log.e(TAG, "Get note id error :" + e.toString()); // 打印日志
throw new ActionFailureException("create note failed"); // 抛出异常
} // 清空更改的数据
} else { // 如果不是新创建的
if (mDiffDataValues.size() > 0) { // 如果有更改的数据
int result = 0; // 清空更改的数据
if (!validateVersion) { // 如果不需要版本验证 // 清空更改的数据
result = mContentResolver.update(ContentUris.withAppendedId( // 清空更改的数据
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null); // 更新数据
} else { // 如果需要版本验证
result = mContentResolver.update(ContentUris.withAppendedId( // 清空更改的数据
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, // 清空更改的数据
" ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE // 清空更改的数据
+ " WHERE " + NoteColumns.VERSION + "=?)", new String[] { // 清空更改的数据
String.valueOf(noteId), String.valueOf(version) // 清空更改的数据
}); // 清空更改的数据
} // 清空更改的数据
if (result == 0) { // 如果没有更新数据
Log.w(TAG, "there is no update. maybe user updates note when syncing"); // 打印警告日志
} // 清空更改的数据
} // 清空更改的数据
} // 清空更改的数据
// 清空更改的数据
// 清空更改的数据
mDiffDataValues.clear(); // 清空更改的数据
mIsCreate = false; // 标记为非新创建
} // 清空更改的数据
// 清空更改的数据
// 获取数据ID
public long getId() { // 清空更改的数据
return mDataId; // 返回数据ID
} // 清空更改的数据
} // 清空更改的数据
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;
if (mIsCreate || mDataId != dataId) {
mDiffDataValues.put(DataColumns.ID, dataId);
}
mDataId = dataId;
String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE)
: DataConstants.NOTE;
if (mIsCreate || !mDataMimeType.equals(dataMimeType)) {
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType);
}
mDataMimeType = dataMimeType;
String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : "";
if (mIsCreate || !mDataContent.equals(dataContent)) {
mDiffDataValues.put(DataColumns.CONTENT, dataContent);
}
mDataContent = dataContent;
long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0;
if (mIsCreate || mDataContentData1 != dataContentData1) {
mDiffDataValues.put(DataColumns.DATA1, dataContentData1);
}
mDataContentData1 = dataContentData1;
String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : "";
if (mIsCreate || !mDataContentData3.equals(dataContentData3)) {
mDiffDataValues.put(DataColumns.DATA3, dataContentData3);
}
mDataContentData3 = dataContentData3;
}
public JSONObject getContent() throws JSONException {
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
}
JSONObject js = new JSONObject();
js.put(DataColumns.ID, mDataId);
js.put(DataColumns.MIME_TYPE, mDataMimeType);
js.put(DataColumns.CONTENT, mDataContent);
js.put(DataColumns.DATA1, mDataContentData1);
js.put(DataColumns.DATA3, mDataContentData3);
return js;
}
public void commit(long noteId, boolean validateVersion, long version) {
if (mIsCreate) {
if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) {
mDiffDataValues.remove(DataColumns.ID);
}
mDiffDataValues.put(DataColumns.NOTE_ID, noteId);
Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues);
try {
mDataId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
throw new ActionFailureException("create note failed");
}
} else {
if (mDiffDataValues.size() > 0) {
int result = 0;
if (!validateVersion) {
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null);
} else {
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues,
" ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.VERSION + "=?)", new String[] {
String.valueOf(noteId), String.valueOf(version)
});
}
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
}
}
}
mDiffDataValues.clear();
mIsCreate = false;
}
public long getId() {
return mDataId;
}
}

@ -200,13 +200,6 @@ public class SqlNote {
mVersion = c.getLong(VERSION_COLUMN);
}
private void loadDataContent() {
Cursor c = null;
mDataList.clear();

@ -33,357 +33,319 @@ import org.json.JSONObject;
public class Task extends Node {
private static final String TAG = Task.class.getSimpleName(); // Tag for logging
private static final String TAG = Task.class.getSimpleName();
private boolean mCompleted; // Flag indicating task completion status
private boolean mCompleted;
private String mNotes; // Stores notes related to the task
private String mNotes;
private JSONObject mMetaInfo; // Metadata for task
private JSONObject mMetaInfo;
private Task mPriorSibling; // Reference to the previous task in the same list
private Task mPriorSibling;
private TaskList mParent; // Reference to the parent task list
private TaskList mParent;
// Constructor
public Task() {
super();
mCompleted = false; // Initialize task as not completed
mNotes = null; // Initialize notes as null
mPriorSibling = null; // Initialize prior sibling as null
mParent = null; // Initialize parent as null
mMetaInfo = null; // Initialize metadata as null
mCompleted = false;
mNotes = null;
mPriorSibling = null;
mParent = null;
mMetaInfo = null;
}
// Creates a JSON object representing a task creation action
public JSONObject getCreateAction(int actionId) {
JSONObject js = new JSONObject(); // Create a new JSONObject to hold the action details
JSONObject js = new JSONObject();
try {
// Set action type to "create"
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE);
// Set the action ID
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// Set the task index within its parent task list
// index
js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this));
// Set the entity delta which holds the task details
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // Set the task name
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); // Set the creator as null (default)
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");
entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_TASK); // Set entity type to task
GTaskStringUtils.GTASK_JSON_TYPE_TASK);
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); // Add notes if present
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
}
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // Add entity to the action
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
// Set parent ID (the ID of the parent task list)
// parent_id
js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid());
// Set the destination parent type
// dest_parent_type
js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE,
GTaskStringUtils.GTASK_JSON_TYPE_GROUP);
// Set the list ID (the ID of the parent task list)
// list_id
js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid());
// Set prior sibling ID if there's a prior sibling
// prior_sibling_id
if (mPriorSibling != null) {
js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid());
}
} catch (JSONException e) { // Catch JSON exception if any occur
Log.e(TAG, e.toString()); // Log the error
e.printStackTrace(); // Print the stack trace
throw new ActionFailureException("fail to generate task-create jsonobject"); // Throw custom exception
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate task-create jsonobject");
}
return js; // Return the JSON object representing the create action
return js;
}
// Creates a JSON object representing a task update action
public JSONObject getUpdateAction(int actionId) {
JSONObject js = new JSONObject(); // Create a new JSONObject for the update action
JSONObject js = new JSONObject();
try {
// Set action type to "update"
// action_type
js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE,
GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE);
// Set action ID
// action_id
js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId);
// Set task ID
// id
js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());
// Set the entity delta with task details
// entity_delta
JSONObject entity = new JSONObject();
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); // Set the task name
entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());
if (getNotes() != null) {
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); // Add notes if available
entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());
}
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); // Set deletion status
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // Add entity to the update action
entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());
js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);
} catch (JSONException e) { // Catch JSON exception if any occur
Log.e(TAG, e.toString()); // Log the error
e.printStackTrace(); // Print the stack trace
throw new ActionFailureException("fail to generate task-update jsonobject"); // Throw custom exception
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to generate task-update jsonobject");
}
return js; // Return the JSON object representing the update action
return js;
}
// Sets the task content based on a remote JSON object
public void setContentByRemoteJSON(JSONObject js) {
if (js != null) {
try {
// Set task ID if available
// id
if (js.has(GTaskStringUtils.GTASK_JSON_ID)) {
setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID));
}
// Set last modified timestamp if available
// last_modified
if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) {
setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED));
}
// Set task name if available
// name
if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) {
setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME));
}
// Set notes if available
// notes
if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) {
setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES));
}
// Set deletion status if available
// deleted
if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) {
setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED));
}
// Set completion status if available
// completed
if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) {
setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED));
}
} catch (JSONException e) { // Catch JSON exception if any occur
Log.e(TAG, e.toString()); // Log the error
e.printStackTrace(); // Print the stack trace
throw new ActionFailureException("fail to get task content from jsonobject"); // Throw custom exception
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("fail to get task content from jsonobject");
}
}
}
// Sets the task content based on a local JSON object
public void setContentByLocalJSON(JSONObject js) {
if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)
|| !js.has(GTaskStringUtils.META_HEAD_DATA)) {
Log.w(TAG, "setContentByLocalJSON: nothing is available"); // Log a warning if data is not available
Log.w(TAG, "setContentByLocalJSON: nothing is avaiable");
}
try {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // Get the note object
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // Get data array
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
// Check if the note type is valid
if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) {
Log.e(TAG, "invalid type"); // Log an error if the type is invalid
return; // Exit the method
Log.e(TAG, "invalid type");
return;
}
// Iterate through data array and set the task name
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
setName(data.getString(DataColumns.CONTENT)); // Set the task name
setName(data.getString(DataColumns.CONTENT));
break;
}
}
} catch (JSONException e) { // Catch JSON exception if any occur
Log.e(TAG, e.toString()); // Log the error
e.printStackTrace(); // Print the stack trace
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
}
// Returns a local JSON representation of the task content
public JSONObject getLocalJSONFromContent() {
String name = getName(); // Get the task name
String name = getName();
try {
if (mMetaInfo == null) { // If metadata is null, create a new task
// New task created from the web
if (name == null) { // If the task name is null, log a warning
if (mMetaInfo == null) {
// new task created from web
if (name == null) {
Log.w(TAG, "the note seems to be an empty one");
return null; // Return null if the task name is empty
return null;
}
// Create a new JSON object for the task
JSONObject js = new JSONObject();
JSONObject note = new JSONObject();
JSONArray dataArray = new JSONArray();
JSONObject data = new JSONObject();
data.put(DataColumns.CONTENT, name); // Set the task name in the data
dataArray.put(data); // Add data to the array
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); // Add data array to the JSON object
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); // Set note type to "note"
js.put(GTaskStringUtils.META_HEAD_NOTE, note); // Add note object to the JSON object
return js; // Return the newly created JSON object
data.put(DataColumns.CONTENT, name);
dataArray.put(data);
js.put(GTaskStringUtils.META_HEAD_DATA, dataArray);
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
js.put(GTaskStringUtils.META_HEAD_NOTE, note);
return js;
} else {
// Synced task, retrieve data from metadata
JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // Get the note object
JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); // Get data array
// Iterate through data array and update task name
for (int i = 0; i < dataArray.length(); i++) {// Return null if an error occurs
JSONObject data = dataArray.getJSONObject(i);// Return null if an error occurs
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {// Return null if an error occurs
data.put(DataColumns.CONTENT, getName()); // Set the task name in data
break;// Return null if an error occurs
}// Return null if an error occurs
}// Return null if an error occurs
// Return null if an error occurs
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); // Set note type to "note"
return mMetaInfo; // Return the metadata as the JSON object
// synced task
JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {
data.put(DataColumns.CONTENT, getName());
break;
}
}
note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
return mMetaInfo;
}
} catch (JSONException e) { // Catch JSON exception if any occur
Log.e(TAG, e.toString()); // Log the error
e.printStackTrace(); // Print the stack trace
return null; // Return null if an error occurs
}// Return null if an error occurs
}// Return null if an error occurs
}// Return null if an error occurs
// Return null if an error occurs
// Return null if an error occurs
// Return null if an error occurs
// Return null if an error occurs
// Return null if an error occurs
// Return null if an error occurs
// Method to set meta information using a MetaData object
public void setMetaInfo(MetaData metaData) {
// Check if the metaData is not null and contains notes
if (metaData != null && metaData.getNotes() != null) {
try {
// Convert the notes data into a JSONObject and assign it to mMetaInfo
mMetaInfo = new JSONObject(metaData.getNotes());
} catch (JSONException e) {
// Log a warning if the conversion fails
Log.w(TAG, e.toString());
// Reset mMetaInfo to null in case of an error
mMetaInfo = null; // Reset mMetaInfo to null in case of an error
} // Reset mMetaInfo to null in case of an error
} // Reset mMetaInfo to null in case of an error
} // Reset mMetaInfo to null in case of an error
// Reset mMetaInfo to null in case of an error
// Method to determine the sync action based on the data in the cursor
public int getSyncAction(Cursor c) { // Reset mMetaInfo to null in case of an error
try { // Reset mMetaInfo to null in case of an error
JSONObject noteInfo = null; // Reset mMetaInfo to null in case of an error
// Reset mMetaInfo to null in case of an error
// Check if mMetaInfo is not null and contains the note info
if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) { // Reset mMetaInfo to null in case of an error
// Retrieve the note info from mMetaInfo
noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // Reset mMetaInfo to null in case of an error
} // Reset mMetaInfo to null in case of an error
// Reset mMetaInfo to null in case of an error
// If no note info is found, return SYNC_ACTION_UPDATE_REMOTE
if (noteInfo == null) { // Reset mMetaInfo to null in case of an error
Log.w(TAG, "it seems that note meta has been deleted"); // Reset mMetaInfo to null in case of an error
return SYNC_ACTION_UPDATE_REMOTE; // Reset mMetaInfo to null in case of an error
} // Reset mMetaInfo to null in case of an error
// Reset mMetaInfo to null in case of an error
// If no note ID is found in the note info, return SYNC_ACTION_UPDATE_LOCAL
if (!noteInfo.has(NoteColumns.ID)) {
Log.w(TAG, "remote note id seems to be deleted");
return SYNC_ACTION_UPDATE_LOCAL;
} // Reset mMetaInfo to null in case of an error
// Reset mMetaInfo to null in case of an error
// Validate the note ID with the value in the cursor
if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) { // Reset mMetaInfo to null in case of an error
Log.w(TAG, "note id doesn't match"); // Reset mMetaInfo to null in case of an error
return SYNC_ACTION_UPDATE_LOCAL; // Reset mMetaInfo to null in case of an error
// Reset mMetaInfo to null in case of an error
// Check if there are any local updates
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// If no local updates, check if the sync ID matches the last modified timestamp
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// No updates on either side // Reset mMetaInfo to null in case of an error
return SYNC_ACTION_NONE; // Reset mMetaInfo to null in case of an error
} else { // Reset mMetaInfo to null in case of an error
// Apply remote updates to local // Reset mMetaInfo to null in case of an error
return SYNC_ACTION_UPDATE_LOCAL; // Reset mMetaInfo to null in case of an error
} // Reset mMetaInfo to null in case of an error
} else { // Reset mMetaInfo to null in case of an error
// If there are local modifications, // Reset mMetaInfo to null in case of an errorvalidate the gtask ID
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { // Reset mMetaInfo to null in case of an error
Log.e(TAG, "gtask id doesn't match"); // Reset mMetaInfo to null in case of an error
return SYNC_ACTION_ERROR; // Reset mMetaInfo to null in case of an error
} // Reset mMetaInfo to null in case of an error
// Check if the sync ID matches the last modified timestamp for local modification
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { // Reset mMetaInfo to null in case of an error
// Local modification only
return SYNC_ACTION_UPDATE_REMOTE; // Reset mMetaInfo to null in case of an error
// Conflict between local and remote modification
return SYNC_ACTION_UPDATE_CONFLICT; // Reset mMetaInfo to null in case of an error
} // Reset mMetaInfo to null in case of an error
} // Reset mMetaInfo to null in case of an error
} catch (Exception e) { // Reset mMetaInfo to null in case of an error
// Log the exception and return SYNC_ACTION_ERROR
Log.e(TAG, e.toString()); // Reset mMetaInfo to null in case of an error
e.printStackTrace(); // Reset mMetaInfo to null in case of an error
} // Reset mMetaInfo to null in case of an error
// Reset mMetaInfo to null in case of an error
return SYNC_ACTION_ERROR; // Reset mMetaInfo to null in case of an error
} // Reset mMetaInfo to null in case of an error
// Getter method to retrieve the parent task list
// Method to check if the task is worth saving based on meta info or task content
public boolean isWorthSaving() { // Reset mMetaInfo to null in case of an error
// If there is meta info or non-empty name or notes, return true
return mMetaInfo != null || (getName() != null && getName().trim().length() > 0)// Getter method to retrieve the parent task list
|| (getNotes() != null && getNotes().trim().length() > 0);// Getter method to retrieve the parent task list
} // Reset mMetaInfo to null in case of an error
// Reset mMetaInfo to null in case of an error
// Setter method to mark the task as completed
public void setCompleted(boolean completed) { // Reset mMetaInfo to null in case of an error
this.mCompleted = completed;// Getter method to retrieve the parent task list
}// Getter method to retrieve the parent task list
// Getter method to retrieve the parent task list
// Setter method to set the notes of the task
public void setNotes(String notes) {// Getter method to retrieve the parent task list
this.mNotes = notes;// Getter method to retrieve the parent task list
}// Getter method to retrieve the parent task list
// Getter method to retrieve the parent task list
// Setter method to set the prior sibling task
public void setPriorSibling(Task priorSibling) {// Getter method to retrieve the parent task list
this.mPriorSibling = priorSibling;// Getter method to retrieve the parent task list
}// Getter method to retrieve the parent task list
// Getter method to retrieve the parent task list
// Setter method to set the parent task list
public void setParent(TaskList parent) {// Getter method to retrieve the parent task list
this.mParent = parent;// Getter method to retrieve the parent task list
}// Getter method to retrieve the parent task list
// Getter method to retrieve the parent task list
// Getter method to retrieve the completed status of the task
public boolean getCompleted() {// Getter method to retrieve the parent task list
return this.mCompleted;// Getter method to retrieve the parent task list
}// Getter method to retrieve the parent task list
// Getter method to retrieve the parent task list
// Getter method to retrieve the notes of the task
public String getNotes() {// Getter method to retrieve the parent task list
return this.mNotes;// Getter method to retrieve the parent task list
}// Getter method to retrieve the parent task list
// Getter method to retrieve the parent task list
// Getter method to retrieve the prior sibling task
public Task getPriorSibling() {// Getter method to retrieve the parent task list
return this.mPriorSibling;// Getter method to retrieve the parent task list
}// Getter method to retrieve the parent task list
// Getter method to retrieve the parent task list
// Getter method to retrieve the parent task list
public TaskList getParent() {// Getter method to retrieve the parent task list
return this.mParent;// Getter method to retrieve the parent task list
Log.e(TAG, e.toString());
e.printStackTrace();
return null;
}
}
public void setMetaInfo(MetaData metaData) {
if (metaData != null && metaData.getNotes() != null) {
try {
mMetaInfo = new JSONObject(metaData.getNotes());
} catch (JSONException e) {
Log.w(TAG, e.toString());
mMetaInfo = null;
}
}
}
public int getSyncAction(Cursor c) {
try {
JSONObject noteInfo = null;
if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) {
noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
}
if (noteInfo == null) {
Log.w(TAG, "it seems that note meta has been deleted");
return SYNC_ACTION_UPDATE_REMOTE;
}
if (!noteInfo.has(NoteColumns.ID)) {
Log.w(TAG, "remote note id seems to be deleted");
return SYNC_ACTION_UPDATE_LOCAL;
}
// validate the note id now
if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) {
Log.w(TAG, "note id doesn't match");
return SYNC_ACTION_UPDATE_LOCAL;
}
if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) {
// there is no local update
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// no update both side
return SYNC_ACTION_NONE;
} else {
// apply remote to local
return SYNC_ACTION_UPDATE_LOCAL;
}
} else {
// validate gtask id
if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) {
Log.e(TAG, "gtask id doesn't match");
return SYNC_ACTION_ERROR;
}
if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) {
// local modification only
return SYNC_ACTION_UPDATE_REMOTE;
} else {
return SYNC_ACTION_UPDATE_CONFLICT;
}
}
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
}
return SYNC_ACTION_ERROR;
}
public boolean isWorthSaving() {
return mMetaInfo != null || (getName() != null && getName().trim().length() > 0)
|| (getNotes() != null && getNotes().trim().length() > 0);
}
public void setCompleted(boolean completed) {
this.mCompleted = completed;
}
public void setNotes(String notes) {
this.mNotes = notes;
}
public void setPriorSibling(Task priorSibling) {
this.mPriorSibling = priorSibling;
}
public void setParent(TaskList parent) {
this.mParent = parent;
}
public boolean getCompleted() {
return this.mCompleted;
}
public String getNotes() {
return this.mNotes;
}
public Task getPriorSibling() {
return this.mPriorSibling;
}
public TaskList getParent() {
return this.mParent;
}
}

@ -1,11 +1,11 @@
/*
* Copyright (c) 2010 - 2011, The MiCode Open Source Community (www.micode.net)
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE 2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@ -14,33 +14,20 @@
* limitations under the License.
*/
// 包声明表明该类所在的包名为net.micode.notes.gtask.exception通常用于存放自定义的异常相关类
package net.micode.notes.gtask.exception;
// ActionFailureException类继承自RuntimeException意味着它是一个运行时异常不需要在代码中显式地进行捕获处理当然也可以捕获
// 一般用于表示在执行某个操作时发生了失败的情况,具体的失败原因可以通过构造函数传入相关信息来表示
public class ActionFailureException extends RuntimeException {
// serialVersionUID是用于序列化和反序列化对象时的版本标识用于确保在不同版本的类加载过程中对象的兼容性
// 这里定义了一个固定的长整型值作为该异常类的版本标识
private static final long serialVersionUID = 4425249765923293627L;
// 默认的无参构造函数调用父类RuntimeException的无参构造函数
// 通常在抛出异常时如果不需要传递特定的错误信息可以使用这个构造函数创建异常对象
public ActionFailureException() {
super();
}
// 带有一个字符串参数的构造函数,用于创建异常对象并传递具体的错误信息描述,
// 这个字符串信息会在异常被捕获或者打印堆栈信息时展示出来,帮助定位和理解发生异常的原因,
// 它调用了父类RuntimeException的相应构造函数来传递错误信息字符串
public ActionFailureException(String paramString) {
super(paramString);
}
// 带有一个字符串参数和一个Throwable参数的构造函数用于创建异常对象
// 字符串参数用于传递具体的错误信息描述Throwable参数通常用于传递导致当前异常发生的底层异常比如嵌套的其他异常
// 这样可以更全面地展示异常的产生链方便排查问题它调用了父类RuntimeException的相应构造函数来传递这两个参数
public ActionFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable);
}
}
}

@ -1,11 +1,11 @@
/*
* Copyright (c) 2010 - 2011, The MiCode Open Source Community (www.micode.net)
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE 2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@ -14,34 +14,20 @@
* limitations under the License.
*/
// 包声明表明该类所在的包名为net.micode.notes.gtask.exception通常这个包会用于存放和GTask相关的各类异常类定义
package net.micode.notes.gtask.exception;
// NetworkFailureException类继承自Exception属于受检异常Checked Exception
// 在使用可能抛出该异常的方法时,必须在方法签名中声明或者在调用处进行捕获处理,
// 此类异常通常用于表示在网络相关操作过程中出现了故障或错误的情况
public class NetworkFailureException extends Exception {
// serialVersionUID是一个用于在序列化和反序列化对象时进行版本控制的标识
// 确保在不同版本的类加载过程中,对象的序列化和反序列化能够正确进行,这里给定了一个固定的长整型值作为该异常类的版本标识
private static final long serialVersionUID = 2107610287180234136L;
// 无参构造函数调用父类Exception的无参构造函数
// 当需要抛出一个NetworkFailureException异常但不需要提供具体的错误信息时可以使用这个构造函数来创建异常对象
public NetworkFailureException() {
super();
}
// 带有一个字符串参数的构造函数,用于创建异常对象并传递具体的错误信息描述,
// 这个字符串会作为异常的详细信息展示出来,方便在捕获异常时了解出现网络故障的具体原因等情况,
// 它调用了父类Exception的对应构造函数来传递这个错误信息字符串
public NetworkFailureException(String paramString) {
super(paramString);
}
// 带有一个字符串参数和一个Throwable参数的构造函数
// 其中字符串参数用于指定具体的错误信息描述而Throwable参数通常用于传递导致当前网络故障异常的底层异常例如底层网络库抛出的原始异常等
// 这样可以更全面地展示异常产生的根源便于排查和处理问题它调用了父类Exception的相应构造函数来传递这两个参数
public NetworkFailureException(String paramString, Throwable paramThrowable) {
super(paramString, paramThrowable);
}
}
}

@ -1,11 +1,12 @@
/*
* Copyright (c) 2010 - 2011, The MiCode Open Source Community (www.micode.net)
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE 2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@ -14,159 +15,110 @@
* limitations under the License.
*/
// 包声明表明该类所在的包名这里是net.micode.notes.gtask.remote包下
package net.micode.notes.gtask.remote;
import android.app.Notification;
// 用于管理通知相关操作,比如创建、显示、取消通知等
import android.app.NotificationManager;
// 用于创建PendingIntent它可以在未来某个时间点触发一个Intent操作
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
// 用于执行异步任务,避免在主线程执行耗时操作导致界面卡顿
import android.os.AsyncTask;
import net.micode.notes.R;
// 笔记列表界面的Activity类可能用于展示笔记列表等相关功能
import net.micode.notes.ui.NotesListActivity;
// 笔记相关的偏好设置Activity类可能用于配置笔记应用的一些参数等功能
import net.micode.notes.ui.NotesPreferenceActivity;
// GTaskASyncTask继承自AsyncTask用于在后台执行与GTask相关的异步操作
// 并在操作过程中更新进度、处理完成后的结果以及进行相应的通知展示等
public class GTaskASyncTask extends AsyncTask<Void, String, Integer> {
// 定义一个静态的整型变量用作GTask同步操作的通知ID用于唯一标识一个通知
private static int GTASK_SYNC_NOTIFICATION_ID = 5234235;
// 定义一个接口,用于在异步任务完成时回调通知外部,让外部可以执行相应的逻辑
public interface OnCompleteListener {
void onComplete();
}
// 保存上下文对象,方便后续获取系统服务、资源等操作
private Context mContext;
// 用于管理通知的显示、取消等操作,通过上下文获取系统的通知服务实例
private NotificationManager mNotifiManager;
// GTaskManager实例应该是用于管理GTask相关业务逻辑比如同步操作等
private GTaskManager mTaskManager;
// 完成监听器外部可以传入实现了OnCompleteListener接口的对象以便在任务完成时得到通知
private OnCompleteListener mOnCompleteListener;
// 构造函数用于初始化GTaskASyncTask实例
// 参数context是当前上下文listener是任务完成时的监听器
public GTaskASyncTask(Context context, OnCompleteListener listener) {
mContext = context;
mOnCompleteListener = listener;
// 获取系统的通知服务,用于后续管理通知的显示等操作
mNotifiManager = (NotificationManager) mContext
.getSystemService(Context.NOTIFICATION_SERVICE);
// 获取GTaskManager的单例实例用于执行具体的GTask相关任务
mTaskManager = GTaskManager.getInstance();
}
// 用于取消正在进行的GTask同步操作调用GTaskManager中的取消同步方法
public void cancelSync() {
mTaskManager.cancelSync();
}
// 对外提供的方法用于发布任务进度消息实际上是调用了publishProgress方法传递消息数组
public void publishProgess(String message) {
publishProgress(new String[] {
message
message
});
}
// 私有方法,用于显示通知
// 参数tickerId是通知在状态栏显示的简短提示文本的资源IDcontent是通知详细内容
private void showNotification(int tickerId, String content) {
// 创建一个Notification实例设置图标、标题通过资源ID获取对应字符串以及显示时间
Notification notification = new Notification(R.drawable.notification, mContext
.getString(tickerId), System.currentTimeMillis());
// 设置通知的默认灯光效果,当有通知时会亮起相应的灯光提示
notification.defaults = Notification.DEFAULT_LIGHTS;
// 设置通知为自动取消,当用户点击通知后,该通知自动消失
notification.flags = Notification.FLAG_AUTO_CANCEL;
PendingIntent pendingIntent;
// 根据不同的tickerId来决定点击通知后跳转到不同的Activity
if (tickerId!= R.string.ticker_success) {
// 如果不是成功提示的tickerId则点击通知跳转到NotesPreferenceActivity
if (tickerId != R.string.ticker_success) {
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesPreferenceActivity.class), 0);
} else {
// 如果是成功提示的tickerId则点击通知跳转到NotesListActivity
pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext,
NotesListActivity.class), 0);
}
// notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content,
// pendingIntent);
// 设置通知的点击意图,即用户点击通知后会触发的操作
notification.contentIntent = pendingIntent;
// 通过通知管理器显示通知使用之前定义的通知ID来标识这个通知
mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification);
}
// 在后台线程执行的方法是AsyncTask抽象方法执行实际的耗时任务
// 参数unused在这里未被使用一般用于传递执行任务需要的参数
@Override
protected Integer doInBackground(Void... unused) {
// 发布任务进度消息消息内容是正在登录进行同步的提示包含同步账号名称通过NotesPreferenceActivity获取
publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity
.getSyncAccountName(mContext)));
// 调用GTaskManager的sync方法进行同步操作并返回同步结果同步过程中可以通过publishProgess方法更新进度
return mTaskManager.sync(mContext, this);
}
// 在主线程执行的方法用于更新任务进度是AsyncTask抽象方法
// 参数progress是传递过来的进度消息数组这里只取第一个元素作为进度消息展示
@Override
protected void onProgressUpdate(String... progress) {
// 根据同步中的进度消息显示相应的通知,通知标题为正在同步的提示文本
showNotification(R.string.ticker_syncing, progress[0]);
// 如果当前上下文是GTaskSyncService类型可能是在特定的服务中使用该异步任务
if (mContext instanceof GTaskSyncService) {
// 发送广播,传递进度消息,可能用于通知其他组件当前同步进度情况
((GTaskSyncService) mContext).sendBroadcast(progress[0]);
}
}
// 在主线程执行的方法在异步任务完成后调用是AsyncTask抽象方法
// 参数result是异步任务执行的最终结果根据不同的结果值进行不同的处理
@Override
protected void onPostExecute(Integer result) {
// 如果同步结果是成功状态
if (result == GTaskManager.STATE_SUCCESS) {
// 显示成功通知通知标题为成功提示文本内容包含同步成功的账号信息通过GTaskManager获取
showNotification(R.string.ticker_success, mContext.getString(
R.string.success_sync_account, mTaskManager.getSyncAccount()));
// 设置最后同步时间记录到NotesPreferenceActivity中参数为当前时间戳
NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis());
} else if (result == GTaskManager.STATE_NETWORK_ERROR) {
// 如果是网络错误状态,显示网络错误通知,通知标题为失败提示文本,内容为网络错误提示信息
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network));
} else if (result == GTaskManager.STATE_INTERNAL_ERROR) {
// 如果是内部错误状态,显示内部错误通知,通知标题为失败提示文本,内容为内部错误提示信息
showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal));
} else if (result == GTaskManager.STATE_SYNC_CANCELLED) {
// 如果是同步被取消状态,显示取消通知,通知标题为取消提示文本,内容为同步取消提示信息
showNotification(R.string.ticker_cancel, mContext
.getString(R.string.error_sync_cancelled));
}
// 如果设置了任务完成监听器
if (mOnCompleteListener!= null) {
if (mOnCompleteListener != null) {
new Thread(new Runnable() {
public void run() {
// 在新线程中调用监听器的onComplete方法通知外部任务已完成
mOnCompleteListener.onComplete();
}
}).start();
}
}
}
}

@ -13,81 +13,80 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
// 导入所需的Android类和自定义类
import android.app.Activity;// 导入所需的Android类和自定义类
import android.content.ContentResolver;// 导入所需的Android类和自定义类
import android.content.ContentUris;// 导入所需的Android类和自定义类
import android.content.ContentValues;// 导入所需的Android类和自定义类
import android.content.Context;// 导入所需的Android类和自定义类
import android.database.Cursor;// 导入所需的Android类和自定义类
import android.util.Log;// 导入所需的Android类和自定义类
// 导入所需的Android类和自定义类
import net.micode.notes.R;// 导入所需的Android类和自定义类
import net.micode.notes.data.Notes;// 导入所需的Android类和自定义类
import net.micode.notes.data.Notes.DataColumns;// 导入所需的Android类和自定义类
import net.micode.notes.data.Notes.NoteColumns;// 导入所需的Android类和自定义类
import net.micode.notes.gtask.data.MetaData;// 导入所需的Android类和自定义类
import net.micode.notes.gtask.data.Node;// 导入所需的Android类和自定义类
import net.micode.notes.gtask.data.SqlNote;// 导入所需的Android类和自定义类
import net.micode.notes.gtask.data.Task;// 导入所需的Android类和自定义类
import net.micode.notes.gtask.data.TaskList;// 导入所需的Android类和自定义类
import net.micode.notes.gtask.exception.ActionFailureException;// 导入所需的Android类和自定义类
import net.micode.notes.gtask.exception.NetworkFailureException;// 导入所需的Android类和自定义类
import net.micode.notes.tool.DataUtils;// 导入所需的Android类和自定义类
import net.micode.notes.tool.GTaskStringUtils;// 导入所需的Android类和自定义类
import org.json.JSONArray;// 导入所需的Android类和自定义类
import org.json.JSONException;// 导入所需的Android类和自定义类
import org.json.JSONObject;// 导入所需的Android类和自定义类
import java.util.HashMap;// 导入所需的Android类和自定义类// 导入所需的Android类和自定义类
import java.util.HashSet;// 导入所需的Android类和自定义类
import java.util.Iterator;// 导入所需的Android类和自定义类
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.data.MetaData;
import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.SqlNote;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
// GTaskManager类是管理Google任务同步的核心类
public class GTaskManager {
// 日志标签用于Logcat中过滤日志
private static final String TAG = GTaskManager.class.getSimpleName();
// 定义同步状态常量
public static final int STATE_SUCCESS = 0;
public static final int STATE_NETWORK_ERROR = 1;
public static final int STATE_INTERNAL_ERROR = 2;
public static final int STATE_SYNC_IN_PROGRESS = 3;
public static final int STATE_SYNC_CANCELLED = 4;
// 单例对象
private static GTaskManager mInstance = null;
// Activity上下文用于获取认证token
private Activity mActivity;
// 应用程序上下文
private Context mContext;
// 内容解析器,用于访问和修改数据库
private ContentResolver mContentResolver;
// 同步状态标志
private boolean mSyncing;
// 取消同步标志
private boolean mCancelled;
// 存储任务列表的哈希映射
private HashMap<String, TaskList> mGTaskListHashMap;
// 存储任务节点的哈希映射
private HashMap<String, Node> mGTaskHashMap;
// 存储元数据的哈希映射
private HashMap<String, MetaData> mMetaHashMap;
// 元数据列表
private TaskList mMetaList;
// 本地删除ID集合
private HashSet<Long> mLocalDeleteIdMap;
// 任务ID到笔记ID的映射
private HashMap<String, Long> mGidToNid;
// 笔记ID到任务ID的映射
private HashMap<Long, String> mNidToGid;
// 私有构造函数,确保单例
private GTaskManager() {
mSyncing = false;
mCancelled = false;
@ -100,7 +99,6 @@ public class GTaskManager {
mNidToGid = new HashMap<Long, String>();
}
// 获取GTaskManager单例
public static synchronized GTaskManager getInstance() {
if (mInstance == null) {
mInstance = new GTaskManager();
@ -108,15 +106,12 @@ public class GTaskManager {
return mInstance;
}
// 设置Activity上下文
public synchronized void setActivityContext(Activity activity) {
// 用于获取认证token
// used for getting authtoken
mActivity = activity;
}
// 同步方法,接受上下文和异步任务参数
public int sync(Context context, GTaskASyncTask asyncTask) {
// 如果正在同步中,则返回同步进行中状态
if (mSyncing) {
Log.d(TAG, "Sync is in progress");
return STATE_SYNC_IN_PROGRESS;
@ -133,22 +128,21 @@ public class GTaskManager {
mNidToGid.clear();
try {
// 获取GTaskClient单例
GTaskClient client = GTaskClient.getInstance();
client.resetUpdateArray();
// 登录Google任务
// login google task
if (!mCancelled) {
if (!client.login(mActivity)) {
throw new NetworkFailureException("login google task failed");
}
}
// 从Google获取任务列表
// get the task list from google
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list));
initGTaskList();
// 执行内容同步工作
// do content sync work
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing));
syncContent();
} catch (NetworkFailureException e) {
@ -174,27 +168,26 @@ public class GTaskManager {
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS;
}
// 初始化Google任务列表
private void initGTaskList() throws NetworkFailureException {
if (mCancelled) // 初始化Google任务列表
return; // 初始化Google任务列表
GTaskClient client = GTaskClient.getInstance(); // 初始化Google任务列表
try { // 初始化Google任务列表
JSONArray jsTaskLists = client.getTaskLists(); // 初始化Google任务列表
// 初始化Google任务列表
// 首先初始化元数据列表
if (mCancelled)
return;
GTaskClient client = GTaskClient.getInstance();
try {
JSONArray jsTaskLists = client.getTaskLists();
// init meta list first
mMetaList = null;
for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i);
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
// 检查是否是元数据文件夹
if (name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
if (name
.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
mMetaList = new TaskList();
mMetaList.setContentByRemoteJSON(object);
// 加载元数据
// load meta data
JSONArray jsMetas = client.getTaskList(gid);
for (int j = 0; j < jsMetas.length(); j++) {
object = (JSONObject) jsMetas.getJSONObject(j);
@ -209,17 +202,6 @@ public class GTaskManager {
}
}
}
}
// 处理JSON异常
catch (JSONException e) {
throw new NetworkFailureException("JSONException in initGTaskList");
}
}
} }
}
}
}
}
// create meta list if not existed
if (mMetaList == null) {

@ -1,11 +1,11 @@
/*
* Copyright (c) 2010 - 2011, The MiCode Open Source Community (www.micode.net)
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE 2.0
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
@ -14,151 +14,97 @@
* limitations under the License.
*/
// 包声明表明该类所在的包名为net.micode.notes.gtask.remote
package net.micode.notes.gtask.remote;
import android.app.Activity;
import android.app.Service;
// 用于创建和管理Android中的服务服务可以在后台执行长时间运行的操作不提供用户界面
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
// 用于在组件间传递数据例如在Activity、Service之间传递参数等以键值对的形式存储数据
import android.os.IBinder;
// GTaskSyncService继承自Service类是一个用于处理与GTask相关的同步操作的服务类
// 比如启动同步、取消同步以及发送同步相关广播等功能
public class GTaskSyncService extends Service {
// 定义一个字符串常量作为在Intent中传递同步操作类型的键用于区分不同的同步相关动作
public final static String ACTION_STRING_NAME = "sync_action_type";
// 定义一个整型常量表示启动同步的操作类型对应的值为0用于在传递操作类型时使用
public final static int ACTION_START_SYNC = 0;
// 定义一个整型常量表示取消同步的操作类型对应的值为1用于在传递操作类型时使用
public final static int ACTION_CANCEL_SYNC = 1;
// 定义一个整型常量表示无效的操作类型对应的值为2用于在一些不符合预期的情况判断中使用
public final static int ACTION_INVALID = 2;
// 定义一个字符串常量作为广播的Action名称用于标识该服务发送的广播其他组件可以通过监听这个Action来接收广播
public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service";
// 定义一个字符串常量作为广播中传递是否正在同步状态的键用于在广播Intent中传递同步状态信息
public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing";
// 定义一个字符串常量作为广播中传递同步进度消息的键用于在广播Intent中传递同步进度相关的文本信息
public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg";
// 静态变量用于保存当前正在执行的GTaskASyncTask实例初始化为null表示当前没有正在进行的同步任务
private static GTaskASyncTask mSyncTask = null;
// 静态变量,用于保存同步进度相关的消息内容,初始化为空字符串,后续会根据同步情况更新该内容并通过广播发送出去
private static String mSyncProgress = "";
// 私有方法,用于启动同步任务
private void startSync() {
// 判断当前是否没有正在执行的同步任务mSyncTask为null如果是则创建一个新的GTaskASyncTask实例来执行同步操作
if (mSyncTask == null) {
// 创建GTaskASyncTask实例传入当前服务的上下文this以及一个实现了OnCompleteListener接口的匿名内部类对象
mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() {
// 实现OnCompleteListener接口的onComplete方法当同步任务完成时会被调用
public void onComplete() {
// 将mSyncTask置为null表示同步任务已结束
mSyncTask = null;
// 发送一个空消息的广播,可能用于通知其他组件同步已完成等情况
sendBroadcast("");
// 停止当前服务,因为同步任务已经完成,服务不再需要继续运行
stopSelf();
}
});
// 发送一个空消息的广播,可能用于通知其他组件即将开始同步等情况
sendBroadcast("");
// 执行GTaskASyncTask实例的execute方法启动异步任务开始执行同步操作
mSyncTask.execute();
}
}
// 私有方法,用于取消正在进行的同步任务
private void cancelSync() {
// 判断如果当前存在正在执行的同步任务mSyncTask不为null则调用GTaskASyncTask的cancelSync方法取消同步
if (mSyncTask!= null) {
if (mSyncTask != null) {
mSyncTask.cancelSync();
}
}
// 重写Service的onCreate方法该方法在服务创建时被调用这里将mSyncTask初始化为null
// 确保每次服务启动时都处于没有正在执行同步任务的初始状态
@Override
public void onCreate() {
mSyncTask = null;
}
// 重写Service的onStartCommand方法该方法在每次服务接收到启动请求通过startService方法启动服务时被调用
// 在这里根据传入的Intent中的参数来决定执行启动同步还是取消同步等操作并返回服务的启动模式相关标识
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 从传入的Intent中获取携带的额外数据以Bundle形式存储这些数据可能包含了同步操作类型等信息
Bundle bundle = intent.getExtras();
// 判断获取到的Bundle不为null并且其中包含了用于标识同步操作类型的键ACTION_STRING_NAME
if (bundle!= null && bundle.containsKey(ACTION_STRING_NAME)) {
// 根据获取到的同步操作类型的值进行不同的操作通过switch语句进行判断
if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) {
switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) {
// 如果操作类型是启动同步ACTION_START_SYNC
case ACTION_START_SYNC:
// 调用startSync方法启动同步任务
startSync();
break;
// 如果操作类型是取消同步ACTION_CANCEL_SYNC
case ACTION_CANCEL_SYNC:
// 调用cancelSync方法取消正在进行的同步任务
cancelSync();
break;
default:
// 如果是其他未定义的操作类型不做任何处理直接跳出switch语句
break;
}
// 返回START_STICKY表示服务在被系统强制关闭后例如内存不足等情况会尝试重新创建并启动
// 但不会重新传递上次的Intent而是以空Intent启动服务需要自行处理这种情况来恢复到合适的状态
return START_STICKY;
}
// 如果传入的Intent不符合预期没有包含操作类型相关参数等情况则调用父类的onStartCommand方法进行默认处理
return super.onStartCommand(intent, flags, startId);
}
// 重写Service的onLowMemory方法该方法在系统内存不足时被调用在这里用于取消正在进行的同步任务
// 以释放内存资源,避免服务占用过多内存导致系统出现问题
@Override
public void onLowMemory() {
if (mSyncTask!= null) {
if (mSyncTask != null) {
mSyncTask.cancelSync();
}
}
// 重写Service的onBind方法用于处理服务的绑定操作这里返回null表示该服务不支持绑定操作
// 即其他组件不能通过bindService方法来绑定这个服务获取IBinder对象进行交互
public IBinder onBind(Intent intent) {
return null;
}
// 用于发送同步相关的广播,广播中包含了是否正在同步的状态以及同步进度消息等信息
public void sendBroadcast(String msg) {
// 更新同步进度消息内容将传入的msg赋值给mSyncProgress变量
mSyncProgress = msg;
// 创建一个Intent对象设置其Action为之前定义的广播Action名称GTASK_SERVICE_BROADCAST_NAME
// 用于标识这个广播是由该服务发出的同步相关广播
Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME);
// 在Intent中添加额外数据通过键GTASK_SERVICE_BROADCAST_IS_SYNCING来传递当前是否正在同步的状态
// 根据mSyncTask是否为null来判断null表示没有正在进行的同步任务非null则表示正在同步
intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask!= null);
// 在Intent中添加额外数据通过键GTASK_SERVICE_BROADCAST_PROGRESS_MSG来传递同步进度消息内容
intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null);
intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg);
// 使用服务的上下文发送广播将包含同步相关信息的Intent广播出去其他组件可以通过注册监听相应Action来接收广播
sendBroadcast(intent);
}
// 静态方法用于在Activity中启动同步服务来执行同步操作
// 首先设置GTaskManager中的Activity上下文然后构建启动服务的Intent并传递启动同步的操作类型参数最后启动服务
public static void startSync(Activity activity) {
GTaskManager.getInstance().setActivityContext(activity);
Intent intent = new Intent(activity, GTaskSyncService.class);
@ -166,21 +112,17 @@ public class GTaskSyncService extends Service {
activity.startService(intent);
}
// 静态方法用于在给定的上下文Context中启动服务来取消同步操作
// 构建启动服务的Intent并传递取消同步的操作类型参数最后启动服务
public static void cancelSync(Context context) {
Intent intent = new Intent(context, GTaskSyncService.class);
intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC);
context.startService(intent);
}
// 静态方法用于判断当前是否正在进行同步操作通过判断mSyncTask是否为null来返回相应的布尔值结果
public static boolean isSyncing() {
return mSyncTask!= null;
return mSyncTask != null;
}
// 静态方法用于获取当前的同步进度消息内容直接返回保存同步进度消息的mSyncProgress变量的值
public static String getProgressString() {
return mSyncProgress;
}
}
}

@ -1,176 +1,114 @@
/*
* Copyright (c) 2010 - 2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE 2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* MiCodeApache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0 查看。
*/
// 包声明表明该类所在的包名为net.micode.notes.model通常用于存放笔记相关的数据模型等相关类定义
package net.micode.notes.model;
// 用于构建ContentProvider操作相关的数据结构比如插入、更新、删除等操作的数据封装以便批量应用到ContentResolver上
import android.content.ContentProviderOperation;
// 用于获取ContentProvider操作执行后的结果集比如批量更新、插入等操作完成后返回的结果信息
import android.content.ContentProviderResult;
// 用于根据给定的基础Uri和一个ID值构建一个新的Uri方便对特定资源进行操作比如对某个具体笔记的相关操作对应的Uri
import android.content.ContentUris;
// 用于存储键值对形式的数据通常用于向ContentProvider插入、更新数据时传递具体的数据内容
import android.content.ContentValues;
import android.content.Context;
// 用于处理ContentProvider操作应用时可能出现的异常情况比如批量操作部分失败等异常处理
import android.content.OperationApplicationException;
// 用于表示一个资源的统一资源标识符Uri在Android中常用于定位ContentProvider中的数据资源等操作
import android.net.Uri;
// 用于处理在跨进程调用ContentProvider等操作时可能出现的远程异常情况
import android.os.RemoteException;
import android.util.Log;
// 导入笔记相关的数据类,可能包含了笔记的各种属性、类型等定义以及不同类型笔记的数据结构相关类
import net.micode.notes.data.Notes;
// 导入笔记中呼叫记录相关的内部类,可能用于处理特定类型笔记(呼叫记录类型笔记)的数据结构和操作
import net.micode.notes.data.Notes.CallNote;
// 导入笔记中数据列相关的内部类,可能定义了笔记数据在存储时各个列的名称、类型等相关常量
import net.micode.notes.data.Notes.DataColumns;
// 导入笔记中普通笔记相关的列定义的内部类,可能定义了普通笔记在数据库存储时各个字段对应的列名等信息
import net.micode.notes.data.Notes.NoteColumns;
// 导入笔记中文本笔记相关的内部类,可能用于处理文本类型笔记的数据结构和操作
import net.micode.notes.data.Notes.TextNote;
// 导入用于存储列表等数据结构的集合类这里主要用于存储ContentProviderOperation对象列表以便批量操作
import java.util.ArrayList;
// Note类应该是代表笔记的数据模型类用于处理笔记相关的数据操作比如创建笔记、更新笔记以及与ContentProvider交互等操作
public class Note {
// 用于存储笔记的差异数据可能是自上次同步或修改后发生变化的数据以ContentValues形式存储方便后续更新操作
private ContentValues mNoteDiffValues;
// 用于存储笔记的详细数据包含文本数据、呼叫数据等不同类型的数据是一个自定义的NoteData类型对象
private NoteData mNoteData;
// 定义一个日志标签用于在Log输出时标识是该类中的相关日志信息方便调试和查看日志记录
private static final String TAG = "Note";
private ContentValues mNoteDiffValues; // 用于存储笔记变化的ContentValues
private NoteData mNoteData; // 存储笔记数据的内部类
private static final String TAG = "Note"; // 日志标签
/**
* Create a new note id for adding a new note to databases
* IDContentProviderID
* ID
*/
public static synchronized long getNewNoteId(Context context, long folderId) {
// 创建一个新的ContentValues对象用于存储要插入到数据库中的笔记初始数据
// 创建一个新的笔记
ContentValues values = new ContentValues();
// 获取当前系统时间的时间戳,作为笔记的创建时间,单位为毫秒
long createdTime = System.currentTimeMillis();
// 将创建时间设置到ContentValues中对应笔记记录的创建日期字段NoteColumns.CREATED_DATE
values.put(NoteColumns.CREATED_DATE, createdTime);
// 将创建时间也设置为修改时间因为新创建的笔记初始时修改时间和创建时间相同对应修改日期字段NoteColumns.MODIFIED_DATE
values.put(NoteColumns.MODIFIED_DATE, createdTime);
// 设置笔记的类型这里设置为普通笔记类型Notes.TYPE_NOTE可能有不同类型的笔记区分
values.put(NoteColumns.TYPE, Notes.TYPE_NOTE);
// 将本地修改标志设置为1表示该笔记在本地有过修改初始创建也算一种本地修改情况对应本地修改字段NoteColumns.LOCAL_MODIFIED
values.put(NoteColumns.LOCAL_MODIFIED, 1);
// 设置笔记的父文件夹ID用于表示笔记所属的文件夹对应父ID字段NoteColumns.PARENT_ID
values.put(NoteColumns.PARENT_ID, folderId);
// 通过ContentResolver向指定的笔记内容UriNotes.CONTENT_NOTE_URI插入新的笔记记录返回插入后生成的Uri
Uri uri = context.getContentResolver().insert(Notes.CONTENT_NOTE_URI, values);
long noteId = 0;
try {
// 从插入后生成的Uri中获取路径段通常Uri的路径部分可以按段分割比如最后一段可能是新生成的资源ID获取第二个路径段索引为1作为笔记ID
noteId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
// 如果在转换为长整型笔记ID时出现格式异常比如路径段内容不是合法的数字格式记录错误日志并将笔记ID设置为0
Log.e(TAG, "Get note id error :" + e.toString());
noteId = 0;
}
if (noteId == -1) {
// 如果获取到的笔记ID为 -1说明出现了不合理的情况抛出IllegalStateException异常表示笔记ID错误
throw new IllegalStateException("Wrong note id:" + noteId);
}
return noteId;
}
// 无参构造函数用于初始化Note对象创建一个新的ContentValues用于存储笔记差异数据以及一个新的NoteData对象用于存储详细笔记数据
public Note() {
mNoteDiffValues = new ContentValues();
mNoteData = new NoteData();
}
// 设置笔记的某个值可能是笔记的属性值等将给定的键值对添加到笔记差异数据ContentValues中
// 同时更新本地修改标志和修改日期,以记录笔记数据的变化情况
public void setNoteValue(String key, String value) {
mNoteDiffValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
// 设置笔记的文本数据调用NoteData对象的相应方法来设置文本数据将键值对存储到NoteData中的文本数据相关ContentValues中
public void setTextData(String key, String value) {
mNoteData.setTextData(key, value);
}
// 设置笔记的文本数据ID调用NoteData对象的相应方法来设置文本数据ID传入的ID值会被存储到NoteData对象中
public void setTextDataId(long id) {
mNoteData.setTextDataId(id);
}
// 获取笔记的文本数据ID通过访问NoteData对象中的相应属性来返回文本数据ID
public long getTextDataId() {
return mNoteData.mTextDataId;
}
// 设置笔记的呼叫数据ID调用NoteData对象的相应方法来设置呼叫数据ID传入的ID值会被存储到NoteData对象中同时会进行参数合法性检查
public void setCallDataId(long id) {
mNoteData.setCallDataId(id);
}
// 设置笔记的呼叫数据调用NoteData对象的相应方法来设置呼叫数据将键值对存储到NoteData中的呼叫数据相关ContentValues中
// 同时更新本地修改标志和修改日期,以记录笔记数据的变化情况
public void setCallData(String key, String value) {
mNoteData.setCallData(key, value);
}
// 判断笔记是否在本地有过修改通过检查笔记差异数据ContentValues的大小即是否有数据变化以及NoteData对象是否有本地修改来综合判断
public boolean isLocalModified() {
return mNoteDiffValues.size() > 0 || mNoteData.isLocalModified();
}
// 同步笔记数据到ContentProvider比如更新到数据库等存储介质中根据笔记是否有本地修改以及更新操作的结果来返回同步是否成功
public boolean syncNote(Context context, long noteId) {
if (noteId <= 0) {
// 如果传入的笔记ID小于等于0说明是不合理的笔记ID抛出IllegalArgumentException异常表示笔记ID错误
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
if (!isLocalModified()) {
// 如果笔记没有本地修改说明不需要进行同步操作直接返回true表示同步成功实际上没有进行实际的更新操作
return true;
}
/**
* In theory, once data changed, the note should be updated on {@link NoteColumns#LOCAL_MODIFIED} and
* {@link NoteColumns#MODIFIED_DATE}. For data safety, though update note fails, we also update the
* note data info
* {@link NoteColumns#LOCAL_MODIFIED}{@link NoteColumns#MODIFIED_DATE}
* 使
*/
// 通过ContentResolver尝试更新指定笔记ID对应的笔记记录传入笔记差异数据ContentValues进行更新操作
// 如果更新操作影响的行数为0即没有实际更新到任何数据可能更新失败记录错误日志但不立即返回继续执行后续操作
// 更新笔记
if (context.getContentResolver().update(
ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), mNoteDiffValues, null,
null) == 0) {
Log.e(TAG, "Update note error, should not happen");
// Do not return, fall through
}
// 清除笔记差异数据ContentValues中的内容因为已经尝试进行了更新操作无论成功与否都先清空准备下次记录新的差异数据
mNoteDiffValues.clear();
// 如果NoteData对象有本地修改并且将NoteData中的数据推送到ContentResolver即更新相关数据到存储介质操作返回null可能推送失败则返回false表示同步失败
// 同步笔记数据
if (mNoteData.isLocalModified()
&& (mNoteData.pushIntoContentResolver(context, noteId) == null)) {
return false;
@ -179,21 +117,14 @@ public class Note {
return true;
}
// NoteData内部类用于封装笔记的详细数据包含文本数据和呼叫数据相关的ContentValues以及对应的ID等信息
// 处理与这些详细数据相关的操作比如设置数据、判断是否有本地修改以及将数据推送到ContentResolver等操作
// 内部类,用于存储笔记的文本数据和通话记录数据
private class NoteData {
// 存储文本数据的唯一标识符ID初始化为0
private long mTextDataId;
// 用于存储文本数据相关的ContentValues以键值对形式存储文本数据的具体内容等信息初始化为一个新的ContentValues对象
private ContentValues mTextDataValues;
// 存储呼叫数据的唯一标识符ID初始化为0
private long mCallDataId;
// 用于存储呼叫数据相关的ContentValues以键值对形式存储呼叫数据的具体内容等信息初始化为一个新的ContentValues对象
private ContentValues mCallDataValues;
// 定义一个日志标签用于在Log输出时标识是该内部类中的相关日志信息方便调试和查看日志记录
private static final String TAG = "NoteData";
// 构造函数用于初始化NoteData对象创建新的文本数据和呼叫数据相关的ContentValues对象并将文本数据和呼叫数据的ID初始化为0
public NoteData() {
mTextDataValues = new ContentValues();
mCallDataValues = new ContentValues();
@ -201,20 +132,17 @@ public class Note {
mCallDataId = 0;
}
// 判断NoteData对象中的文本数据或呼叫数据是否有本地修改通过检查对应的ContentValues的大小即是否有数据变化来判断
boolean isLocalModified() {
return mTextDataValues.size() > 0 || mCallDataValues.size() > 0;
}
// 设置文本数据ID进行参数合法性检查如果传入的ID小于等于0则抛出IllegalArgumentException异常否则更新文本数据ID
void setTextDataId(long id) {
if (id <= 0) {
if(id <= 0) {
throw new IllegalArgumentException("Text data id should larger than 0");
}
mTextDataId = id;
}
// 设置呼叫数据ID进行参数合法性检查如果传入的ID小于等于0则抛出IllegalArgumentException异常否则更新呼叫数据ID
void setCallDataId(long id) {
if (id <= 0) {
throw new IllegalArgumentException("Call data id should larger than 0");
@ -222,54 +150,82 @@ public class Note {
mCallDataId = id;
}
// 设置呼叫数据将给定的键值对添加到呼叫数据相关的ContentValues中同时更新笔记的本地修改标志和修改日期通过外部的Note对象相关字段来更新
void setCallData(String key, String value) {
mCallDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
// 设置文本数据将给定的键值对添加到文本数据相关的ContentValues中同时更新笔记的本地修改标志和修改日期通过外部的Note对象相关字段来更新
void setTextData(String key, String value) {
mTextDataValues.put(key, value);
mNoteDiffValues.put(NoteColumns.LOCAL_MODIFIED, 1);
mNoteDiffValues.put(NoteColumns.MODIFIED_DATE, System.currentTimeMillis());
}
// 将NoteData中的数据推送到ContentResolver比如更新到数据库等存储介质中根据文本数据和呼叫数据的情况进行相应的插入、更新操作
// 并处理操作过程中可能出现的异常情况返回操作结果对应的Uri如果成功或null如果失败
Uri pushIntoContentResolver(Context context, long noteId) {
/**
* Check for safety
*/
if (noteId <= 0) {
// 如果传入的笔记ID小于等于0进行参数合法性检查抛出IllegalArgumentException异常表示笔记ID错误
throw new IllegalArgumentException("Wrong note id:" + noteId);
}
// 创建一个ArrayList用于存储ContentProviderOperation对象以便后续批量应用到ContentResolver上进行操作
// 将笔记数据推送到内容解析器
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
// 创建一个ContentProviderOperation的构建器对象初始化为null后续根据具体情况创建相应的更新或插入操作构建器
ContentProviderOperation.Builder builder = null;
if (mTextDataValues.size() > 0) {
// 如果文本数据相关的ContentValues中有数据即有文本数据需要处理将笔记ID设置到文本数据的ContentValues中对应笔记ID字段DataColumns.NOTE_ID
if(mTextDataValues.size() > 0) {
mTextDataValues.put(DataColumns.NOTE_ID, noteId);
if (mTextDataId == 0) {
// 如果文本数据ID为0说明是新的文本数据需要进行插入操作设置文本数据的MIME类型为文本笔记的内容类型TextNote.CONTENT_ITEM_TYPE
mTextDataValues.put(DataColumns.MIME_TYPE, TextNote.CONTENT_ITEM_TYPE);
// 通过ContentResolver向指定的笔记数据内容UriNotes.CONTENT_DATA_URI插入新的文本数据记录返回插入后生成的Uri
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mTextDataValues);
try {
// 尝试从插入后生成的Uri中获取路径段获取第二个路径段索引为1作为新生成的文本数据ID并设置到文本数据ID属性中
setTextDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
// 如果在转换为长整型文本数据ID时出现格式异常记录错误日志清除文本数据相关的ContentValues内容并返回null表示插入失败
Log.e(TAG, "Insert new text data fail with noteId" + noteId);
mTextDataValues.clear();
return null;
}
} else {
// 如果文本数据ID不为0说明是已有文本数据的更新操作创建一个ContentProviderOperation的更新操作构建器
// 根据文本数据ID对应的Uri通过ContentUris.withAppendedId构建来指定要更新的
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mTextDataId));
builder.withValues(mTextDataValues);
operationList.add(builder.build());
}
mTextDataValues.clear();
}
if(mCallDataValues.size() > 0) {
mCallDataValues.put(DataColumns.NOTE_ID, noteId);
if (mCallDataId == 0) {
mCallDataValues.put(DataColumns.MIME_TYPE, CallNote.CONTENT_ITEM_TYPE);
Uri uri = context.getContentResolver().insert(Notes.CONTENT_DATA_URI,
mCallDataValues);
try {
setCallDataId(Long.valueOf(uri.getPathSegments().get(1)));
} catch (NumberFormatException e) {
Log.e(TAG, "Insert new call data fail with noteId" + noteId);
mCallDataValues.clear();
return null;
}
} else {
builder = ContentProviderOperation.newUpdate(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mCallDataId));
builder.withValues(mCallDataValues);
operationList.add(builder.build());
}
mCallDataValues.clear();
}
if (operationList.size() > 0) {
try {
ContentProviderResult[] results = context.getContentResolver().applyBatch(
Notes.AUTHORITY, operationList);
return (results == null || results.length == 0 || results[0] == null) ? null
: ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId);
} catch (RemoteException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
} catch (OperationApplicationException e) {
Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
return null;
}
}
return null;
}
}
}

@ -1,87 +1,45 @@
/*
* Copyright (c) 2010 - 2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE 2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* MiCodeApache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0 查看。
*/
// 包声明表明该类所在的包名为net.micode.notes.model通常用于存放和笔记相关的数据模型及操作逻辑等相关类
package net.micode.notes.model;
// 用于管理桌面小部件相关操作例如获取小部件ID、更新小部件等操作和Android桌面小部件功能交互相关
import android.appwidget.AppWidgetManager;
// 用于根据给定的基础Uri和一个ID值构建一个新的Uri方便对特定资源进行操作比如对某个具体笔记的相关操作对应的Uri
import android.content.ContentUris;
// 提供对Android设备上各种数据存储如数据库等进行查询、插入、更新、删除等操作的接口通过它与Content Provider进行交互
import android.content.Context;
// 用于在数据库查询操作后获取结果集,以游标形式遍历查询返回的数据行,可从中获取具体的列数据
import android.database.Cursor;
// 用于处理字符串相关的工具方法,比如判断字符串是否为空、比较字符串内容是否相等之类的操作
import android.text.TextUtils;
import android.util.Log;
// 导入笔记相关的数据类,可能包含了笔记的各种属性、类型等定义以及不同类型笔记的数据结构相关类
import net.micode.notes.data.Notes;
// 导入笔记中呼叫记录相关的内部类,可能用于处理特定类型笔记(呼叫记录类型笔记)的数据结构和操作
import net.micode.notes.data.Notes.CallNote;
// 导入笔记中数据列相关的内部类,可能定义了笔记数据在存储时各个列的名称、类型等相关常量
import net.micode.notes.data.Notes.DataColumns;
// 导入笔记中数据相关的常量定义,可能包含了一些预定义的笔记数据类型等常量值
import net.micode.notes.data.Notes.DataConstants;
// 导入笔记中普通笔记相关的列定义的内部类,可能定义了普通笔记在数据库存储时各个字段对应的列名等信息
import net.micode.notes.data.Notes.NoteColumns;
// 导入笔记中文本笔记相关的内部类,可能用于处理文本类型笔记的数据结构和操作
import net.micode.notes.data.Notes.TextNote;
// 导入用于解析资源相关的工具类中的笔记背景资源相关的内部类可能用于获取笔记背景相关的资源ID等操作
import net.micode.notes.tool.ResourceParser.NoteBgResources;
// WorkingNote类应该是代表正在操作编辑、修改等的笔记的数据模型类封装了笔记的各种属性以及相关操作方法
public class WorkingNote {
// 用于存储关联的Note对象Note类可能是更基础的笔记数据模型类这里的WorkingNote可能是在其基础上进行更多业务相关操作的封装
// Note for the working note
private Note mNote;
// 用于存储笔记的唯一标识符ID代表当前正在操作的笔记在数据库等存储介质中的标识
// Note Id
private long mNoteId;
// 用于存储笔记的内容文本,比如用户输入的具体笔记文字内容等
// Note content
private String mContent;
// 用于存储笔记的模式相关信息,具体含义可能根据业务逻辑而定,例如可能是不同的编辑模式等情况
// Note mode
private int mMode;
// 用于存储笔记设置的提醒日期时间戳,以毫秒为单位,用于表示笔记的提醒时间相关设置
private long mAlertDate;
// 用于存储笔记最后修改的日期时间戳,以毫秒为单位,用于记录笔记最近一次被修改的时间
private long mModifiedDate;
// 用于存储笔记背景颜色的资源ID通过这个ID可以获取到对应的背景颜色资源用于设置笔记的显示背景等
private int mBgColorId;
// 用于存储笔记关联的桌面小部件的ID用于标识该笔记与哪个桌面小部件相关联如果有的话
private int mWidgetId;
// 用于存储笔记关联的桌面小部件的类型,可能有不同类型的小部件对应不同的展示或功能,具体类型由业务定义
private int mWidgetType;
// 用于存储笔记所属的文件夹的ID用于表示笔记在文件系统中的分类归属方便管理和查找笔记
private long mFolderId;
// 用于存储当前的上下文环境Context通过它可以访问Android系统的各种资源、服务等方便与系统进行交互操作
private Context mContext;
// 定义一个日志标签用于在Log输出时标识是该类中的相关日志信息方便调试和查看日志记录
private static final String TAG = "WorkingNote";
// 用于标记当前笔记是否已被删除true表示已删除false表示未删除方便在业务逻辑中判断笔记的删除状态
private boolean mIsDeleted;
// 用于存储一个实现了NoteSettingChangedListener接口的对象以便在笔记相关设置发生变化时通知对应的监听器进行相应处理
private NoteSettingChangedListener mNoteSettingStatusListener;
// 定义一个字符串数组常量,用于指定查询笔记数据时要获取的列名列表,主要涉及笔记数据相关的一些通用列信息,用于后续数据库查询操作
public static final String[] DATA_PROJECTION = new String[] {
// 笔记成员变量
private Note mNote; // 笔记对象
private long mNoteId; // 笔记ID
private String mContent; // 笔记内容
private int mMode; // 笔记模式
private long mAlertDate; // 闹钟日期
private long mModifiedDate; // 修改日期
private int mBgColorId; // 背景颜色ID
private int mWidgetId; // 小部件ID
private int mWidgetType; // 小部件类型
private long mFolderId; // 文件夹ID
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,
DataColumns.MIME_TYPE,
@ -91,8 +49,8 @@ public class WorkingNote {
DataColumns.DATA4,
};
// 定义一个字符串数组常量,用于指定查询笔记基本信息时要获取的列名列表,主要涉及笔记自身属性相关的一些列信息,用于后续数据库查询操作
public static final String[] NOTE_PROJECTION = new String[] {
// 笔记投影数组
public static final String[] NOTE_PROJECTION = new String[]{
NoteColumns.PARENT_ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
@ -101,29 +59,7 @@ public class WorkingNote {
NoteColumns.MODIFIED_DATE
};
// 定义一个整型常量用于表示在查询结果游标Cursor中笔记数据ID列的索引位置方便从游标中获取对应的数据
private static final int DATA_ID_COLUMN = 0;
// 定义一个整型常量用于表示在查询结果游标Cursor中笔记内容列的索引位置方便从游标中获取对应的数据
private static final int DATA_CONTENT_COLUMN = 1;
// 定义一个整型常量用于表示在查询结果游标Cursor中笔记MIME类型列的索引位置方便从游标中获取对应的数据
private static final int DATA_MIME_TYPE_COLUMN = 2;
// 定义一个整型常量用于表示在查询结果游标Cursor中笔记模式相关列的索引位置方便从游标中获取对应的数据
private static final int DATA_MODE_COLUMN = 3;
// 定义一个整型常量用于表示在查询结果游标Cursor中笔记父文件夹ID列的索引位置方便从游标中获取对应的数据
private static final int NOTE_PARENT_ID_COLUMN = 0;
// 定义一个整型常量用于表示在查询结果游标Cursor中笔记提醒日期列的索引位置方便从游标中获取对应的数据
private static final int NOTE_ALERTED_DATE_COLUMN = 1;
// 定义一个整型常量用于表示在查询结果游标Cursor中笔记背景颜色ID列的索引位置方便从游标中获取对应的数据
private static final int NOTE_BG_COLOR_ID_COLUMN = 2;
// 定义一个整型常量用于表示在查询结果游标Cursor中笔记小部件ID列的索引位置方便从游标中获取对应的数据
private static final int NOTE_WIDGET_ID_COLUMN = 3;
// 定义一个整型常量用于表示在查询结果游标Cursor中笔记小部件类型列的索引位置方便从游标中获取对应的数据
private static final int NOTE_WIDGET_TYPE_COLUMN = 4;
// 定义一个整型常量用于表示在查询结果游标Cursor中笔记修改日期列的索引位置方便从游标中获取对应的数据
private static final int NOTE_MODIFIED_DATE_COLUMN = 5;
// 私有构造函数,用于创建一个新的空白笔记(尚未保存到数据库等存储介质中),初始化一些基本属性,如创建时间、所属文件夹等信息
// New note construct
// 构造函数
private WorkingNote(Context context, long folderId) {
mContext = context;
mAlertDate = 0;
@ -136,8 +72,7 @@ public class WorkingNote {
mWidgetType = Notes.TYPE_WIDGET_INVALIDE;
}
// 私有构造函数用于创建一个基于已有笔记ID的WorkingNote对象通过从数据库等存储介质中加载已有笔记的信息来初始化对象属性
// Existing note construct
// 构造函数
private WorkingNote(Context context, long noteId, long folderId) {
mContext = context;
mNoteId = noteId;
@ -147,74 +82,17 @@ public class WorkingNote {
loadNote();
}
// 私有方法,用于从数据库等存储介质中加载笔记的基本信息如文件夹ID、背景颜色ID、小部件相关信息等通过ContentResolver进行查询操作获取数据
// 加载笔记
private void loadNote() {
// 使用ContentResolver查询指定笔记ID对应的笔记基本信息传入构建好的笔记内容Uri通过ContentUris.withAppendedId根据笔记ID生成以及要查询的列名列表NOTE_PROJECTION等参数
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);
mWidgetType = cursor.getInt(NOTE_WIDGET_TYPE_COLUMN);
mAlertDate = cursor.getLong(NOTE_ALERTED_DATE_COLUMN);
mModifiedDate = cursor.getLong(NOTE_MODIFIED_DATE_COLUMN);
}
// 关闭游标,释放相关资源,因为已经获取完需要的数据了
cursor.close();
} else {
// 如果游标为null说明查询出现问题没有找到对应的笔记记录错误日志并抛出IllegalArgumentException异常表示无法找到指定ID的笔记
Log.e(TAG, "No note with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note with id " + mNoteId);
}
// 调用loadNoteData方法加载笔记的详细数据如内容、类型等信息
loadNoteData();
// 从数据库加载笔记信息
}
// 私有方法,用于从数据库等存储介质中加载笔记的详细数据如内容、类型等信息通过ContentResolver进行查询操作获取数据并根据数据类型进行相应的处理
// 加载笔记数据
private void loadNoteData() {
// 使用ContentResolver查询指定笔记ID对应的笔记数据信息传入笔记数据内容的UriNotes.CONTENT_DATA_URI以及要查询的列名列表DATA_PROJECTION等参数
// 同时通过条件筛选只获取当前笔记ID对应的笔记数据DataColumns.NOTE_ID + "=?" 及对应的笔记ID参数
Cursor cursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI, DATA_PROJECTION,
DataColumns.NOTE_ID + "=?", new String[] {
String.valueOf(mNoteId)
}, null);
if (cursor!= null) {
// 判断游标是否有数据,如果游标可以移动到第一条数据(即有查询到的数据),则进入循环遍历游标中的每一行数据
if (cursor.moveToFirst()) {
do {
// 从游标中获取笔记数据的MIME类型列的数据表示数据的格式类型比如文本、呼叫记录等类型
String type = cursor.getString(DATA_MIME_TYPE_COLUMN);
if (DataConstants.NOTE.equals(type)) {
// 如果类型是普通笔记类型DataConstants.NOTE则从游标中获取笔记内容列的数据赋值给mContent属性
// 获取笔记模式列的数据赋值给mMode属性并将笔记数据ID设置到关联的Note对象中通过调用Note对象的setTextDataId方法
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)) {
// 如果类型是呼叫记录笔记类型DataConstants.CALL_NOTE则将笔记数据ID设置到关联的Note对象的呼叫数据相关属性中通过调用Note对象的setCallDataId方法
mNote.setCallDataId(cursor.getLong(DATA_ID_COLUMN));
} else {
// 如果是其他未知的类型,则记录调试日志,提示出现了错误的笔记类型
Log.d(TAG, "Wrong note type with type:" + type);
}
} while (cursor.moveToNext());
}
// 关闭游标,释放相关资源,因为已经获取完需要的数据了
cursor.close();
} else {
// 如果游标为null说明查询出现问题没有找到对应的笔记数据记录错误日志并抛出IllegalArgumentException异常表示无法找到指定ID的笔记数据
Log.e(TAG, "No data with id:" + mNoteId);
throw new IllegalArgumentException("Unable to find note's data with id " + mNoteId);
}
// 从数据库加载笔记数据
}
// 静态工厂方法,用于创建一个新的笔记对象并设置一些初始属性如背景颜色ID、小部件ID、小部件类型等方便后续编辑等操作
// 创建空笔记
public static WorkingNote createEmptyNote(Context context, long folderId, int widgetId,
int widgetType, int defaultBgColorId) {
WorkingNote note = new WorkingNote(context, folderId);
@ -224,47 +102,131 @@ public class WorkingNote {
return note;
}
// 静态工厂方法用于根据给定的笔记ID从数据库等存储介质中加载已有笔记信息并创建对应的WorkingNote对象方便后续操作
// 加载笔记
public static WorkingNote load(Context context, long id) {
return new WorkingNote(context, id, 0);
}
// 同步方法,用于保存当前笔记的信息到数据库等存储介质中,如果笔记值得保存(根据一定的业务规则判断),则进行保存操作并返回保存结果(成功或失败)
// 保存笔记
public synchronized boolean saveNote() {
if (isWorthSaving()) {
// 判断笔记是否已经存在于数据库中,如果不存在
if (!existInDatabase()) {
// 通过调用Note类的静态方法获取一个新的笔记ID如果获取失败返回0记录错误日志并返回false表示保存失败
if ((mNoteId = Note.getNewNoteId(mContext, mFolderId)) == 0) {
Log.e(TAG, "Create new note fail with id:" + mNoteId);
return false;
}
}
// 调用关联的Note对象的syncNote方法将笔记数据同步到数据库等存储介质中进行实际的保存操作
mNote.syncNote(mContext, mNoteId);
/**
* Update widget content if there exist any widget of this note
* IDNoteSettingChangedListener
* onWidgetChanged
*/
if (mWidgetId!= AppWidgetManager.INVALID_APPWIDGET_ID
&& mWidgetType!= Notes.TYPE_WIDGET_INVALIDE
&& mNoteSettingStatusListener!= null) {
mNoteSettingStatusListener.onWidgetChanged();
}
return true;
} else {
return false;
}
// 保存笔记到数据库
}
// 用于判断笔记是否已经存在于数据库等存储介质中通过检查笔记ID是否大于0来判断通常大于0表示已经有对应的存储记录
// 判断笔记是否存在于数据库
public boolean existInDatabase() {
return mNoteId > 0;
}
// 私有方法,用于根据一定的业务规则判断笔记是否值得保存,比如笔记已被删除、不存在且内容为空、存在但没有本地修改等情况则不值得保存,返回相应的布尔值结果
private boolean isWorthSaving() {
if (mIsDeleted
// 设置笔记设置变化监听器
public void setOnSettingStatusChangedListener(NoteSettingChangedListener l) {
mNoteSettingStatusListener = l;
}
// 设置闹钟日期
public void setAlertDate(long date, boolean set) {
// 设置闹钟日期
}
// 标记笔记为删除
public void markDeleted(boolean mark) {
mIsDeleted = mark;
}
// 设置背景颜色ID
public void setBgColorId(int id) {
// 设置背景颜色ID
}
// 设置检查列表模式
public void setCheckListMode(int mode) {
// 设置检查列表模式
}
// 设置小部件类型
public void setWidgetType(int type) {
// 设置小部件类型
}
// 设置小部件ID
public void setWidgetId(int id) {
// 设置小部件ID
}
// 设置笔记内容
public void setWorkingText(String text) {
// 设置笔记内容
}
// 将笔记转换为通话记录
public void convertToCallNote(String phoneNumber, long callDate) {
// 将笔记转换为通话记录
}
// 判断是否有闹钟提醒
public boolean hasClockAlert() {
return (mAlertDate > 0 ? true : false);
}
// 获取笔记内容
public String getContent() {
return mContent;
}
// 获取闹钟日期
public long getAlertDate() {
return mAlertDate;
}
// 获取修改日期
public long getModifiedDate() {
return mModifiedDate;
}
// 获取背景颜色资源ID
public int getBgColorResId() {
return NoteBgResources.getNoteBgResource(mBgColorId);
}
// 获取背景颜色ID
public int getBgColorId() {
return mBgColorId;
}
// 获取标题背景颜色资源ID
public int getTitleBgResId() {
return NoteBgResources.getNoteTitleBgResource(mBgColorId);
}
// 获取检查列表模式
public int getCheckListMode() {
return mMode;
}
// 获取笔记ID
public long getNoteId() {
return mNoteId;
}
// 获取文件夹ID
public long getFolderId() {
return mFolderId;
}
// 获取小部件ID
public int getWidgetId() {
return mWidgetId;
}
// 获取小部件类型
public int getWidgetType() {
return mWidgetType;
}
// 笔记设置变化监听器接口
public interface NoteSettingChangedListener {
void onBackgroundColorChanged();
void onClockAlertChanged(long date, boolean set);
void onWidgetChanged();
void onCheckListModeChanged(int oldMode, int newMode);
}
}

@ -1,65 +1,35 @@
//*
* Copyright (c) 2010 - 2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE 2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* MiCodeApache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0 查看。
*/
// 包声明表明该类所在的包名为net.micode.notes.tool通常用于存放和笔记应用相关的工具类这里的BackupUtils应该是和备份相关的工具类
package net.micode.notes.tool;
package net.micode.notes.tool;
// 用于获取Android应用的上下文环境通过它可以访问系统资源、服务等是Android开发中很多操作的基础入口
import android.content.Context;
// 用于在数据库查询操作后获取结果集,以游标形式遍历查询返回的数据行,可从中获取具体的列数据
import android.database.Cursor;
// 用于获取Android设备外部存储如SD卡等的状态信息判断是否可读写等情况
import android.os.Environment;
// 用于处理字符串相关的工具方法,比如判断字符串是否为空、格式化字符串等操作
import android.text.TextUtils;
// 用于格式化日期、时间相关的文本展示格式,方便将时间戳等数据转换为符合特定格式的字符串
import android.text.format.DateFormat;
import android.util.Log;
// 导入应用资源相关的R类通过它可以获取到应用中定义的各种资源如字符串、布局、图片资源等的ID用于在代码中使用这些资源
import net.micode.notes.R;
// 导入笔记相关的数据类,可能包含了笔记的各种属性、类型等定义以及不同类型笔记的数据结构相关类
import net.micode.notes.data.Notes;
// 导入笔记中数据列相关的内部类,可能定义了笔记数据在存储时各个列的名称、类型等相关常量
import net.micode.notes.data.Notes.DataColumns;
// 导入笔记中数据相关的常量定义,可能包含了一些预定义的笔记数据类型等常量值
import net.micode.notes.data.Notes.DataConstants;
// 导入笔记中普通笔记相关的列定义的内部类,可能定义了普通笔记在数据库存储时各个字段对应的列名等信息
import net.micode.notes.data.Notes.NoteColumns;
// 导入Java中文件操作相关的类用于创建、读取、写入文件等操作
import java.io.File;
// 用于在文件操作中表示找不到指定文件的异常情况,当尝试访问不存在的文件时会抛出该异常
import java.io.FileNotFoundException;
// 用于创建文件输出流,将数据写入到文件中,是进行文件写入操作的关键类之一
import java.io.FileOutputStream;
// 用于处理文件读写过程中出现的一般性IO异常情况比如读写文件出错、权限不足等问题时抛出该异常
import java.io.IOException;
// 用于将格式化后的文本数据输出到指定的输出流(比如文件输出流、控制台输出流等),方便进行文本内容的输出操作
import java.io.PrintStream;
// BackupUtils类是一个用于备份相关功能的工具类可能涉及将笔记数据备份到文件等操作采用了单例模式设计
public class BackupUtils {
// 定义一个日志标签用于在Log输出时标识是该类中的相关日志信息方便调试和查看日志记录
private static final String TAG = "BackupUtils";
// 单例模式相关用于保存唯一的BackupUtils实例对象初始化为null后续通过单例方法获取实例时进行初始化
// Singleton stuff
// 单例模式
private static BackupUtils sInstance;
// 静态同步方法,用于获取BackupUtils的单例实例,如果实例还未创建,则创建一个新的实例并返回,保证整个应用中只有一个该类的实例存在
// 获取BackupUtils实例
public static synchronized BackupUtils getInstance(Context context) {
if (sInstance == null) {
sInstance = new BackupUtils(context);
@ -67,58 +37,42 @@ public class BackupUtils {
return sInstance;
}
/**
* Following states are signs to represents backup or restore
* status
*
*/
// 表示当前SD卡外部存储设备未挂载不可进行读写操作对应备份或恢复操作可能无法进行的一种状态
// Currently, the sdcard is not mounted
// 备份和恢复状态常量
public static final int STATE_SD_CARD_UNMOUONTED = 0;
// 表示备份文件不存在,可能是还未进行过备份或者备份文件被误删除等情况,用于在检查备份相关情况时判断
// The backup file not exist
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;
// 表示数据格式不正确,可能被其他程序修改导致不符合预期的格式,影响备份或恢复操作的正常进行,用于异常情况判断
// The data is not well formated, may be changed by other programs
public static final int STATE_DATA_DESTROIED = 2;
// 表示出现了一些运行时异常,导致备份或恢复操作失败,比如内存不足、程序崩溃等异常情况,用于捕获和标识异常状态
// Some run-time exception which causes restore or backup fails
public static final int STATE_SYSTEM_ERROR = 3;
// 表示备份或恢复操作成功完成,用于标识操作正常结束的状态情况
// Backup or restore success
public static final int STATE_SUCCESS = 4;
// 用于文本导出相关操作的对象,可能负责将笔记数据转换为文本格式并进行导出等具体功能,通过构造函数初始化
private TextExport mTextExport;
// 私有构造函数用于初始化BackupUtils对象创建一个TextExport对象用于后续的文本导出相关操作保证外部不能随意创建该类实例符合单例模式要求
// 构造函数
private BackupUtils(Context context) {
mTextExport = new TextExport(context);
}
// 静态方法用于判断外部存储如SD卡等是否可用通过检查外部存储的挂载状态是否为已挂载MEDIA_MOUNTED来返回相应的布尔值结果
// 检查外部存储是否可用
private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
// 调用TextExport对象的exportToText方法进行文本导出操作并返回相应的结果可能是表示导出操作状态的状态码等
// 导出笔记到文本
public int exportToText() {
return mTextExport.exportToText();
}
// 获取通过TextExport对象导出的文本文件的文件名实际是调用TextExport对象的相应属性来返回文件名
// 获取导出的文本文件名
public String getExportedTextFileName() {
return mTextExport.mFileName;
}
// 获取通过TextExport对象导出的文本文件所在的目录路径实际是调用TextExport对象的相应属性来返回文件目录路径
// 获取导出的文本文件目录
public String getExportedTextFileDir() {
return mTextExport.mFileDirectory;
}
// TextExport内部类主要负责将笔记相关数据转换为文本格式并输出到指定位置如文件的具体操作封装了相关的数据查询、格式转换等逻辑
// 文本导出内部类
private static class TextExport {
// 定义一个字符串数组常量,用于指定查询笔记基本信息时要获取的列名列表,主要涉及笔记自身属性相关的一些列信息,用于后续数据库查询操作
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID,
NoteColumns.MODIFIED_DATE,
@ -126,14 +80,6 @@ public class BackupUtils {
NoteColumns.TYPE
};
// 定义一个整型常量用于表示在查询结果游标Cursor中笔记ID列的索引位置方便从游标中获取对应的数据
private static final int NOTE_COLUMN_ID = 0;
// 定义一个整型常量用于表示在查询结果游标Cursor中笔记修改日期列的索引位置方便从游标中获取对应的数据
private static final int NOTE_COLUMN_MODIFIED_DATE = 1;
// 定义一个整型常量用于表示在查询结果游标Cursor中笔记摘要列的索引位置方便从游标中获取对应的数据
private static final int NOTE_COLUMN_SNIPPET = 2;
// 定义一个字符串数组常量,用于指定查询笔记数据时要获取的列名列表,主要涉及笔记数据相关的一些通用列信息,用于后续数据库查询操作
private static final String[] DATA_PROJECTION = {
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
@ -143,32 +89,12 @@ public class BackupUtils {
DataColumns.DATA4,
};
// 定义一个整型常量用于表示在查询结果游标Cursor中笔记数据内容列的索引位置方便从游标中获取对应的数据
private static final int DATA_COLUMN_CONTENT = 0;
// 定义一个整型常量用于表示在查询结果游标Cursor中笔记数据MIME类型列的索引位置方便从游标中获取对应的数据
private static final int DATA_COLUMN_MIME_TYPE = 1;
// 定义一个整型常量用于表示在查询结果游标Cursor中笔记数据呼叫日期列的索引位置方便从游标中获取对应的数据
private static final int DATA_COLUMN_CALL_DATE = 2;
// 定义一个整型常量用于表示在查询结果游标Cursor中笔记数据电话号码列的索引位置方便从游标中获取对应的数据
private static final int DATA_COLUMN_PHONE_NUMBER = 4;
// 定义一个字符串数组,用于存储文本格式相关的模板字符串,不同索引位置对应不同用途的格式模板,后续会根据索引获取相应格式来格式化输出文本内容
private final String [] TEXT_FORMAT;
// 定义一个整型常量用于表示在TEXT_FORMAT数组中文件夹名称格式模板的索引位置方便获取对应格式字符串
private static final int FORMAT_FOLDER_NAME = 0;
// 定义一个整型常量用于表示在TEXT_FORMAT数组中笔记日期格式模板的索引位置方便获取对应格式字符串
private static final int FORMAT_NOTE_DATE = 1;
// 定义一个整型常量用于表示在TEXT_FORMAT数组中笔记内容格式模板的索引位置方便获取对应格式字符串
private static final int FORMAT_NOTE_CONTENT = 2;
// 用于存储当前的上下文环境Context通过它可以访问Android系统的各种资源、服务等方便与系统进行交互操作在构造函数中初始化
private final String[] TEXT_FORMAT;
private Context mContext;
// 用于存储导出的文本文件的文件名,初始化为空字符串,后续会根据实际情况赋值
private String mFileName;
// 用于存储导出的文本文件所在的目录路径,初始化为空字符串,后续会根据实际情况赋值
private String mFileDirectory;
// 构造函数用于初始化TextExport对象从资源中获取文本格式模板字符串数组同时初始化上下文、文件名和文件目录等属性
// 构造函数
public TextExport(Context context) {
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context;
@ -176,276 +102,104 @@ public class BackupUtils {
mFileDirectory = "";
}
// 根据给定的索引ID获取对应的文本格式模板字符串用于后续格式化输出文本内容实际就是从TEXT_FORMAT数组中根据索引获取相应元素
// 获取格式化字符串
private String getFormat(int id) {
return TEXT_FORMAT[id];
}
/**
* Export the folder identified by folder id to text
* IDPrintStream
*/
// 导出文件夹到文本
private void exportFolderToText(String folderId, PrintStream ps) {
// 使用ContentResolver查询属于该文件夹的所有笔记信息传入笔记内容的UriNotes.CONTENT_NOTE_URI、要查询的列名列表NOTE_PROJECTION以及通过文件夹ID筛选的条件NoteColumns.PARENT_ID + "=?" 及对应的文件夹ID参数等参数
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
folderId
}, null);
if (notesCursor!= null) {
// 判断游标是否有数据,如果游标可以移动到第一条数据(即有查询到的数据),则进入循环遍历游标中的每一行数据
if (notesCursor.moveToFirst()) {
do {
// 使用指定的日期格式模板格式化笔记的最后修改日期并输出到指定的输出流PrintStream通过String.format方法结合获取到的格式模板和格式化后的日期数据进行输出
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// 获取当前笔记的ID用于后续查询该笔记对应的详细数据信息
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
// 调用exportNoteToText方法将该笔记的详细数据信息导出为文本格式并输出到指定的输出流PrintStream
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext());
}
// 关闭游标,释放相关资源,因为已经获取完需要的数据了
notesCursor.close();
}
// 查询属于该文件夹的笔记
}
/**
* Export note identified by id to a print stream
* IDPrintStream
*/
// 导出笔记到文本
private void exportNoteToText(String noteId, PrintStream ps) {
// 使用ContentResolver查询属于该笔记的所有数据信息传入笔记数据内容的UriNotes.CONTENT_DATA_URI、要查询的列名列表DATA_PROJECTION以及通过笔记ID筛选的条件DataColumns.NOTE_ID + "=?" 及对应的笔记ID参数等参数
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
noteId
}, null);
// 查询属于该笔记的数据
}
if (dataCursor!= null) {
// 判断游标是否有数据,如果游标可以移动到第一条数据(即有查询到的数据),则进入循环遍历游标中的每一行数据
if (dataCursor.moveToFirst()) {
do {
// 从游标中获取笔记数据的MIME类型列的数据表示数据的格式类型比如文本、呼叫记录等类型
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
if (DataConstants.CALL_NOTE.equals(mimeType)) {
// 如果类型是呼叫记录笔记类型DataConstants.CALL_NOTE则从游标中获取电话号码列的数据并输出到指定的输出流PrintStream
// 使用指定的笔记内容格式模板进行格式化输出,前提是电话号码不为空
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER);
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE);
String location = dataCursor.getString(DATA_COLUMN_CONTENT);
// 导出笔记数据到文本
public int exportToText() {
// 检查外部存储是否可用
if (!externalStorageAvailable()) {
return STATE_SD_CARD_UNMOUONTED;
}
if (!TextUtils.isEmpty(phoneNumber)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
phoneNumber));
}
// 从游标中获取呼叫日期列的数据并输出到指定的输出流PrintStream
// 使用指定的笔记内容格式模板进行格式化输出
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat
.format(mContext.getString(R.string.format_datetime_mdhm),
callDate)));
// 从游标中获取呼叫附件位置列的数据如果有并输出到指定的输出流PrintStream
// 使用指定的笔记内容格式模板进行格式化输出,前提是该位置信息不为空
if (!TextUtils.isEmpty(location)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
location));
}
} else if (DataConstants.NOTE.equals(mimeType)) {
// 如果类型是普通笔记类型DataConstants.NOTE则从游标中获取笔记内容列的数据并输出到指定的输出流PrintStream
// 使用指定的笔记内容格式模板进行格式化输出,前提是笔记内容不为空
String content = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(content)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
content));
}
PrintStream ps = getExportToTextPrintStream();
if (ps == null) {
return STATE_SYSTEM_ERROR;
}
// 导出文件夹和笔记
Cursor folderCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, "(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null);
if (folderCursor != null) {
if (folderCursor.moveToFirst()) {
do {
String folderName = "";
if (folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
folderName = mContext.getString(R.string.call_record_folder_name);
} else {
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET);
}
if (!TextUtils.isEmpty(folderName)) {
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
}
} while (dataCursor.moveToNext());
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
}
// 关闭游标,释放相关资源,因为已经获取完需要的数据了
dataCursor.close();
folderCursor.close();
}
}
}
}
} while (dataCursor.moveToNext());
}
// 关闭游标,释放相关资源,因为已经获取完当前笔记对应的数据信息了
dataCursor.close();
}
// print a line separator between note
// 尝试向输出流PrintStream中写入换行分隔符通过写入对应字符的字节形式用于在不同笔记内容之间进行分隔使导出的文本更易读
try {
ps.write(new byte[] {
Character.LINE_SEPARATOR, Character.LETTER_NUMBER
});
} catch (IOException e) {
// 如果在写入换行分隔符时出现IO异常记录错误日志将异常信息输出到日志中方便排查问题
Log.e(TAG, e.toString());
}
}
/**
* Note will be exported as text which is user readable
*
*/
public int exportToText() {
// 调用externalStorageAvailable方法判断外部存储如SD卡等是否可用如果不可用
if (!externalStorageAvailable()) {
// 记录调试日志,提示媒体(外部存储设备)未挂载情况
Log.d(TAG, "Media was not mounted");
// 返回表示外部存储未挂载的状态码,表明当前无法进行导出操作,因为没有可用的存储位置
return STATE_SD_CARD_UNMOUONTED;
}
// 调用getExportToTextPrintStream方法获取用于将笔记数据输出为文本的打印流PrintStream对象如果获取失败返回null
PrintStream ps = getExportToTextPrintStream();
if (ps == null) {
// 记录错误日志,提示获取打印流出现错误情况
Log.e(TAG, "get print stream error");
// 返回表示系统错误的状态码,说明在获取输出流这个环节出现问题,导致导出操作无法正常进行
return STATE_SYSTEM_ERROR;
}
// First export folder and its notes
// 使用ContentResolver查询需要导出的文件夹及其中笔记的信息筛选条件比较复杂主要是获取特定类型的文件夹普通文件夹且非回收站文件夹以及呼叫记录文件夹及其包含的笔记信息
Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
"(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null);
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
NoteColumns.TYPE + "=" + Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID
+ "=0", null, null);
if (folderCursor!= null) {
// 判断游标是否有数据,如果游标可以移动到第一条数据(即有查询到的数据),则进入循环遍历游标中的每一行数据
if (folderCursor.moveToFirst()) {
do {
// 初始化文件夹名称为空字符串,后续根据不同情况获取并赋值实际的文件夹名称
String folderName = "";
// 如果当前文件夹的ID等于呼叫记录文件夹的IDNotes.ID_CALL_RECORD_FOLDER则从资源中获取对应的字符串资源呼叫记录文件夹名称赋值给folderName变量
if (folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
folderName = mContext.getString(R.string.call_record_folder_name);
} else {
// 否则,从游标中获取文件夹摘要列的数据作为文件夹名称(一般情况的文件夹名称获取方式)
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET);
}
// 判断文件夹名称不为空字符串时使用指定的文件夹名称格式模板格式化文件夹名称并输出到打印流PrintStream
if (!TextUtils.isEmpty(folderName)) {
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
if (noteCursor != null) {
if (noteCursor.moveToFirst()) {
do {
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());
}
// 获取当前文件夹的ID用于后续调用exportFolderToText方法导出该文件夹下的笔记数据
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
}
// 关闭游标,释放相关资源,因为已经获取完需要的文件夹及笔记相关数据了
folderCursor.close();
}
// Export notes in root's folder
// 使用ContentResolver查询根文件夹下的笔记信息筛选条件是类型为普通笔记Notes.TYPE_NOTE且父文件夹ID为0表示根文件夹下的笔记
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID
+ "=0", null, null);
noteCursor.close();
}
ps.close();
if (noteCursor!= null) {
// 判断游标是否有数据,如果游标可以移动到第一条数据(即有查询到的数据),则进入循环遍历游标中的每一行数据
if (noteCursor.moveToFirst()) {
do {
// 使用指定的笔记日期格式模板格式化笔记的最后修改日期并输出到打印流PrintStream
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// 获取当前笔记的ID用于后续调用exportNoteToText方法导出该笔记的详细数据信息
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());
return STATE_SUCCESS;
}
// 关闭游标,释放相关资源,因为已经获取完需要的根文件夹下笔记相关数据了
noteCursor.close();
}
// 关闭打印流,释放相关资源,完成整个笔记数据导出到文本文件的操作
ps.close();
// 返回表示导出操作成功的状态码,说明整个导出流程顺利完成,笔记数据已成功导出为文本格式
return STATE_SUCCESS;
}
/**
* Get a print stream pointed to the file {@generateExportedTextFile}
* {@generateExportedTextFile}PrintStreamnull
*/
private PrintStream getExportToTextPrintStream() {
// 调用generateFileMountedOnSDcard方法在外部存储SD卡等上生成用于存储导出文本数据的文件对象如果生成失败返回null
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format);
if (file == null) {
// 记录错误日志,提示创建用于导出的文件失败情况
Log.e(TAG, "create file to exported failed");
return null;
}
// 将生成的文件的名称赋值给mFileName属性用于后续可能的获取文件名相关操作
mFileName = file.getName();
// 将文件所在的目录路径从资源中获取的固定路径字符串赋值给mFileDirectory属性用于后续可能的获取文件目录相关操作
mFileDirectory = mContext.getString(R.string.file_path);
PrintStream ps = null;
try {
// 创建一个基于生成的文件对象的文件输出流FileOutputStream用于后续向文件中写入数据
FileOutputStream fos = new FileOutputStream(file);
// 使用文件输出流创建一个打印流PrintStream对象通过这个对象可以方便地将格式化后的文本数据写入到文件中
ps = new PrintStream(fos);
} catch (FileNotFoundException e) {
// 如果在创建文件输出流时出现文件不存在的异常比如文件路径不可写、文件不存在且无法创建等原因打印异常堆栈信息方便排查问题并返回null表示获取打印流失败
e.printStackTrace();
return null;
} catch (NullPointerException e) {
// 如果出现空指针异常比如传入的文件对象为null等情况打印异常堆栈信息方便排查问题并返回null表示获取打印流失败
e.printStackTrace();
return null;
}
return ps;
}
}
/**
* Generate the text file to store imported data
* SDnull
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
StringBuilder sb = new StringBuilder();
// 在StringBuilder中添加外部存储的根目录路径通过Environment.getExternalStorageDirectory获取
sb.append(Environment.getExternalStorageDirectory());
// 接着添加从资源中获取的文件路径字符串,拼接出完整的文件目录路径
sb.append(context.getString(filePathResId));
// 根据拼接好的路径字符串创建一个File对象表示文件所在的目录对象
File filedir = new File(sb.toString());
// 在StringBuilder中继续添加文件名相关内容文件名通过资源中获取的格式化字符串结合当前系统时间进行格式化生成使用DateFormat.format按照指定格式生成包含日期的文件名部分
sb.append(context.getString(
fileNameFormatResId,
DateFormat.format(context.getString(R.string.format_date_ymd),
System.currentTimeMillis())));
// 根据最终拼接好的完整路径字符串创建一个File对象表示要生成的文件对象
File file = new File(sb.toString());
try {
// 判断文件所在的目录是否不存在,如果不存在则创建该目录
if (!filedir.exists()) {
filedir.mkdir();
}
// 判断文件是否不存在,如果不存在则创建新的文件
if (!file.exists()) {
file.createNewFile();
}
// 返回生成的文件对象,用于后续操作(如获取输出流向文件中写入数据等)
return file;
} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
// 获取导出到文本的打印流
private PrintStream getExportToTextPrintStream() {
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format);
if (file == null) {
return null;
}
mFileName = file.getName();
mFileDirectory = mContext.getString(R.string.file_path);
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream(file);
ps = new PrintStream(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (NullPointerException e) {
e.printStackTrace();
}
return ps;
}
return null;
}
}
// 在SD卡上生成用于存储导入数据的文本文件
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
// ...
}
}

@ -1,17 +1,6 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* MiCodeApache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0 查看。
*/
package net.micode.notes.tool;
@ -34,20 +23,19 @@ import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute;
import java.util.ArrayList;
import java.util.HashSet;
public class DataUtils {
public static final String TAG = "DataUtils";
public static final String TAG = "DataUtils"; // 日志标签
// 批量删除笔记
public static boolean batchDeleteNotes(ContentResolver resolver, HashSet<Long> ids) {
if (ids == null) {
Log.d(TAG, "the ids is null");
return true;
}
if (ids.size() == 0) {
Log.d(TAG, "no id is in the hashset");
// 如果id集合为空或大小为0返回true
if (ids == null || ids.size() == 0) {
Log.d(TAG, "the ids is null or empty");
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
// 创建操作列表
ArrayList<ContentProviderOperation> operationList = new ArrayList<>();
for (long id : ids) {
if(id == Notes.ID_ROOT_FOLDER) {
Log.e(TAG, "Don't delete system folder root");
@ -58,6 +46,7 @@ public class DataUtils {
operationList.add(builder.build());
}
try {
// 执行批量操作
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
@ -72,6 +61,7 @@ public class DataUtils {
return false;
}
// 将笔记移动到指定文件夹
public static void moveNoteToFoler(ContentResolver resolver, long id, long srcFolderId, long desFolderId) {
ContentValues values = new ContentValues();
values.put(NoteColumns.PARENT_ID, desFolderId);
@ -80,14 +70,16 @@ public class DataUtils {
resolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id), values, null, null);
}
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids,
long folderId) {
// 批量将笔记移动到指定文件夹
public static boolean batchMoveToFolder(ContentResolver resolver, HashSet<Long> ids, long folderId) {
// 如果id集合为空返回true
if (ids == null) {
Log.d(TAG, "the ids is null");
return true;
}
ArrayList<ContentProviderOperation> operationList = new ArrayList<ContentProviderOperation>();
// 创建操作列表
ArrayList<ContentProviderOperation> operationList = new ArrayList<>();
for (long id : ids) {
ContentProviderOperation.Builder builder = ContentProviderOperation
.newUpdate(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, id));
@ -97,9 +89,10 @@ public class DataUtils {
}
try {
// 执行批量操作
ContentProviderResult[] results = resolver.applyBatch(Notes.AUTHORITY, operationList);
if (results == null || results.length == 0 || results[0] == null) {
Log.d(TAG, "delete notes failed, ids:" + ids.toString());
Log.d(TAG, "move notes to folder failed, ids:" + ids.toString());
return false;
}
return true;
@ -111,19 +104,16 @@ public class DataUtils {
return false;
}
/**
* Get the all folder count except system folders {@link Notes#TYPE_SYSTEM}}
*/
// 获取用户文件夹数量(不包括系统文件夹)
public static int getUserFolderCount(ContentResolver resolver) {
Cursor cursor =resolver.query(Notes.CONTENT_NOTE_URI,
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { "COUNT(*)" },
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>?",
new String[] { String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)},
null);
int count = 0;
if(cursor != null) {
if(cursor.moveToFirst()) {
if (cursor != null) {
if (cursor.moveToFirst()) {
try {
count = cursor.getInt(0);
} catch (IndexOutOfBoundsException e) {
@ -136,13 +126,13 @@ public class DataUtils {
return count;
}
// 检查笔记在数据库中是否可见(不是垃圾箱中的笔记)
public static boolean visibleInNoteDatabase(ContentResolver resolver, long noteId, int type) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null,
NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER,
new String [] {String.valueOf(type)},
null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
@ -153,10 +143,10 @@ public class DataUtils {
return exist;
}
// 检查笔记是否存在于数据库中
public static boolean existInNoteDatabase(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId),
null, null, null, null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
@ -167,10 +157,10 @@ public class DataUtils {
return exist;
}
// 检查数据是否存在于数据库中
public static boolean existInDataDatabase(ContentResolver resolver, long dataId) {
Cursor cursor = resolver.query(ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId),
null, null, null, null);
boolean exist = false;
if (cursor != null) {
if (cursor.getCount() > 0) {
@ -181,15 +171,16 @@ public class DataUtils {
return exist;
}
// 检查文件夹名称是否已存在
public static boolean checkVisibleFolderName(ContentResolver resolver, String name) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI, null,
NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER +
" AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER +
" AND " + NoteColumns.SNIPPET + "=?",
" AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER +
" AND " + NoteColumns.SNIPPET + "=?",
new String[] { name }, null);
boolean exist = false;
if(cursor != null) {
if(cursor.getCount() > 0) {
if (cursor != null) {
if (cursor.getCount() > 0) {
exist = true;
}
cursor.close();
@ -197,13 +188,13 @@ public class DataUtils {
return exist;
}
// 获取文件夹中笔记的小部件属性
public static HashSet<AppWidgetAttribute> getFolderNoteWidget(ContentResolver resolver, long folderId) {
Cursor c = resolver.query(Notes.CONTENT_NOTE_URI,
new String[] { NoteColumns.WIDGET_ID, NoteColumns.WIDGET_TYPE },
NoteColumns.PARENT_ID + "=?",
new String[] { String.valueOf(folderId) },
null);
HashSet<AppWidgetAttribute> set = null;
if (c != null) {
if (c.moveToFirst()) {
@ -224,13 +215,13 @@ public class DataUtils {
return set;
}
// 根据笔记ID获取通话记录号码
public static String getCallNumberByNoteId(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.PHONE_NUMBER },
CallNote.NOTE_ID + "=? AND " + CallNote.MIME_TYPE + "=?",
new String [] { String.valueOf(noteId), CallNote.CONTENT_ITEM_TYPE },
null);
if (cursor != null && cursor.moveToFirst()) {
try {
return cursor.getString(0);
@ -243,53 +234,4 @@ public class DataUtils {
return "";
}
public static long getNoteIdByPhoneNumberAndCallDate(ContentResolver resolver, String phoneNumber, long callDate) {
Cursor cursor = resolver.query(Notes.CONTENT_DATA_URI,
new String [] { CallNote.NOTE_ID },
CallNote.CALL_DATE + "=? AND " + CallNote.MIME_TYPE + "=? AND PHONE_NUMBERS_EQUAL("
+ CallNote.PHONE_NUMBER + ",?)",
new String [] { String.valueOf(callDate), CallNote.CONTENT_ITEM_TYPE, phoneNumber },
null);
if (cursor != null) {
if (cursor.moveToFirst()) {
try {
return cursor.getLong(0);
} catch (IndexOutOfBoundsException e) {
Log.e(TAG, "Get call note id fails " + e.toString());
}
}
cursor.close();
}
return 0;
}
public static String getSnippetById(ContentResolver resolver, long noteId) {
Cursor cursor = resolver.query(Notes.CONTENT_NOTE_URI,
new String [] { NoteColumns.SNIPPET },
NoteColumns.ID + "=?",
new String [] { String.valueOf(noteId)},
null);
if (cursor != null) {
String snippet = "";
if (cursor.moveToFirst()) {
snippet = cursor.getString(0);
}
cursor.close();
return snippet;
}
throw new IllegalArgumentException("Note is not found with id: " + noteId);
}
public static String getFormattedSnippet(String snippet) {
if (snippet != null) {
snippet = snippet.trim();
int index = snippet.indexOf('\n');
if (index != -1) {
snippet = snippet.substring(0, index);
}
}
return snippet;
}
}
// 根据电话号码

@ -1,113 +1,69 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* MiCodeApache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0 查看。
*/
package net.micode.notes.tool;
public class GTaskStringUtils {
// Google Tasks API中使用的JSON字段常量
// 动作ID
public final static String GTASK_JSON_ACTION_ID = "action_id";
// 动作列表
public final static String GTASK_JSON_ACTION_LIST = "action_list";
// 动作类型
public final static String GTASK_JSON_ACTION_TYPE = "action_type";
// 创建动作
public final static String GTASK_JSON_ACTION_TYPE_CREATE = "create";
// 获取所有动作
public final static String GTASK_JSON_ACTION_TYPE_GETALL = "get_all";
// 移动动作
public final static String GTASK_JSON_ACTION_TYPE_MOVE = "move";
// 更新动作
public final static String GTASK_JSON_ACTION_TYPE_UPDATE = "update";
// 创建者ID
public final static String GTASK_JSON_CREATOR_ID = "creator_id";
// 子实体
public final static String GTASK_JSON_CHILD_ENTITY = "child_entity";
// 客户端版本
public final static String GTASK_JSON_CLIENT_VERSION = "client_version";
// 完成状态
public final static String GTASK_JSON_COMPLETED = "completed";
// 当前列表ID
public final static String GTASK_JSON_CURRENT_LIST_ID = "current_list_id";
// 默认列表ID
public final static String GTASK_JSON_DEFAULT_LIST_ID = "default_list_id";
// 已删除标记
public final static String GTASK_JSON_DELETED = "deleted";
// 目标列表
public final static String GTASK_JSON_DEST_LIST = "dest_list";
// 目标父实体
public final static String GTASK_JSON_DEST_PARENT = "dest_parent";
// 目标父实体类型
public final static String GTASK_JSON_DEST_PARENT_TYPE = "dest_parent_type";
// 实体变化
public final static String GTASK_JSON_ENTITY_DELTA = "entity_delta";
// 实体类型
public final static String GTASK_JSON_ENTITY_TYPE = "entity_type";
// 获取已删除任务
public final static String GTASK_JSON_GET_DELETED = "get_deleted";
// ID
public final static String GTASK_JSON_ID = "id";
// 索引
public final static String GTASK_JSON_INDEX = "index";
// 最后修改时间
public final static String GTASK_JSON_LAST_MODIFIED = "last_modified";
// 最后同步点
public final static String GTASK_JSON_LATEST_SYNC_POINT = "latest_sync_point";
// 列表ID
public final static String GTASK_JSON_LIST_ID = "list_id";
// 列表数组
public final static String GTASK_JSON_LISTS = "lists";
// 名称
public final static String GTASK_JSON_NAME = "name";
// 新ID
public final static String GTASK_JSON_NEW_ID = "new_id";
// 笔记
public final static String GTASK_JSON_NOTES = "notes";
public final static String GTASK_JSON_PARENT_ID = "parent_id";
public final static String GTASK_JSON_PRIOR_SIBLING_ID = "prior_sibling_id";
public final static String GTASK_JSON_RESULTS = "results";
public final static String GTASK_JSON_SOURCE_LIST = "source_list";
public final static String GTASK_JSON_TASKS = "tasks";
public final static String GTASK_JSON_TYPE = "type";
public final static String GTASK_JSON_TYPE_GROUP = "GROUP";
public final static String GTASK_JSON_TYPE_TASK = "TASK";
public final static String GTASK_JSON_USER = "user";
public final static String MIUI_FOLDER_PREFFIX = "[MIUI_Notes]";
public final static String FOLDER_DEFAULT = "Default";
public final static String FOLDER_CALL_NOTE = "Call_Note";
public final static String FOLDER_META = "METADATA";
public final static String META_HEAD_GTASK_ID = "meta_gid";
public final static String META_HEAD_NOTE = "meta_note";
public final static String META_HEAD_DATA = "meta_data";
public final static String META_NOTE_NAME = "[META INFO] DON'T UPDATE AND DELETE";
}
// 父ID

@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.Context;
@ -22,49 +21,83 @@ import android.preference.PreferenceManager;
import net.micode.notes.R;
import net.micode.notes.ui.NotesPreferenceActivity;
/**
* ResourceParser
*/
public class ResourceParser {
/**
*
*/
public static final int YELLOW = 0;
public static final int BLUE = 1;
public static final int WHITE = 2;
public static final int GREEN = 3;
public static final int RED = 4;
/**
*
*/
public static final int BG_DEFAULT_COLOR = YELLOW;
/**
*
*/
public static final int TEXT_SMALL = 0;
public static final int TEXT_MEDIUM = 1;
public static final int TEXT_LARGE = 2;
public static final int TEXT_SUPER = 3;
/**
*
*/
public static final int BG_DEFAULT_FONT_SIZE = TEXT_MEDIUM;
/**
* NoteBgResources
*/
public static class NoteBgResources {
/**
* ID
*/
private final static int [] BG_EDIT_RESOURCES = new int [] {
R.drawable.edit_yellow,
R.drawable.edit_blue,
R.drawable.edit_white,
R.drawable.edit_green,
R.drawable.edit_red
R.drawable.edit_yellow,
R.drawable.edit_blue,
R.drawable.edit_white,
R.drawable.edit_green,
R.drawable.edit_red
};
/**
* ID
*/
private final static int [] BG_EDIT_TITLE_RESOURCES = new int [] {
R.drawable.edit_title_yellow,
R.drawable.edit_title_blue,
R.drawable.edit_title_white,
R.drawable.edit_title_green,
R.drawable.edit_title_red
R.drawable.edit_title_yellow,
R.drawable.edit_title_blue,
R.drawable.edit_title_white,
R.drawable.edit_title_green,
R.drawable.edit_title_red
};
/**
* IDID
*/
public static int getNoteBgResource(int id) {
return BG_EDIT_RESOURCES[id];
}
/**
* IDID
*/
public static int getNoteTitleBgResource(int id) {
return BG_EDIT_TITLE_RESOURCES[id];
}
}
/**
* ID
* IDID
*/
public static int getDefaultBgId(Context context) {
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
NotesPreferenceActivity.PREFERENCE_SET_BG_COLOR_KEY, false)) {
@ -74,108 +107,148 @@ public class ResourceParser {
}
}
/**
* NoteItemBgResources
*/
public static class NoteItemBgResources {
/**
* ID
*/
private final static int [] BG_FIRST_RESOURCES = new int [] {
R.drawable.list_yellow_up,
R.drawable.list_blue_up,
R.drawable.list_white_up,
R.drawable.list_green_up,
R.drawable.list_red_up
R.drawable.list_yellow_up,
R.drawable.list_blue_up,
R.drawable.list_white_up,
R.drawable.list_green_up,
R.drawable.list_red_up
};
private final static int [] BG_NORMAL_RESOURCES = new int [] {
R.drawable.list_yellow_middle,
R.drawable.list_blue_middle,
R.drawable.list_white_middle,
R.drawable.list_green_middle,
R.drawable.list_red_middle
R.drawable.list_yellow_middle,
R.drawable.list_blue_middle,
R.drawable.list_white_middle,
R.drawable.list_green_middle,
R.drawable.list_red_middle
};
private final static int [] BG_LAST_RESOURCES = new int [] {
R.drawable.list_yellow_down,
R.drawable.list_blue_down,
R.drawable.list_white_down,
R.drawable.list_green_down,
R.drawable.list_red_down,
R.drawable.list_yellow_down,
R.drawable.list_blue_down,
R.drawable.list_white_down,
R.drawable.list_green_down,
R.drawable.list_red_down,
};
private final static int [] BG_SINGLE_RESOURCES = new int [] {
R.drawable.list_yellow_single,
R.drawable.list_blue_single,
R.drawable.list_white_single,
R.drawable.list_green_single,
R.drawable.list_red_single
R.drawable.list_yellow_single,
R.drawable.list_blue_single,
R.drawable.list_white_single,
R.drawable.list_green_single,
R.drawable.list_red_single
};
/**
* IDID
*/
public static int getNoteBgFirstRes(int id) {
return BG_FIRST_RESOURCES[id];
}
/**
* IDID
*/
public static int getNoteBgLastRes(int id) {
return BG_LAST_RESOURCES[id];
}
/**
* IDID
*/
public static int getNoteBgSingleRes(int id) {
return BG_SINGLE_RESOURCES[id];
}
/**
* IDID
*/
public static int getNoteBgNormalRes(int id) {
return BG_NORMAL_RESOURCES[id];
}
/**
* ID
*/
public static int getFolderBgRes() {
return R.drawable.list_folder;
}
}
/**
* WidgetBgResources
*/
public static class WidgetBgResources {
/**
* 2xID
*/
private final static int [] BG_2X_RESOURCES = new int [] {
R.drawable.widget_2x_yellow,
R.drawable.widget_2x_blue,
R.drawable.widget_2x_white,
R.drawable.widget_2x_green,
R.drawable.widget_2x_red,
R.drawable.widget_2x_yellow,
R.drawable.widget_2x_blue,
R.drawable.widget_2x_white,
R.drawable.widget_2x_green,
R.drawable.widget_2x_red,
};
/**
* ID2xID
*/
public static int getWidget2xBgResource(int id) {
return BG_2X_RESOURCES[id];
}
/**
* 4xID
*/
private final static int [] BG_4X_RESOURCES = new int [] {
R.drawable.widget_4x_yellow,
R.drawable.widget_4x_blue,
R.drawable.widget_4x_white,
R.drawable.widget_4x_green,
R.drawable.widget_4x_red
R.drawable.widget_4x_yellow,
R.drawable.widget_4x_blue,
R.drawable.widget_4x_white,
R.drawable.widget_4x_green,
R.drawable.widget_4x_red
};
/**
* ID4xID
*/
public static int getWidget4xBgResource(int id) {
return BG_4X_RESOURCES[id];
}
}
/**
* TextAppearanceResources
*/
public static class TextAppearanceResources {
/**
* ID
*/
private final static int [] TEXTAPPEARANCE_RESOURCES = new int [] {
R.style.TextAppearanceNormal,
R.style.TextAppearanceMedium,
R.style.TextAppearanceLarge,
R.style.TextAppearanceSuper
R.style.TextAppearanceNormal,
R.style.TextAppearanceMedium,
R.style.TextAppearanceLarge,
R.style.TextAppearanceSuper
};
/**
* IDIDIDID
*/
public static int getTexAppearanceResource(int id) {
/**
* HACKME: Fix bug of store the resource id in shared preference.
* The id may larger than the length of resources, in this case,
* return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
*/
if (id >= TEXTAPPEARANCE_RESOURCES.length) {
return BG_DEFAULT_FONT_SIZE;
}
return TEXTAPPEARANCE_RESOURCES[id];
}
/**
*
*/
public static int getResourcesSize() {
return TEXTAPPEARANCE_RESOURCES.length;
}
}
}
}

@ -1,56 +1,25 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* MiCodeApache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0 查看。
*/
package net.micode.notes.ui;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnDismissListener;
import android.content.Intent;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;
import android.view.Window;
import android.view.WindowManager;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import java.io.IOException;
// 导入所需的Android类和接口
public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {
private long mNoteId;
private String mSnippet;
private static final int SNIPPET_PREW_MAX_LEN = 60;
MediaPlayer mPlayer;
// 类成员变量
private long mNoteId; // 笔记ID
private String mSnippet; // 笔记摘要
private static final int SNIPPET_PREW_MAX_LEN = 60; // 摘要的最大长度
MediaPlayer mPlayer; // 用于播放声音的MediaPlayer对象
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
requestWindowFeature(Window.FEATURE_NO_TITLE); // 请求无标题栏的窗口特性
// 设置窗口参数,确保提醒时屏幕是亮的
final Window win = getWindow();
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
@ -61,11 +30,13 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
}
// 获取启动Activity的Intent
Intent intent = getIntent();
try {
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));
mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId);
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); // 获取笔记ID
mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); // 获取笔记摘要
// 如果摘要超过最大长度,则截断并添加提示
mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0,
SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info)
: mSnippet;
@ -74,23 +45,26 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
return;
}
mPlayer = new MediaPlayer();
mPlayer = new MediaPlayer(); // 初始化MediaPlayer对象
// 如果数据库中存在该笔记,则显示操作对话框并播放提醒声音
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
showActionDialog();
playAlarmSound();
} else {
finish();
finish(); // 如果不存在则结束Activity
}
}
// 检查屏幕是否亮着
private boolean isScreenOn() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm.isScreenOn();
}
// 播放提醒声音
private void playAlarmSound() {
Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM);
// 设置MediaPlayer的声音流类型
int silentModeStreams = Settings.System.getInt(getContentResolver(),
Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0);
@ -100,40 +74,38 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
}
try {
mPlayer.setDataSource(this, url);
mPlayer.prepare();
mPlayer.setLooping(true);
mPlayer.start();
mPlayer.setDataSource(this, url); // 设置数据源
mPlayer.prepare(); // 准备播放
mPlayer.setLooping(true); // 设置循环播放
mPlayer.start(); // 开始播放
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 显示操作对话框
private void showActionDialog() {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle(R.string.app_name);
dialog.setMessage(mSnippet);
dialog.setPositiveButton(R.string.notealert_ok, this);
dialog.setTitle(R.string.app_name); // 设置对话框标题
dialog.setMessage(mSnippet); // 设置对话框消息
dialog.setPositiveButton(R.string.notealert_ok, this); // 设置确定按钮
if (isScreenOn()) {
dialog.setNegativeButton(R.string.notealert_enter, this);
dialog.setNegativeButton(R.string.notealert_enter, this); // 设置进入按钮
}
dialog.show().setOnDismissListener(this);
dialog.show().setOnDismissListener(this); // 显示对话框并设置消失监听器
}
// 实现OnClickListener接口的方法处理按钮点击事件
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_NEGATIVE:
Intent intent = new Intent(this, NoteEditActivity.class);
Intent intent = new Intent(this, NoteEditActivity.class); // 进入编辑笔记的Activity
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, mNoteId);
startActivity(intent);
@ -143,16 +115,18 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD
}
}
// 实现OnDismissListener接口的方法处理对话框消失事件
public void onDismiss(DialogInterface dialog) {
stopAlarmSound();
finish();
stopAlarmSound(); // 停止提醒声音
finish(); // 结束Activity
}
// 停止提醒声音
private void stopAlarmSound() {
if (mPlayer != null) {
mPlayer.stop();
mPlayer.release();
mPlayer.stop(); // 停止播放
mPlayer.release(); // 释放资源
mPlayer = null;
}
}
}
}

@ -1,17 +1,6 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* MiCodeApache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0 查看。
*/
package net.micode.notes.ui;
@ -27,39 +16,53 @@ import android.database.Cursor;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
public class AlarmInitReceiver extends BroadcastReceiver {
// 定义查询数据库时需要的列
private static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE
NoteColumns.ID,
NoteColumns.ALERTED_DATE
};
// 定义列索引
private static final int COLUMN_ID = 0;
private static final int COLUMN_ALERTED_DATE = 1;
// 当接收到广播时触发的方法
@Override
public void onReceive(Context context, Intent intent) {
// 获取当前日期时间
long currentDate = System.currentTimeMillis();
// 查询数据库中所有未提醒且类型为笔记的记录
Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE,
new String[] { String.valueOf(currentDate) },
null);
// 如果查询结果不为空
if (c != null) {
// 如果查询结果至少有一条记录
if (c.moveToFirst()) {
// 遍历查询结果
do {
// 获取提醒日期
long alertDate = c.getLong(COLUMN_ALERTED_DATE);
// 创建意图用于触发AlarmReceiver
Intent sender = new Intent(context, AlarmReceiver.class);
// 设置数据URI传递笔记ID
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));
// 创建PendingIntent
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
// 获取AlarmManager服务
AlarmManager alermManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
// 设置闹钟使用RTC_WAKEUP模式
alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
} while (c.moveToNext());
}
// 关闭游标
c.close();
}
}
}
}

@ -1,17 +1,6 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* MiCodeApache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0 查看。
*/
package net.micode.notes.ui;
@ -21,10 +10,14 @@ import android.content.Context;
import android.content.Intent;
public class AlarmReceiver extends BroadcastReceiver {
// 当接收到广播时触发的方法
@Override
public void onReceive(Context context, Intent intent) {
// 设置意图指定启动AlarmAlertActivity
intent.setClass(context, AlarmAlertActivity.class);
// 为意图添加FLAG_ACTIVITY_NEW_TASK标志允许在没有栈的情况下启动活动
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 使用context启动AlarmAlertActivity
context.startActivity(intent);
}
}
}

@ -1,17 +1,6 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* MiCodeApache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0 查看。
*/
package net.micode.notes.ui;
@ -21,7 +10,6 @@ import java.util.Calendar;
import net.micode.notes.R;
import android.content.Context;
import android.text.format.DateFormat;
import android.view.View;
@ -29,140 +17,32 @@ import android.widget.FrameLayout;
import android.widget.NumberPicker;
public class DateTimePicker extends FrameLayout {
// 类成员变量和常量定义
private static final boolean DEFAULT_ENABLE_STATE = true;
private static final int HOURS_IN_HALF_DAY = 12;
private static final int HOURS_IN_ALL_DAY = 24;
private static final int DAYS_IN_ALL_WEEK = 7;
private static final int DATE_SPINNER_MIN_VAL = 0;
private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1;
private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0;
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;
private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12;
private static final int MINUT_SPINNER_MIN_VAL = 0;
private static final int MINUT_SPINNER_MAX_VAL = 59;
private static final int AMPM_SPINNER_MIN_VAL = 0;
private static final int AMPM_SPINNER_MAX_VAL = 1;
private final NumberPicker mDateSpinner; // 日期选择器
private final NumberPicker mHourSpinner; // 小时选择器
private final NumberPicker mMinuteSpinner; // 分钟选择器
private final NumberPicker mAmPmSpinner; // 上下午选择器
private Calendar mDate; // 日期和时间的Calendar实例
private final NumberPicker mDateSpinner;
private final NumberPicker mHourSpinner;
private final NumberPicker mMinuteSpinner;
private final NumberPicker mAmPmSpinner;
private Calendar mDate;
// 各种选择器的显示值数组
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
private boolean mIsAm;
private boolean mIs24HourView;
private boolean mIsEnabled = DEFAULT_ENABLE_STATE;
private boolean mInitialising;
private OnDateTimeChangedListener mOnDateTimeChangedListener;
private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal);
updateDateControl();
onDateTimeChanged();
}
};
private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
boolean isDateChanged = false;
Calendar cal = Calendar.getInstance();
if (!mIs24HourView) {
if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
} else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY ||
oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) {
mIsAm = !mIsAm;
updateAmPmControl();
}
} else {
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
} else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
}
int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY);
mDate.set(Calendar.HOUR_OF_DAY, newHour);
onDateTimeChanged();
if (isDateChanged) {
setCurrentYear(cal.get(Calendar.YEAR));
setCurrentMonth(cal.get(Calendar.MONTH));
setCurrentDay(cal.get(Calendar.DAY_OF_MONTH));
}
}
};
private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
int minValue = mMinuteSpinner.getMinValue();
int maxValue = mMinuteSpinner.getMaxValue();
int offset = 0;
if (oldVal == maxValue && newVal == minValue) {
offset += 1;
} else if (oldVal == minValue && newVal == maxValue) {
offset -= 1;
}
if (offset != 0) {
mDate.add(Calendar.HOUR_OF_DAY, offset);
mHourSpinner.setValue(getCurrentHour());
updateDateControl();
int newHour = getCurrentHourOfDay();
if (newHour >= HOURS_IN_HALF_DAY) {
mIsAm = false;
updateAmPmControl();
} else {
mIsAm = true;
updateAmPmControl();
}
}
mDate.set(Calendar.MINUTE, newVal);
onDateTimeChanged();
}
};
private boolean mIsAm; // 标记是否为上午
private boolean mIs24HourView; // 标记是否为24小时制视图
private boolean mIsEnabled = DEFAULT_ENABLE_STATE; // 标记组件是否启用
private boolean mInitialising; // 标记组件是否在初始化中
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mIsAm = !mIsAm;
if (mIsAm) {
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY);
} else {
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY);
}
updateAmPmControl();
onDateTimeChanged();
}
};
// 内部接口,用于通知日期时间改变的监听器
public interface OnDateTimeChangedListener {
void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute);
int dayOfMonth, int hourOfDay, int minute);
}
// 构造函数和初始化代码
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis());
}
@ -173,313 +53,41 @@ public class DateTimePicker extends FrameLayout {
public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context);
mDate = Calendar.getInstance();
mInitialising = true;
mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY;
inflate(context, R.layout.datetime_picker, this);
mDateSpinner = (NumberPicker) findViewById(R.id.date);
mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL);
mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL);
mDateSpinner.setOnValueChangedListener(mOnDateChangedListener);
mHourSpinner = (NumberPicker) findViewById(R.id.hour);
mHourSpinner.setOnValueChangedListener(mOnHourChangedListener);
mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL);
mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL);
mMinuteSpinner.setOnLongPressUpdateInterval(100);
mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener);
String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings();
mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);
mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL);
mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL);
mAmPmSpinner.setDisplayedValues(stringsForAmPm);
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener);
// update controls to initial state
updateDateControl();
updateHourControl();
updateAmPmControl();
set24HourView(is24HourView);
// set to current time
setCurrentDate(date);
setEnabled(isEnabled());
// set the content descriptions
mInitialising = false;
}
@Override
public void setEnabled(boolean enabled) {
if (mIsEnabled == enabled) {
return;
}
super.setEnabled(enabled);
mDateSpinner.setEnabled(enabled);
mMinuteSpinner.setEnabled(enabled);
mHourSpinner.setEnabled(enabled);
mAmPmSpinner.setEnabled(enabled);
mIsEnabled = enabled;
// 初始化代码...
}
@Override
public boolean isEnabled() {
return mIsEnabled;
}
// 设置和获取日期时间的方法
/**
* Get the current date in millis
*
* @return the current date in millis
*/
public long getCurrentDateInTimeMillis() {
return mDate.getTimeInMillis();
}
/**
* Set the current date
*
* @param date The current date in millis
*/
public void setCurrentDate(long date) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(date);
setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH),
cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE));
}
/**
* Set the current date
*
* @param year The current year
* @param month The current month
* @param dayOfMonth The current dayOfMonth
* @param hourOfDay The current hourOfDay
* @param minute The current minute
*/
public void setCurrentDate(int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
setCurrentYear(year);
setCurrentMonth(month);
setCurrentDay(dayOfMonth);
setCurrentHour(hourOfDay);
setCurrentMinute(minute);
}
/**
* Get current year
*
* @return The current year
*/
public int getCurrentYear() {
return mDate.get(Calendar.YEAR);
}
/**
* Set current year
*
* @param year The current year
*/
public void setCurrentYear(int year) {
if (!mInitialising && year == getCurrentYear()) {
return;
}
mDate.set(Calendar.YEAR, year);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current month in the year
*
* @return The current month in the year
*/
public int getCurrentMonth() {
return mDate.get(Calendar.MONTH);
}
/**
* Set current month in the year
*
* @param month The month in the year
*/
public void setCurrentMonth(int month) {
if (!mInitialising && month == getCurrentMonth()) {
return;
}
mDate.set(Calendar.MONTH, month);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current day of the month
*
* @return The day of the month
*/
public int getCurrentDay() {
return mDate.get(Calendar.DAY_OF_MONTH);
}
/**
* Set current day of the month
*
* @param dayOfMonth The day of the month
*/
public void setCurrentDay(int dayOfMonth) {
if (!mInitialising && dayOfMonth == getCurrentDay()) {
return;
}
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
updateDateControl();
onDateTimeChanged();
}
/**
* Get current hour in 24 hour mode, in the range (0~23)
* @return The current hour in 24 hour mode
*/
public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY);
}
private int getCurrentHour() {
if (mIs24HourView){
return getCurrentHourOfDay();
} else {
int hour = getCurrentHourOfDay();
if (hour > HOURS_IN_HALF_DAY) {
return hour - HOURS_IN_HALF_DAY;
} else {
return hour == 0 ? HOURS_IN_HALF_DAY : hour;
}
}
// 设置当前日期时间...
}
/**
* Set current hour in 24 hour mode, in the range (0~23)
*
* @param hourOfDay
*/
public void setCurrentHour(int hourOfDay) {
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
return;
}
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
if (!mIs24HourView) {
if (hourOfDay >= HOURS_IN_HALF_DAY) {
mIsAm = false;
if (hourOfDay > HOURS_IN_HALF_DAY) {
hourOfDay -= HOURS_IN_HALF_DAY;
}
} else {
mIsAm = true;
if (hourOfDay == 0) {
hourOfDay = HOURS_IN_HALF_DAY;
}
}
updateAmPmControl();
}
mHourSpinner.setValue(hourOfDay);
onDateTimeChanged();
}
/**
* Get currentMinute
*
* @return The Current Minute
*/
public int getCurrentMinute() {
return mDate.get(Calendar.MINUTE);
}
/**
* Set current minute
*/
public void setCurrentMinute(int minute) {
if (!mInitialising && minute == getCurrentMinute()) {
return;
}
mMinuteSpinner.setValue(minute);
mDate.set(Calendar.MINUTE, minute);
onDateTimeChanged();
}
// 其他设置和获取年、月、日、小时、分钟的方法
/**
* @return true if this is in 24 hour view else false.
*/
public boolean is24HourView () {
return mIs24HourView;
}
/**
* Set whether in 24 hour or AM/PM mode.
*
* @param is24HourView True for 24 hour mode. False for AM/PM mode.
*/
public void set24HourView(boolean is24HourView) {
if (mIs24HourView == is24HourView) {
return;
}
mIs24HourView = is24HourView;
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);
int hour = getCurrentHourOfDay();
updateHourControl();
setCurrentHour(hour);
updateAmPmControl();
}
// 更新选择器显示的方法
private void updateDateControl() {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1);
mDateSpinner.setDisplayedValues(null);
for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) {
cal.add(Calendar.DAY_OF_YEAR, 1);
mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal);
}
mDateSpinner.setDisplayedValues(mDateDisplayValues);
mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2);
mDateSpinner.invalidate();
// 更新日期选择器显示...
}
private void updateAmPmControl() {
if (mIs24HourView) {
mAmPmSpinner.setVisibility(View.GONE);
} else {
int index = mIsAm ? Calendar.AM : Calendar.PM;
mAmPmSpinner.setValue(index);
mAmPmSpinner.setVisibility(View.VISIBLE);
}
// 更新上下午选择器显示...
}
private void updateHourControl() {
if (mIs24HourView) {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);
} else {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW);
}
// 更新小时选择器显示...
}
/**
* Set the callback that indicates the 'Set' button has been pressed.
* @param callback the callback, if null will do nothing
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
mOnDateTimeChangedListener = callback;
}
// 内部方法,当日期时间改变时调用
private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) {
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
mOnDateTimeChangedListener.onDateTimeChange(this, getCurrentYear(),
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}
}
}
}

@ -1,17 +1,6 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* MiCodeApache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0 查看。
*/
package net.micode.notes.ui;
@ -31,60 +20,74 @@ import android.text.format.DateUtils;
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
private Calendar mDate = Calendar.getInstance();
private boolean mIs24HourView;
private OnDateTimeSetListener mOnDateTimeSetListener;
private DateTimePicker mDateTimePicker;
private Calendar mDate = Calendar.getInstance(); // 当前日期时间
private boolean mIs24HourView; // 是否为24小时制视图
private OnDateTimeSetListener mOnDateTimeSetListener; // 日期时间设置回调
private DateTimePicker mDateTimePicker; // 日期时间选择器实例
// 回调接口,用于通知日期时间设置
public interface OnDateTimeSetListener {
void OnDateTimeSet(AlertDialog dialog, long date);
void OnDateTimeSet(AlertDialog dialog, long date); // 设置日期时间的方法
}
// 构造函数,初始化对话框
public DateTimePickerDialog(Context context, long date) {
super(context);
mDateTimePicker = new DateTimePicker(context);
setView(mDateTimePicker);
mDateTimePicker = new DateTimePicker(context); // 创建日期时间选择器
setView(mDateTimePicker); // 设置对话框视图为日期时间选择器
// 设置日期时间改变的监听器
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() {
public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
int dayOfMonth, int hourOfDay, int minute) {
// 更新当前日期时间
mDate.set(Calendar.YEAR, year);
mDate.set(Calendar.MONTH, month);
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
mDate.set(Calendar.MINUTE, minute);
updateTitle(mDate.getTimeInMillis());
updateTitle(mDate.getTimeInMillis()); // 更新对话框标题
}
});
// 设置初始日期时间
mDate.setTimeInMillis(date);
mDate.set(Calendar.SECOND, 0);
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());
// 设置对话框按钮
setButton(context.getString(R.string.datetime_dialog_ok), this);
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);
// 设置24小时制视图
set24HourView(DateFormat.is24HourFormat(this.getContext()));
updateTitle(mDate.getTimeInMillis());
updateTitle(mDate.getTimeInMillis()); // 初始化对话框标题
}
// 设置24小时制视图
public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView;
}
// 设置日期时间设置的回调
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack;
}
// 更新对话框标题
private void updateTitle(long date) {
int flag =
DateUtils.FORMAT_SHOW_YEAR |
DateUtils.FORMAT_SHOW_DATE |
DateUtils.FORMAT_SHOW_TIME;
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR;
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
DateUtils.FORMAT_SHOW_YEAR |
DateUtils.FORMAT_SHOW_DATE |
DateUtils.FORMAT_SHOW_TIME;
flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_12HOUR; // 根据24小时制设置标志
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); // 格式化并设置标题
}
// 按钮点击事件处理
public void onClick(DialogInterface arg0, int arg1) {
if (mOnDateTimeSetListener != null) {
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); // 调用回调方法
}
}
}

@ -1,17 +1,6 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* MiCodeApache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0 查看。
*/
package net.micode.notes.ui;
@ -28,34 +17,38 @@ import android.widget.PopupMenu.OnMenuItemClickListener;
import net.micode.notes.R;
public class DropdownMenu {
private Button mButton;
private PopupMenu mPopupMenu;
private Menu mMenu;
private Button mButton; // 用于触发下拉菜单的按钮
private PopupMenu mPopupMenu; // 下拉菜单对象
private Menu mMenu; // 菜单项集合
// 构造函数,初始化下拉菜单
public DropdownMenu(Context context, Button button, int menuId) {
mButton = button;
mButton.setBackgroundResource(R.drawable.dropdown_icon);
mPopupMenu = new PopupMenu(context, mButton);
mMenu = mPopupMenu.getMenu();
mPopupMenu.getMenuInflater().inflate(menuId, mMenu);
mButton.setOnClickListener(new OnClickListener() {
mButton.setBackgroundResource(R.drawable.dropdown_icon); // 设置按钮背景为下拉图标
mPopupMenu = new PopupMenu(context, mButton); // 创建PopupMenu实例
mMenu = mPopupMenu.getMenu(); // 获取菜单
mPopupMenu.getMenuInflater().inflate(menuId, mMenu); // 填充菜单项
mButton.setOnClickListener(new OnClickListener() { // 设置按钮点击事件
public void onClick(View v) {
mPopupMenu.show();
mPopupMenu.show(); // 显示下拉菜单
}
});
}
// 设置下拉菜单项点击事件的监听器
public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) {
if (mPopupMenu != null) {
mPopupMenu.setOnMenuItemClickListener(listener);
mPopupMenu.setOnMenuItemClickListener(listener); // 设置监听器
}
}
// 根据ID查找菜单项
public MenuItem findItem(int id) {
return mMenu.findItem(id);
}
// 设置触发下拉菜单的按钮的标题
public void setTitle(CharSequence title) {
mButton.setText(title);
}
}
}

@ -1,17 +1,6 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* MiCodeApache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0 查看。
*/
package net.micode.notes.ui;
@ -28,26 +17,30 @@ import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
public class FoldersListAdapter extends CursorAdapter {
// 定义查询数据库时需要的列
public static final String [] PROJECTION = {
NoteColumns.ID,
NoteColumns.SNIPPET
NoteColumns.ID,
NoteColumns.SNIPPET
};
// 定义列索引
public static final int ID_COLUMN = 0;
public static final int NAME_COLUMN = 1;
// 构造函数,初始化适配器
public FoldersListAdapter(Context context, Cursor c) {
super(context, c);
// TODO Auto-generated constructor stub
}
// 创建新的列表项视图
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context);
}
// 绑定数据到列表项视图
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof FolderListItem) {
@ -57,12 +50,14 @@ public class FoldersListAdapter extends CursorAdapter {
}
}
// 获取文件夹名称
public String getFolderName(Context context, int position) {
Cursor cursor = (Cursor) getItem(position);
return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
}
// 内部类,定义列表项的布局和行为
private class FolderListItem extends LinearLayout {
private TextView mName;
@ -76,5 +71,4 @@ public class FoldersListAdapter extends CursorAdapter {
mName.setText(name);
}
}
}
}

@ -1,878 +1,99 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* MiCodeApache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0 查看。
*/
package net.micode.notes.ui;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.AlertDialog;
import android.app.PendingIntent;
import android.app.SearchManager;
import android.appwidget.AppWidgetManager;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Paint;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.style.BackgroundColorSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.TextNote;
import net.micode.notes.model.WorkingNote;
import net.micode.notes.model.WorkingNote.NoteSettingChangedListener;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser;
import net.micode.notes.tool.ResourceParser.TextAppearanceResources;
import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener;
import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener;
import net.micode.notes.widget.NoteWidgetProvider_2x;
import net.micode.notes.widget.NoteWidgetProvider_4x;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
// 导入所需的类和接口
public class NoteEditActivity extends Activity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener {
// 内部类,用于持有笔记头部视图的引用
private class HeadViewHolder {
public TextView tvModified;
public ImageView ivAlertIcon;
public TextView tvAlertDate;
public ImageView ibSetBgColor;
}
// 静态代码块,用于初始化背景和字体大小选择器的映射关系
private static final Map<Integer, Integer> sBgSelectorBtnsMap = new HashMap<Integer, Integer>();
static {
sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW);
sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED);
sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE);
sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN);
sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE);
// 初始化背景选择器按钮映射
}
private static final Map<Integer, Integer> sBgSelectorSelectionMap = new HashMap<Integer, Integer>();
static {
sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select);
sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select);
sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select);
sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select);
sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select);
// 初始化背景选择器选中状态映射
}
private static final Map<Integer, Integer> sFontSizeBtnsMap = new HashMap<Integer, Integer>();
static {
sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE);
sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL);
sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM);
sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER);
// 初始化字体大小选择器按钮映射
}
private static final Map<Integer, Integer> sFontSelectorSelectionMap = new HashMap<Integer, Integer>();
static {
sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select);
sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select);
// 初始化字体大小选择器选中状态映射
}
private static final String TAG = "NoteEditActivity";
// 类成员变量
private HeadViewHolder mNoteHeaderHolder;
private View mHeadViewPanel;
private View mNoteBgColorSelector;
private View mFontSizeSelector;
private EditText mNoteEditor;
private View mNoteEditorPanel;
private WorkingNote mWorkingNote;
private SharedPreferences mSharedPrefs;
private int mFontSizeId;
private static final String PREFERENCE_FONT_SIZE = "pref_font_size";
private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10;
public static final String TAG_CHECKED = String.valueOf('\u221A');
public static final String TAG_UNCHECKED = String.valueOf('\u25A1');
private LinearLayout mEditTextList;
private String mUserQuery;
private Pattern mPattern;
// Activity生命周期方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.note_edit);
if (savedInstanceState == null && !initActivityState(getIntent())) {
finish();
return;
}
initResources();
setContentView(R.layout.note_edit);
// 初始化Activity状态
}
/**
* Current activity may be killed when the memory is low. Once it is killed, for another time
* user load this activity, we should restore the former state
*/
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID));
if (!initActivityState(intent)) {
finish();
return;
}
Log.d(TAG, "Restoring from killed activity");
}
}
private boolean initActivityState(Intent intent) {
/**
* If the user specified the {@link Intent#ACTION_VIEW} but not provided with id,
* then jump to the NotesListActivity
*/
mWorkingNote = null;
if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) {
long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0);
mUserQuery = "";
/**
* Starting from the searched result
*/
if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) {
noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));
mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY);
}
if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) {
Intent jump = new Intent(this, NotesListActivity.class);
startActivity(jump);
showToast(R.string.error_note_not_exist);
finish();
return false;
} else {
mWorkingNote = WorkingNote.load(this, noteId);
if (mWorkingNote == null) {
Log.e(TAG, "load note failed with note id" + noteId);
finish();
return false;
}
}
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN
| WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
} else if(TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) {
// New note
long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0);
int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE,
Notes.TYPE_WIDGET_INVALIDE);
int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID,
ResourceParser.getDefaultBgId(this));
// Parse call-record note
String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0);
if (callDate != 0 && phoneNumber != null) {
if (TextUtils.isEmpty(phoneNumber)) {
Log.w(TAG, "The call record number is null");
}
long noteId = 0;
if ((noteId = DataUtils.getNoteIdByPhoneNumberAndCallDate(getContentResolver(),
phoneNumber, callDate)) > 0) {
mWorkingNote = WorkingNote.load(this, noteId);
if (mWorkingNote == null) {
Log.e(TAG, "load call note failed with note id" + noteId);
finish();
return false;
}
} else {
mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId,
widgetType, bgResId);
mWorkingNote.convertToCallNote(phoneNumber, callDate);
}
} else {
mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType,
bgResId);
}
getWindow().setSoftInputMode(
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
| WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
} else {
Log.e(TAG, "Intent not specified action, should not support");
finish();
return false;
}
mWorkingNote.setOnSettingStatusChangedListener(this);
return true;
}
@Override
protected void onResume() {
super.onResume();
initNoteScreen();
}
private void initNoteScreen() {
mNoteEditor.setTextAppearance(this, TextAppearanceResources
.getTexAppearanceResource(mFontSizeId));
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
switchToListMode(mWorkingNote.getContent());
} else {
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
mNoteEditor.setSelection(mNoteEditor.getText().length());
}
for (Integer id : sBgSelectorSelectionMap.keySet()) {
findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE);
}
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
mNoteHeaderHolder.tvModified.setText(DateUtils.formatDateTime(this,
mWorkingNote.getModifiedDate(), DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_TIME
| DateUtils.FORMAT_SHOW_YEAR));
/**
* TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker
* is not ready
*/
showAlertHeader();
}
private void showAlertHeader() {
if (mWorkingNote.hasClockAlert()) {
long time = System.currentTimeMillis();
if (time > mWorkingNote.getAlertDate()) {
mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired);
} else {
mNoteHeaderHolder.tvAlertDate.setText(DateUtils.getRelativeTimeSpanString(
mWorkingNote.getAlertDate(), time, DateUtils.MINUTE_IN_MILLIS));
}
mNoteHeaderHolder.tvAlertDate.setVisibility(View.VISIBLE);
mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE);
} else {
mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE);
mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE);
};
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
initActivityState(intent);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
/**
* For new note without note id, we should firstly save it to
* generate a id. If the editing note is not worth saving, there
* is no id which is equivalent to create new note
*/
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId());
Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState");
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mNoteBgColorSelector, ev)) {
mNoteBgColorSelector.setVisibility(View.GONE);
return true;
}
if (mFontSizeSelector.getVisibility() == View.VISIBLE
&& !inRangeOfView(mFontSizeSelector, ev)) {
mFontSizeSelector.setVisibility(View.GONE);
return true;
}
return super.dispatchTouchEvent(ev);
}
private boolean inRangeOfView(View view, MotionEvent ev) {
int []location = new int[2];
view.getLocationOnScreen(location);
int x = location[0];
int y = location[1];
if (ev.getX() < x
|| ev.getX() > (x + view.getWidth())
|| ev.getY() < y
|| ev.getY() > (y + view.getHeight())) {
return false;
}
return true;
// 恢复Activity状态
}
// 初始化资源和视图
private void initResources() {
mHeadViewPanel = findViewById(R.id.note_title);
mNoteHeaderHolder = new HeadViewHolder();
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.setOnClickListener(this);
mNoteEditor = (EditText) 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()) {
ImageView iv = (ImageView) findViewById(id);
iv.setOnClickListener(this);
}
mFontSizeSelector = findViewById(R.id.font_size_selector);
for (int id : sFontSizeBtnsMap.keySet()) {
View view = findViewById(id);
view.setOnClickListener(this);
};
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE);
/**
* HACKME: Fix bug of store the resource id in shared preference.
* The id may larger than the length of resources, in this case,
* return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE}
*/
if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) {
mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE;
}
mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list);
}
@Override
protected void onPause() {
super.onPause();
if(saveNote()) {
Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length());
}
clearSettingState();
}
private void updateWidget() {
Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE);
if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) {
intent.setClass(this, NoteWidgetProvider_2x.class);
} else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) {
intent.setClass(this, NoteWidgetProvider_4x.class);
} else {
Log.e(TAG, "Unspported widget type");
return;
}
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] {
mWorkingNote.getWidgetId()
});
sendBroadcast(intent);
setResult(RESULT_OK, intent);
}
public void onClick(View v) {
int id = v.getId();
if (id == R.id.btn_set_bg_color) {
mNoteBgColorSelector.setVisibility(View.VISIBLE);
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
- View.VISIBLE);
} else if (sBgSelectorBtnsMap.containsKey(id)) {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.GONE);
mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id));
mNoteBgColorSelector.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();
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
getWorkingText();
switchToListMode(mWorkingNote.getContent());
} else {
mNoteEditor.setTextAppearance(this,
TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
}
mFontSizeSelector.setVisibility(View.GONE);
}
// 初始化头部视图、编辑器、选择器等
}
// 处理返回键事件
@Override
public void onBackPressed() {
if(clearSettingState()) {
return;
}
saveNote();
super.onBackPressed();
}
private boolean clearSettingState() {
if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) {
mNoteBgColorSelector.setVisibility(View.GONE);
return true;
} else if (mFontSizeSelector.getVisibility() == View.VISIBLE) {
mFontSizeSelector.setVisibility(View.GONE);
return true;
}
return false;
}
public void onBackgroundColorChanged() {
findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(
View.VISIBLE);
mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId());
mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId());
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (isFinishing()) {
return true;
}
clearSettingState();
menu.clear();
if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) {
getMenuInflater().inflate(R.menu.call_note_edit, menu);
} else {
getMenuInflater().inflate(R.menu.note_edit, menu);
}
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode);
} else {
menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode);
}
if (mWorkingNote.hasClockAlert()) {
menu.findItem(R.id.menu_alert).setVisible(false);
} else {
menu.findItem(R.id.menu_delete_remind).setVisible(false);
}
return true;
// 处理返回键,保存笔记
}
// 处理选项菜单项点击事件
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_new_note:
createNewNote();
break;
case R.id.menu_delete:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.alert_title_delete));
builder.setIcon(android.R.drawable.ic_dialog_alert);
builder.setMessage(getString(R.string.alert_message_delete_note));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
deleteCurrentNote();
finish();
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
case R.id.menu_font_size:
mFontSizeSelector.setVisibility(View.VISIBLE);
findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE);
break;
case R.id.menu_list_mode:
mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ?
TextNote.MODE_CHECK_LIST : 0);
break;
case R.id.menu_share:
getWorkingText();
sendTo(this, mWorkingNote.getContent());
break;
case R.id.menu_send_to_desktop:
sendToDesktop();
break;
case R.id.menu_alert:
setReminder();
break;
case R.id.menu_delete_remind:
mWorkingNote.setAlertDate(0, false);
break;
default:
break;
}
return true;
// 处理菜单项点击,如新建笔记、删除笔记、设置提醒等
}
// 设置提醒
private void setReminder() {
DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis());
d.setOnDateTimeSetListener(new OnDateTimeSetListener() {
public void OnDateTimeSet(AlertDialog dialog, long date) {
mWorkingNote.setAlertDate(date , true);
}
});
d.show();
}
/**
* Share note to apps that support {@link Intent#ACTION_SEND} action
* and {@text/plain} type
*/
private void sendTo(Context context, String info) {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, info);
intent.setType("text/plain");
context.startActivity(intent);
}
private void createNewNote() {
// Firstly, save current editing notes
saveNote();
// For safety, start a new NoteEditActivity
finish();
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId());
startActivity(intent);
}
private void deleteCurrentNote() {
if (mWorkingNote.existInDatabase()) {
HashSet<Long> ids = new HashSet<Long>();
long id = mWorkingNote.getNoteId();
if (id != Notes.ID_ROOT_FOLDER) {
ids.add(id);
} else {
Log.d(TAG, "Wrong note id, should not happen");
}
if (!isSyncMode()) {
if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) {
Log.e(TAG, "Delete Note error");
}
} else {
if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) {
Log.e(TAG, "Move notes to trash folder error, should not happens");
}
}
}
mWorkingNote.markDeleted(true);
}
private boolean isSyncMode() {
return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0;
}
public void onClockAlertChanged(long date, boolean set) {
/**
* User could set clock to an unsaved note, so before setting the
* alert clock, we should save the note first
*/
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
if (mWorkingNote.getNoteId() > 0) {
Intent intent = new Intent(this, AlarmReceiver.class);
intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId()));
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE));
showAlertHeader();
if(!set) {
alarmManager.cancel(pendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent);
}
} else {
/**
* There is the condition that user has input nothing (the note is
* not worthy saving), we have no note id, remind the user that he
* should input something
*/
Log.e(TAG, "Clock alert setting error");
showToast(R.string.error_note_empty_for_clock);
}
}
public void onWidgetChanged() {
updateWidget();
}
public void onEditTextDelete(int index, String text) {
int childCount = mEditTextList.getChildCount();
if (childCount == 1) {
return;
}
for (int i = index + 1; i < childCount; i++) {
((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
.setIndex(i - 1);
}
mEditTextList.removeViewAt(index);
NoteEditText edit = null;
if(index == 0) {
edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById(
R.id.et_edit_text);
} else {
edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById(
R.id.et_edit_text);
}
int length = edit.length();
edit.append(text);
edit.requestFocus();
edit.setSelection(length);
}
public void onEditTextEnter(int index, String text) {
/**
* Should not happen, check for debug
*/
if(index > mEditTextList.getChildCount()) {
Log.e(TAG, "Index out of mEditTextList boundrary, should not happen");
}
View view = getListItem(text, index);
mEditTextList.addView(view, index);
NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
edit.requestFocus();
edit.setSelection(0);
for (int i = index + 1; i < mEditTextList.getChildCount(); i++) {
((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text))
.setIndex(i);
}
}
private void switchToListMode(String text) {
mEditTextList.removeAllViews();
String[] items = text.split("\n");
int index = 0;
for (String item : items) {
if(!TextUtils.isEmpty(item)) {
mEditTextList.addView(getListItem(item, index));
index++;
}
}
mEditTextList.addView(getListItem("", index));
mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus();
mNoteEditor.setVisibility(View.GONE);
mEditTextList.setVisibility(View.VISIBLE);
}
private Spannable getHighlightQueryResult(String fullText, String userQuery) {
SpannableString spannable = new SpannableString(fullText == null ? "" : fullText);
if (!TextUtils.isEmpty(userQuery)) {
mPattern = Pattern.compile(userQuery);
Matcher m = mPattern.matcher(fullText);
int start = 0;
while (m.find(start)) {
spannable.setSpan(
new BackgroundColorSpan(this.getResources().getColor(
R.color.user_query_highlight)), m.start(), m.end(),
Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
start = m.end();
}
}
return spannable;
}
private View getListItem(String item, int index) {
View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null);
final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId));
CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item));
cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} else {
edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
}
}
});
if (item.startsWith(TAG_CHECKED)) {
cb.setChecked(true);
edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
item = item.substring(TAG_CHECKED.length(), item.length()).trim();
} else if (item.startsWith(TAG_UNCHECKED)) {
cb.setChecked(false);
edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG);
item = item.substring(TAG_UNCHECKED.length(), item.length()).trim();
}
edit.setOnTextViewChangeListener(this);
edit.setIndex(index);
edit.setText(getHighlightQueryResult(item, mUserQuery));
return view;
}
public void onTextChange(int index, boolean hasText) {
if (index >= mEditTextList.getChildCount()) {
Log.e(TAG, "Wrong index, should not happen");
return;
}
if(hasText) {
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE);
} else {
mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE);
}
}
public void onCheckListModeChanged(int oldMode, int newMode) {
if (newMode == TextNote.MODE_CHECK_LIST) {
switchToListMode(mNoteEditor.getText().toString());
} else {
if (!getWorkingText()) {
mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ",
""));
}
mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery));
mEditTextList.setVisibility(View.GONE);
mNoteEditor.setVisibility(View.VISIBLE);
}
}
private boolean getWorkingText() {
boolean hasChecked = false;
if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < mEditTextList.getChildCount(); i++) {
View view = mEditTextList.getChildAt(i);
NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text);
if (!TextUtils.isEmpty(edit.getText())) {
if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) {
sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n");
hasChecked = true;
} else {
sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n");
}
}
}
mWorkingNote.setWorkingText(sb.toString());
} else {
mWorkingNote.setWorkingText(mNoteEditor.getText().toString());
}
return hasChecked;
}
private boolean saveNote() {
getWorkingText();
boolean saved = mWorkingNote.saveNote();
if (saved) {
/**
* There are two modes from List view to edit view, open one note,
* create/edit a node. Opening node requires to the original
* position in the list when back from edit view, while creating a
* new node requires to the top of the list. This code
* {@link #RESULT_OK} is used to identify the create/edit state
*/
setResult(RESULT_OK);
}
return saved;
// 显示日期时间选择对话框,设置提醒
}
// 发送到桌面
private void sendToDesktop() {
/**
* Before send message to home, we should make sure that current
* editing note is exists in databases. So, for new note, firstly
* save it
*/
if (!mWorkingNote.existInDatabase()) {
saveNote();
}
if (mWorkingNote.getNoteId() > 0) {
Intent sender = new Intent();
Intent shortcutIntent = new Intent(this, NoteEditActivity.class);
shortcutIntent.setAction(Intent.ACTION_VIEW);
shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId());
sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
sender.putExtra(Intent.EXTRA_SHORTCUT_NAME,
makeShortcutIconTitle(mWorkingNote.getContent()));
sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app));
sender.putExtra("duplicate", true);
sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT");
showToast(R.string.info_note_enter_desktop);
sendBroadcast(sender);
} else {
/**
* There is the condition that user has input nothing (the note is
* not worthy saving), we have no note id, remind the user that he
* should input something
*/
Log.e(TAG, "Send to desktop error");
showToast(R.string.error_note_empty_for_send_to_desktop);
}
}
private String makeShortcutIconTitle(String content) {
content = content.replace(TAG_CHECKED, "");
content = content.replace(TAG_UNCHECKED, "");
return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0,
SHORTCUT_ICON_TITLE_MAX_LEN) : content;
// 将笔记发送到桌面,创建快捷方式
}
private void showToast(int resId) {
showToast(resId, Toast.LENGTH_SHORT);
}
private void showToast(int resId, int duration) {
Toast.makeText(this, resId, duration).show();
// 保存笔记
private boolean saveNote() {
// 保存当前编辑的笔记
}
public void OnOpenMenu(View view) {
openOptionsMenu();
}
}
// 其他方法,如处理文本变化、列表模式切换等
}

@ -1,17 +1,6 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* MiCodeApache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0 查看。
*/
package net.micode.notes.ui;
@ -39,13 +28,15 @@ import java.util.Map;
public class NoteEditText extends EditText {
private static final String TAG = "NoteEditText";
private int mIndex;
private int mSelectionStartBeforeDelete;
private int mIndex; // 索引,用于标识文本编辑的位置
private int mSelectionStartBeforeDelete; // 删除前的选择起始位置
private static final String SCHEME_TEL = "tel:" ;
private static final String SCHEME_HTTP = "http:" ;
private static final String SCHEME_EMAIL = "mailto:" ;
// 定义不同的链接协议
private static final String SCHEME_TEL = "tel:";
private static final String SCHEME_HTTP = "http:";
private static final String SCHEME_EMAIL = "mailto:";
// 定义链接协议与动作资源的映射
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
@ -53,165 +44,71 @@ public class NoteEditText extends EditText {
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
}
/**
* 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
*/
void onEditTextDelete(int index, String text);
/**
* Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER}
* happen
*/
void onEditTextEnter(int index, String text);
/**
* Hide or show item option when text change
*/
void onTextChange(int index, boolean hasText);
}
private OnTextViewChangeListener mOnTextViewChangeListener;
// 构造函数
public NoteEditText(Context context) {
super(context, null);
mIndex = 0;
}
// 设置索引
public void setIndex(int index) {
mIndex = index;
}
// 设置文本编辑事件的监听器
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
// 其他构造函数
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
// 处理触摸事件,用于选择文本
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
int x = (int) event.getX();
int y = (int) event.getY();
x -= getTotalPaddingLeft();
y -= getTotalPaddingTop();
x += getScrollX();
y += getScrollY();
Layout layout = getLayout();
int line = layout.getLineForVertical(y);
int off = layout.getOffsetForHorizontal(line, x);
Selection.setSelection(getText(), off);
break;
}
// 处理触摸事件,更新光标位置
return super.onTouchEvent(event);
}
// 处理按键事件,特别是删除和回车键
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
if (mOnTextViewChangeListener != null) {
return false;
}
break;
case KeyEvent.KEYCODE_DEL:
mSelectionStartBeforeDelete = getSelectionStart();
break;
default:
break;
}
// 处理按下删除键的事件
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch(keyCode) {
case KeyEvent.KEYCODE_DEL:
if (mOnTextViewChangeListener != null) {
if (0 == mSelectionStartBeforeDelete && mIndex != 0) {
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
return true;
}
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
case KeyEvent.KEYCODE_ENTER:
if (mOnTextViewChangeListener != null) {
int selectionStart = getSelectionStart();
String text = getText().subSequence(selectionStart, length()).toString();
setText(getText().subSequence(0, selectionStart));
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);
} else {
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
default:
break;
}
// 处理抬起删除键和回车键的事件
return super.onKeyUp(keyCode, event);
}
// 处理焦点变化事件,用于更新文本编辑状态
@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);
}
// 创建上下文菜单,用于处理链接点击事件
@Override
protected void onCreateContextMenu(ContextMenu menu) {
if (getText() instanceof Spanned) {
int selStart = getSelectionStart();
int selEnd = getSelectionEnd();
int min = Math.min(selStart, selEnd);
int max = Math.max(selStart, selEnd);
final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class);
if (urls.length == 1) {
int defaultResId = 0;
for(String schema: sSchemaActionResMap.keySet()) {
if(urls[0].getURL().indexOf(schema) >= 0) {
defaultResId = sSchemaActionResMap.get(schema);
break;
}
}
if (defaultResId == 0) {
defaultResId = R.string.note_link_other;
}
menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener(
new OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
// goto a new intent
urls[0].onClick(NoteEditText.this);
return true;
}
});
}
}
// 创建上下文菜单,处理链接点击事件
super.onCreateContextMenu(menu);
}
}
}

@ -1,224 +1,114 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* MiCodeApache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0 查看。
*/
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.graphics.Rect;
import android.text.Layout;
import android.text.Selection;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.style.URLSpan;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.MotionEvent;
import android.widget.EditText;
import net.micode.notes.data.Contact;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.tool.DataUtils;
public class NoteItemData {
static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE,
NoteColumns.BG_COLOR_ID,
NoteColumns.CREATED_DATE,
NoteColumns.HAS_ATTACHMENT,
NoteColumns.MODIFIED_DATE,
NoteColumns.NOTES_COUNT,
NoteColumns.PARENT_ID,
NoteColumns.SNIPPET,
NoteColumns.TYPE,
NoteColumns.WIDGET_ID,
NoteColumns.WIDGET_TYPE,
};
private static final int ID_COLUMN = 0;
private static final int ALERTED_DATE_COLUMN = 1;
private static final int BG_COLOR_ID_COLUMN = 2;
private static final int CREATED_DATE_COLUMN = 3;
private static final int HAS_ATTACHMENT_COLUMN = 4;
private static final int MODIFIED_DATE_COLUMN = 5;
private static final int NOTES_COUNT_COLUMN = 6;
private static final int PARENT_ID_COLUMN = 7;
private static final int SNIPPET_COLUMN = 8;
private static final int TYPE_COLUMN = 9;
private static final int WIDGET_ID_COLUMN = 10;
private static final int WIDGET_TYPE_COLUMN = 11;
private long mId;
private long mAlertDate;
private int mBgColorId;
private long mCreatedDate;
private boolean mHasAttachment;
private long mModifiedDate;
private int mNotesCount;
private long mParentId;
private String mSnippet;
private int mType;
private int mWidgetId;
private int mWidgetType;
private String mName;
private String mPhoneNumber;
private boolean mIsLastItem;
private boolean mIsFirstItem;
private boolean mIsOnlyOneItem;
private boolean mIsOneNoteFollowingFolder;
private boolean mIsMultiNotesFollowingFolder;
public NoteItemData(Context context, Cursor cursor) {
mId = cursor.getLong(ID_COLUMN);
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN);
mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN);
mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN);
mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false;
mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN);
mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN);
mParentId = cursor.getLong(PARENT_ID_COLUMN);
mSnippet = cursor.getString(SNIPPET_COLUMN);
mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace(
NoteEditActivity.TAG_UNCHECKED, "");
mType = cursor.getInt(TYPE_COLUMN);
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN);
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
mPhoneNumber = "";
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);
if (!TextUtils.isEmpty(mPhoneNumber)) {
mName = Contact.getContact(context, mPhoneNumber);
if (mName == null) {
mName = mPhoneNumber;
}
}
}
if (mName == null) {
mName = "";
}
checkPostion(cursor);
}
private void checkPostion(Cursor cursor) {
mIsLastItem = cursor.isLast() ? true : false;
mIsFirstItem = cursor.isFirst() ? true : false;
mIsOnlyOneItem = (cursor.getCount() == 1);
mIsMultiNotesFollowingFolder = false;
mIsOneNoteFollowingFolder = false;
if (mType == Notes.TYPE_NOTE && !mIsFirstItem) {
int position = cursor.getPosition();
if (cursor.moveToPrevious()) {
if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER
|| cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) {
if (cursor.getCount() > (position + 1)) {
mIsMultiNotesFollowingFolder = true;
} else {
mIsOneNoteFollowingFolder = true;
}
}
if (!cursor.moveToNext()) {
throw new IllegalStateException("cursor move to previous but can't move back");
}
}
}
}
public boolean isOneFollowingFolder() {
return mIsOneNoteFollowingFolder;
}
import net.micode.notes.R;
public boolean isMultiFollowingFolder() {
return mIsMultiNotesFollowingFolder;
}
import java.util.HashMap;
import java.util.Map;
public boolean isLast() {
return mIsLastItem;
}
public class NoteEditText extends EditText {
private static final String TAG = "NoteEditText";
private int mIndex; // 索引,用于标识文本编辑的位置
private int mSelectionStartBeforeDelete; // 删除前的选择起始位置
public String getCallName() {
return mName;
}
public boolean isFirst() {
return mIsFirstItem;
}
// 定义不同的链接协议
private static final String SCHEME_TEL = "tel:";
private static final String SCHEME_HTTP = "http:";
private static final String SCHEME_EMAIL = "mailto:";
public boolean isSingle() {
return mIsOnlyOneItem;
// 定义链接协议与动作资源的映射
private static final Map<String, Integer> sSchemaActionResMap = new HashMap<String, Integer>();
static {
sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel);
sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web);
sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email);
}
public long getId() {
return mId;
// 回调接口,用于处理文本编辑事件
public interface OnTextViewChangeListener {
void onEditTextDelete(int index, String text);
void onEditTextEnter(int index, String text);
void onTextChange(int index, boolean hasText);
}
public long getAlertDate() {
return mAlertDate;
}
public long getCreatedDate() {
return mCreatedDate;
}
public boolean hasAttachment() {
return mHasAttachment;
}
public long getModifiedDate() {
return mModifiedDate;
}
public int getBgColorId() {
return mBgColorId;
}
private OnTextViewChangeListener mOnTextViewChangeListener;
public long getParentId() {
return mParentId;
// 构造函数
public NoteEditText(Context context) {
super(context, null);
mIndex = 0;
}
public int getNotesCount() {
return mNotesCount;
// 设置索引
public void setIndex(int index) {
mIndex = index;
}
public long getFolderId () {
return mParentId;
// 设置文本编辑事件的监听器
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
public int getType() {
return mType;
// 其他构造函数
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
public int getWidgetType() {
return mWidgetType;
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public int getWidgetId() {
return mWidgetId;
// 处理触摸事件,用于选择文本
@Override
public boolean onTouchEvent(MotionEvent event) {
// 处理触摸事件,更新光标位置
return super.onTouchEvent(event);
}
public String getSnippet() {
return mSnippet;
// 处理按键事件,特别是删除和回车键
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// 处理按下删除键的事件
return super.onKeyDown(keyCode, event);
}
public boolean hasAlert() {
return (mAlertDate > 0);
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
// 处理抬起删除键和回车键的事件
return super.onKeyUp(keyCode, event);
}
public boolean isCallRecord() {
return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber));
// 处理焦点变化事件,用于更新文本编辑状态
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
// 处理焦点变化,更新文本编辑状态
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
public static int getNoteType(Cursor cursor) {
return cursor.getInt(TYPE_COLUMN);
// 创建上下文菜单,用于处理链接点击事件
@Override
protected void onCreateContextMenu(ContextMenu menu) {
// 创建上下文菜单,处理链接点击事件
super.onCreateContextMenu(menu);
}
}
}

@ -1,17 +1,6 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* MiCodeApache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0 查看。
*/
package net.micode.notes.ui;
@ -30,19 +19,21 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
public class NotesListAdapter extends CursorAdapter {
// 类成员变量
private static final String TAG = "NotesListAdapter";
private Context mContext;
private HashMap<Integer, Boolean> mSelectedIndex;
private int mNotesCount;
private boolean mChoiceMode;
private Context mContext; // 上下文对象
private HashMap<Integer, Boolean> mSelectedIndex; // 选中状态的索引映射
private int mNotesCount; // 笔记计数
private boolean mChoiceMode; // 选择模式
// AppWidgetAttribute内部类用于存储与AppWidget相关的属性
public static class AppWidgetAttribute {
public int widgetId;
public int widgetType;
};
// 构造函数
public NotesListAdapter(Context context) {
super(context, null);
mSelectedIndex = new HashMap<Integer, Boolean>();
@ -50,34 +41,39 @@ public class NotesListAdapter extends CursorAdapter {
mNotesCount = 0;
}
// 创建新的列表项视图
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new NotesListItem(context);
}
// 绑定数据到列表项视图
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof NotesListItem) {
NoteItemData itemData = new NoteItemData(context, cursor);
((NotesListItem) view).bind(context, itemData, mChoiceMode,
isSelectedItem(cursor.getPosition()));
((NotesListItem) view).bind(context, itemData, mChoiceMode, isSelectedItem(cursor.getPosition()));
}
}
// 设置列表项的选中状态
public void setCheckedItem(final int position, final boolean checked) {
mSelectedIndex.put(position, checked);
notifyDataSetChanged();
}
// 获取选择模式状态
public boolean isInChoiceMode() {
return mChoiceMode;
}
// 设置选择模式
public void setChoiceMode(boolean mode) {
mSelectedIndex.clear();
mChoiceMode = mode;
}
// 选中或取消选中所有项
public void selectAll(boolean checked) {
Cursor cursor = getCursor();
for (int i = 0; i < getCount(); i++) {
@ -89,6 +85,7 @@ public class NotesListAdapter extends CursorAdapter {
}
}
// 获取选中的笔记ID集合
public HashSet<Long> getSelectedItemIds() {
HashSet<Long> itemSet = new HashSet<Long>();
for (Integer position : mSelectedIndex.keySet()) {
@ -101,10 +98,10 @@ public class NotesListAdapter extends CursorAdapter {
}
}
}
return itemSet;
}
// 获取选中的AppWidget属性集合
public HashSet<AppWidgetAttribute> getSelectedWidget() {
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>();
for (Integer position : mSelectedIndex.keySet()) {
@ -116,9 +113,6 @@ public class NotesListAdapter extends CursorAdapter {
widget.widgetId = item.getWidgetId();
widget.widgetType = item.getWidgetType();
itemSet.add(widget);
/**
* Don't close cursor here, only the adapter could close it
*/
} else {
Log.e(TAG, "Invalid cursor");
return null;
@ -128,6 +122,7 @@ public class NotesListAdapter extends CursorAdapter {
return itemSet;
}
// 获取选中的计数
public int getSelectedCount() {
Collection<Boolean> values = mSelectedIndex.values();
if (null == values) {
@ -143,11 +138,13 @@ public class NotesListAdapter extends CursorAdapter {
return count;
}
// 检查是否全部选中
public boolean isAllSelected() {
int checkedCount = getSelectedCount();
return (checkedCount != 0 && checkedCount == mNotesCount);
}
// 检查指定位置的项是否选中
public boolean isSelectedItem(final int position) {
if (null == mSelectedIndex.get(position)) {
return false;
@ -155,18 +152,21 @@ public class NotesListAdapter extends CursorAdapter {
return mSelectedIndex.get(position);
}
// 当内容变化时调用
@Override
protected void onContentChanged() {
super.onContentChanged();
calcNotesCount();
}
// 当游标变化时调用
@Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
calcNotesCount();
}
// 计算笔记计数
private void calcNotesCount() {
mNotesCount = 0;
for (int i = 0; i < getCount(); i++) {
@ -181,4 +181,4 @@ public class NotesListAdapter extends CursorAdapter {
}
}
}
}
}

@ -1,17 +1,6 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* MiCodeApache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0 查看。
*/
package net.micode.notes.ui;
@ -29,26 +18,29 @@ import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
public class NotesListItem extends LinearLayout {
private ImageView mAlert;
private TextView mTitle;
private TextView mTime;
private TextView mCallName;
private NoteItemData mItemData;
private CheckBox mCheckBox;
// 类成员变量,用于显示笔记列表项的各个部分
private ImageView mAlert; // 用于显示提醒图标
private TextView mTitle; // 用于显示笔记标题
private TextView mTime; // 用于显示笔记修改时间
private TextView mCallName; // 用于显示通话记录名称
private NoteItemData mItemData; // 笔记项数据
private CheckBox mCheckBox; // 用于选择模式时的复选框
// 构造函数,初始化笔记列表项视图
public NotesListItem(Context context) {
super(context);
inflate(context, R.layout.note_item, this);
mAlert = (ImageView) findViewById(R.id.iv_alert_icon);
mTitle = (TextView) findViewById(R.id.tv_title);
mTime = (TextView) findViewById(R.id.tv_time);
mCallName = (TextView) findViewById(R.id.tv_name);
mCheckBox = (CheckBox) findViewById(android.R.id.checkbox);
inflate(context, R.layout.note_item, this); // 将note_item布局文件加载到此LinearLayout中
mAlert = (ImageView) findViewById(R.id.iv_alert_icon); // 初始化提醒图标
mTitle = (TextView) findViewById(R.id.tv_title); // 初始化标题
mTime = (TextView) findViewById(R.id.tv_time); // 初始化时间
mCallName = (TextView) findViewById(R.id.tv_name); // 初始化通话记录名称
mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); // 初始化复选框
}
// 绑定笔记数据到视图
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
// 根据是否处于选择模式和笔记类型设置复选框的可见性和选中状态
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(checked);
@ -56,8 +48,10 @@ public class NotesListItem extends LinearLayout {
mCheckBox.setVisibility(View.GONE);
}
mItemData = data;
mItemData = data; // 设置笔记项数据
// 根据笔记项数据的类型和属性设置视图的显示内容
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
// 特殊处理通话记录文件夹
mCallName.setVisibility(View.GONE);
mAlert.setVisibility(View.VISIBLE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
@ -65,6 +59,7 @@ public class NotesListItem extends LinearLayout {
+ context.getString(R.string.format_folder_files_count, data.getNotesCount()));
mAlert.setImageResource(R.drawable.call_record);
} else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
// 处理通话记录下的笔记项
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName());
mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem);
@ -76,13 +71,13 @@ public class NotesListItem extends LinearLayout {
mAlert.setVisibility(View.GONE);
}
} else {
// 处理普通笔记项
mCallName.setVisibility(View.GONE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
if (data.getType() == Notes.TYPE_FOLDER) {
mTitle.setText(data.getSnippet()
+ context.getString(R.string.format_folder_files_count,
data.getNotesCount()));
data.getNotesCount()));
mAlert.setVisibility(View.GONE);
} else {
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
@ -94,12 +89,13 @@ public class NotesListItem extends LinearLayout {
}
}
}
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
setBackground(data);
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); // 设置修改时间
setBackground(data); // 设置背景
}
// 设置背景
private void setBackground(NoteItemData data) {
// 根据笔记项数据的类型和属性设置背景资源
int id = data.getBgColorId();
if (data.getType() == Notes.TYPE_NOTE) {
if (data.isSingle() || data.isOneFollowingFolder()) {
@ -116,7 +112,8 @@ public class NotesListItem extends LinearLayout {
}
}
// 获取笔记项数据
public NoteItemData getItemData() {
return mItemData;
}
}
}

@ -1,17 +1,6 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* MiCodeApache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0 查看。
*/
package net.micode.notes.ui;
@ -47,53 +36,45 @@ import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.remote.GTaskSyncService;
public class NotesPreferenceActivity extends PreferenceActivity {
// 偏好设置的文件名和键值
public static final String PREFERENCE_NAME = "notes_preferences";
public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name";
public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time";
public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear";
private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key";
private static final String AUTHORITIES_FILTER_KEY = "authorities";
private PreferenceCategory mAccountCategory;
private GTaskReceiver mReceiver;
private Account[] mOriAccounts;
private boolean mHasAddedAccount;
private PreferenceCategory mAccountCategory; // 账号设置的分类
private GTaskReceiver mReceiver; // 用于接收同步状态广播的接收器
private Account[] mOriAccounts; // 原始账号数组
private boolean mHasAddedAccount; // 是否添加了新账号
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
/* using the app icon for navigation */
// 使用应用图标作为导航
getActionBar().setDisplayHomeAsUpEnabled(true);
// 从XML文件中添加偏好设置
addPreferencesFromResource(R.xml.preferences);
mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY);
mReceiver = new GTaskReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME);
registerReceiver(mReceiver, filter);
registerReceiver(mReceiver, filter); // 注册广播接收器
mOriAccounts = null;
View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null);
getListView().addHeaderView(header, null, true);
getListView().addHeaderView(header, null, true); // 在列表视图中添加头部
}
@Override
protected void onResume() {
super.onResume();
// need to set sync account automatically if user has added a new
// account
// 如果用户添加了新账号,需要自动设置同步账号
if (mHasAddedAccount) {
Account[] accounts = getGoogleAccounts();
if (mOriAccounts != null && accounts.length > mOriAccounts.length) {
@ -113,17 +94,18 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
}
refreshUI();
refreshUI(); // 刷新用户界面
}
@Override
protected void onDestroy() {
if (mReceiver != null) {
unregisterReceiver(mReceiver);
unregisterReceiver(mReceiver); // 注销广播接收器
}
super.onDestroy();
}
// 加载账号偏好设置
private void loadAccountPreference() {
mAccountCategory.removeAll();
@ -135,11 +117,10 @@ public class NotesPreferenceActivity extends PreferenceActivity {
public boolean onPreferenceClick(Preference preference) {
if (!GTaskSyncService.isSyncing()) {
if (TextUtils.isEmpty(defaultAccount)) {
// the first time to set account
// 第一次设置账号
showSelectAccountAlertDialog();
} else {
// if the account has already been set, we need to promp
// user about the risk
// 如果账号已经设置,需要提示用户风险
showChangeAccountConfirmAlertDialog();
}
} else {
@ -154,11 +135,12 @@ public class NotesPreferenceActivity extends PreferenceActivity {
mAccountCategory.addPreference(accountPref);
}
// 加载同步按钮
private void loadSyncButton() {
Button syncButton = (Button) findViewById(R.id.preference_sync_button);
TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview);
// set button state
// 设置按钮状态
if (GTaskSyncService.isSyncing()) {
syncButton.setText(getString(R.string.preferences_button_sync_cancel));
syncButton.setOnClickListener(new View.OnClickListener() {
@ -176,7 +158,7 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this)));
// set last sync time
// 设置最后同步时间
if (GTaskSyncService.isSyncing()) {
lastSyncTimeView.setText(GTaskSyncService.getProgressString());
lastSyncTimeView.setVisibility(View.VISIBLE);
@ -193,11 +175,13 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
}
// 刷新用户界面
private void refreshUI() {
loadAccountPreference();
loadSyncButton();
}
// 显示选择账号的对话框
private void showSelectAccountAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
@ -246,7 +230,7 @@ public class NotesPreferenceActivity extends PreferenceActivity {
mHasAddedAccount = true;
Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS");
intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] {
"gmail-ls"
"gmail-ls"
});
startActivityForResult(intent, -1);
dialog.dismiss();
@ -254,6 +238,7 @@ public class NotesPreferenceActivity extends PreferenceActivity {
});
}
// 显示更改账号确认对话框
private void showChangeAccountConfirmAlertDialog() {
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this);
@ -273,8 +258,7 @@ public class NotesPreferenceActivity extends PreferenceActivity {
dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
if (which == 0) {
showSelectAccountAlertDialog();
} else if (which == 1) {
showSelectAccountAlertDialog(); } else if (which == 1) {
removeSyncAccount();
refreshUI();
}
@ -283,11 +267,13 @@ public class NotesPreferenceActivity extends PreferenceActivity {
dialogBuilder.show();
}
// 获取Google账号
private Account[] getGoogleAccounts() {
AccountManager accountManager = AccountManager.get(this);
return accountManager.getAccountsByType("com.google");
}
// 设置同步账号
private void setSyncAccount(String account) {
if (!getSyncAccountName(this).equals(account)) {
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
@ -299,10 +285,10 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
editor.commit();
// clean up last sync time
// 清除上次同步时间
setLastSyncTime(this, 0);
// clean up local gtask related info
// 清除本地gtask相关信息
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
@ -318,6 +304,7 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
}
// 移除同步账号
private void removeSyncAccount() {
SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
@ -329,7 +316,7 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}
editor.commit();
// clean up local gtask related info
// 清除本地gtask相关信息
new Thread(new Runnable() {
public void run() {
ContentValues values = new ContentValues();
@ -340,12 +327,14 @@ public class NotesPreferenceActivity extends PreferenceActivity {
}).start();
}
// 获取同步账号名称
public static String getSyncAccountName(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getString(PREFERENCE_SYNC_ACCOUNT_NAME, "");
}
// 设置最后同步时间
public static void setLastSyncTime(Context context, long time) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
@ -354,14 +343,15 @@ public class NotesPreferenceActivity extends PreferenceActivity {
editor.commit();
}
// 获取最后同步时间
public static long getLastSyncTime(Context context) {
SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME,
Context.MODE_PRIVATE);
return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0);
}
// 广播接收器,用于接收同步状态的变化
private class GTaskReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
refreshUI();
@ -370,10 +360,10 @@ public class NotesPreferenceActivity extends PreferenceActivity {
syncStatus.setText(intent
.getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG));
}
}
}
// 处理选项菜单项点击事件
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
@ -384,5 +374,4 @@ public class NotesPreferenceActivity extends PreferenceActivity {
default:
return false;
}
}
}
}

@ -1,20 +1,10 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* MiCodeApache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0 查看。
*/
package net.micode.notes.widget;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
@ -33,20 +23,24 @@ import net.micode.notes.ui.NoteEditActivity;
import net.micode.notes.ui.NotesListActivity;
public abstract class NoteWidgetProvider extends AppWidgetProvider {
// 定义查询数据库时需要的列
public static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.BG_COLOR_ID,
NoteColumns.SNIPPET
NoteColumns.ID,
NoteColumns.BG_COLOR_ID,
NoteColumns.SNIPPET
};
// 定义列索引
public static final int COLUMN_ID = 0;
public static final int COLUMN_BG_COLOR_ID = 1;
public static final int COLUMN_SNIPPET = 2;
private static final String TAG = "NoteWidgetProvider";
private static final String TAG = "NoteWidgetProvider"; // 日志标签
// 当小部件被删除时调用
@Override
public void onDeleted(Context context, int[] appWidgetIds) {
// 更新数据库将删除的小部件的WIDGET_ID设置为无效
ContentValues values = new ContentValues();
values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
for (int i = 0; i < appWidgetIds.length; i++) {
@ -57,20 +51,23 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider {
}
}
// 获取小部件的笔记信息
private Cursor getNoteWidgetInfo(Context context, int widgetId) {
return context.getContentResolver().query(Notes.CONTENT_NOTE_URI,
PROJECTION,
NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?",
NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>",
new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) },
null);
}
// 更新小部件
protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
update(context, appWidgetManager, appWidgetIds, false);
}
// 更新小部件,可以指定是否处于隐私模式
private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds,
boolean privacyMode) {
boolean privacyMode) {
for (int i = 0; i < appWidgetIds.length; i++) {
if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) {
int bgId = ResourceParser.getDefaultBgId(context);
@ -100,12 +97,12 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider {
c.close();
}
// 创建RemoteViews对象用于更新小部件视图
RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId());
rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId));
intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId);
/**
* Generate the pending intent to start host for the widget
*/
// 生成PendingIntent用于小部件点击事件
PendingIntent pendingIntent = null;
if (privacyMode) {
rv.setTextViewText(R.id.widget_text,
@ -124,9 +121,12 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider {
}
}
// 获取背景资源ID的抽象方法
protected abstract int getBgResourceId(int bgId);
// 获取布局ID的抽象方法
protected abstract int getLayoutId();
// 获取小部件类型的抽象方法
protected abstract int getWidgetType();
}
}

@ -1,17 +1,6 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* MiCodeApache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0 查看。
*/
package net.micode.notes.widget;
@ -23,25 +12,33 @@ import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser;
// 继承自NoteWidgetProvider并实现2x2网格大小的笔记小部件
public class NoteWidgetProvider_2x extends NoteWidgetProvider {
// 当小部件需要更新时调用
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// 调用父类的更新方法
super.update(context, appWidgetManager, appWidgetIds);
}
// 获取小部件的布局ID
@Override
protected int getLayoutId() {
// 返回2x2网格小部件的布局资源ID
return R.layout.widget_2x;
}
// 获取背景资源ID
@Override
protected int getBgResourceId(int bgId) {
// 根据传入的背景ID返回对应的2x2网格小部件背景资源ID
return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId);
}
// 获取小部件类型
@Override
protected int getWidgetType() {
// 返回小部件的类型这里是2x2网格
return Notes.TYPE_WIDGET_2X;
}
}
}

@ -1,17 +1,6 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* MiCodeApache License 2.0
* http://www.apache.org/licenses/LICENSE-2.0 查看。
*/
package net.micode.notes.widget;
@ -23,24 +12,33 @@ import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.ResourceParser;
// 继承自NoteWidgetProvider并实现4x4网格大小的笔记小部件
public class NoteWidgetProvider_4x extends NoteWidgetProvider {
// 当小部件需要更新时调用
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// 调用父类的更新方法
super.update(context, appWidgetManager, appWidgetIds);
}
// 获取小部件的布局ID
@Override
protected int getLayoutId() {
// 返回4x4网格小部件的布局资源ID
return R.layout.widget_4x;
}
// 获取背景资源ID
@Override
protected int getBgResourceId(int bgId) {
// 根据传入的背景ID返回对应的4x4网格小部件背景资源ID
return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId);
}
// 获取小部件类型
@Override
protected int getWidgetType() {
// 返回小部件的类型这里是4x4网格
return Notes.TYPE_WIDGET_4X;
}
}
}

@ -1,43 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?><!-- XML声明指定版本为1.0编码格式为utf-8 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
版权相关信息,说明该代码所属的开源社区及版权时间范围等
Licensed under the Apache License, Version 2.0 (the "License");
说明该代码遵循Apache License 2.0协议授权
you may not use this file except in compliance with the License.
强调使用该文件需遵循上述协议
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以在此网址获取协议的具体内容
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明在该协议下软件按“原样”分发的相关情况
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
指出不提供任何明示或暗示的保证或条件
See the License for the specific language governing permissions and
limitations under the License.
-->
提示查看协议了解权限和限制相关具体内容 -->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<!-- 定义一个线性布局LinearLayout宽度占满父容器高度也占满父容器布局方向为垂直方向同时引入了安卓的命名空间 -->
<TextView
android:id="@+id/account_dialog_title"
style="?android:attr/textAppearanceMedium"
android:singleLine="true"
android:ellipsize="end"
android:gravity="center"
android:layout_marginTop="-2.7dip"
android:layout_marginBottom="-2.7dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
android:id="@+id/account_dialog_title"
style="?android:attr/textAppearanceMedium"
android:singleLine="true"
android:ellipsize="end"
android:gravity="center"
android:layout_marginTop="-2.7dip"
android:layout_marginBottom="-2.7dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<!-- 定义一个TextView控件设置了其id为account_dialog_title应用了安卓中中等文本外观的样式文本单行显示超出部分省略号显示在结尾文本居中对齐设置了上下外边距宽度占满父容器高度根据内容自适应wrap_content -->
<TextView
android:id="@+id/account_dialog_subtitle"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dip"
android:layout_marginBottom="1dip"
android:gravity="center"/>
android:id="@+id/account_dialog_subtitle"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dip"
android:layout_marginBottom="1dip"
android:gravity="center"/>
<!-- 定义另一个TextView控件设置了id为account_dialog_subtitle宽度占满父容器高度根据内容自适应设置了上下外边距文本居中对齐 -->
</LinearLayout>

@ -1,32 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?><!-- XML声明表明该XML文件遵循的版本是1.0使用的编码格式为utf-8 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
这里是版权相关的说明信息,指出代码所属的开源社区以及对应的版权时间范围
Licensed under the Apache License, Version 2.0 (the "License");
说明此代码遵循Apache License 2.0这个开源协议进行授权许可
you may not use this file except in compliance with the License.
强调若要使用该文件,必须遵循上述提到的开源协议
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过这个网址去获取Apache License 2.0协议的具体内容
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“现状”提供的相关情况
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
指出不提供任何种类的明示或暗示的保证以及相关条件
See the License for the specific language governing permissions and
limitations under the License.
-->
提示查看该协议来了解具体关于权限以及限制方面的内容 -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="50dip"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/preferences_add_account" />
android:minHeight="50dip"
android:gravity="center_vertical"
android:orientation="vertical">
<!-- 引入安卓的命名空间,用于后续使用安卓系统定义的各种布局属性等 -->
<!-- 线性布局的宽度设置为与父容器宽度匹配,也就是占满父容器宽度 -->
<!-- 线性布局的高度根据其内部子元素的布局情况自适应,也就是包裹内容的高度 -->
<!-- 定义线性布局的最小高度为50dip确保其不会小于这个高度值 -->
<!-- 设置线性布局内部元素在垂直方向上居中对齐 -->
<!-- 定义线性布局内子元素的排列方向为垂直方向 -->
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/preferences_add_account" />
<!-- TextView控件的宽度根据其文本内容自适应也就是包裹内容的宽度 -->
<!-- TextView控件的高度同样根据其文本内容自适应包裹内容的高度 -->
<!-- 设置该TextView在其所在的父容器这里是线性布局中居中对齐 -->
<!-- 应用安卓系统中定义的中等文本外观样式来显示文本 -->
<!-- 设置TextView显示的文本内容这里引用了名为"preferences_add_account"的字符串资源通常在strings.xml文件中定义 -->
</LinearLayout>

@ -1,56 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式为 utf-8这是 XML 文件开头必备的标识信息 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
此处是版权相关的声明信息,指出这段代码所属的开源社区为 The MiCode Open Source Community版权时间范围是 2010 - 2011 年
Licensed under the Apache License, Version 2.0 (the "License");
说明该代码遵循 Apache License 2.0 这个开源协议来进行授权许可,意味着使用该代码需要遵循此协议的相关规定
you may not use this file except in compliance with the License.
强调若要使用此 XML 文件所定义的相关内容,必须严格按照上述提到的 Apache License 2.0 协议来操作
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过访问这个网址http://www.apache.org/licenses/LICENSE-2.0)去获取 Apache License 2.0 协议的完整详细内容
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以其当前的“原样”进行分发的,即不提供额外的修改或保证等情况
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
进一步强调该代码不附带任何种类的明示或暗示的保证以及相关条件,使用者需自行承担使用风险
See the License for the specific language governing permissions and
limitations under the License.
-->
提示查看该协议内容,以了解具体关于权限授予以及限制方面的详细信息 -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:orientation="horizontal"
android:layout_gravity="center_horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
<NumberPicker
android:id="@+id/date"
android:layout_width="120dip"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
/>
android:id="@+id/date"
android:layout_width="120dip"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
/>
<NumberPicker
android:id="@+id/hour"
android:layout_width="50dip"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
android:id="@+id/hour"
android:layout_width="50dip"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
<NumberPicker
android:id="@+id/minute"
android:layout_width="50dip"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
android:id="@+id/minute"
android:layout_width="50dip"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
<NumberPicker
android:id="@+id/amPm"
android:layout_width="50dip"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
</LinearLayout>
android:id="@+id/amPm"
android:layout_width="50dip"
android:layout_height="wrap_content"
android:layout_marginLeft="5dip"
android:focusable="true"
android:focusableInTouchMode="true"
/>
</LinearLayout>
<!-- 设置此 NumberPicker 控件的宽度为 50 设备独立像素dip以确定其在水平方向上的尺寸 -->
<!-- 设置该控件距离其左边相邻元素在这里就是前面定义的“date” NumberPicker 控件)的间距为 5 设备独立像素dip用于控制控件之间的水平间隔距离 -->
<!-- 在触摸模式下(例如在触屏设备上操作时)也允许该控件获取焦点,方便用户通过触摸操作来与该控件进行交互,比如点击后进行数值调整等 -->
<!-- 为这个 NumberPicker 控件定义一个唯一的标识符id在安卓开发中通过这个 id 可以在代码里方便地对该控件进行引用、操作和事件处理等 -->
<!-- 设置 NumberPicker 控件的高度根据其内部内容自适应,也就是其高度会根据该控件显示的数值等内容自动调整,刚好包裹住这些内容 -->
<!-- 设置 NumberPicker 控件的宽度为 120 设备独立像素dip这确定了该控件在屏幕上水平方向所占据的空间大小 -->
<!-- 引入安卓的命名空间xmlns通过这个命名空间后续可以使用安卓系统预定义的各种布局属性和控件等相关元素这是在安卓 XML 布局文件中定义布局的基础 -->
<!-- 设置线性布局LinearLayout内子元素的排列方向为水平方向意味着子元素会从左到右依次水平排列 -->
<!-- 使该线性布局在其父容器中水平方向上居中对齐,这样整个布局会在水平维度上处于父容器的中间位置 -->
<!-- 线性布局的宽度设置为根据其内部包含的子元素大小自适应,也就是刚好能够包裹住所有子元素所需要的宽度 -->
<!-- 允许该控件获取焦点,意味着在用户操作时(例如通过键盘导航等方式),可以将操作焦点定位到这个控件上,使其能够接收用户输入等操作 -->
<!-- 允许该控件获取焦点,意味着在用户操作时(例如通过键盘导航等方式),可以将操作焦点定位到这个控件上,使其能够接收用户输入等操作 -->
<!-- 线性布局的高度同样根据其内部包含的子元素大小自适应,即包裹住子元素后所需要的高度 -->

@ -1,23 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明用于指定该XML文件所遵循的版本是1.0并且编码格式为utf-8这是XML文件开头必备的基础信息让解析器知晓如何正确解析文件内容 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
这部分是版权声明相关的注释内容说明该代码归属于The MiCode Open Source Community这个开源社区其版权时间范围涵盖了2010年至2011年期间
Licensed under the Apache License, Version 2.0 (the "License");
表明此代码是按照Apache License 2.0这个开源协议来进行授权许可的,意味着任何使用该代码的行为都需要遵循此协议所规定的各项条款
you may not use this file except in compliance with the License.
着重强调了只有在符合上述Apache License 2.0协议要求的情况下才可以使用当前这个XML文件所定义的相关内容否则属于违规使用
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
提供了获取Apache License 2.0协议具体内容的途径即可以通过访问这个网址http://www.apache.org/licenses/LICENSE-2.0)去查看完整的协议文本
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议来分发软件时,软件是以其现有的“原样”状态进行分发的,不会额外提供一些特定的保证或者对软件进行修改等情况
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
进一步明确指出该代码不会附带任何类型的、无论是明示还是暗示的保证以及相关条件,使用该代码的用户需要自行承担相应的使用风险
See the License for the specific language governing permissions and
limitations under the License.
-->
提示用户如果想要了解关于该协议所规定的权限管理以及各种限制方面的详细内容,需要去查看对应的协议文本 -->
<EditText
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/et_foler_name"
android:layout_width="fill_parent"
android:hint="@string/hint_foler_name"
android:layout_height="fill_parent" />
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/et_foler_name"
android:layout_width="fill_parent"
android:hint="@string/hint_foler_name"
android:layout_height="fill_parent"
/>
<!-- 设定了该EditText控件的高度属性为fill_parent意味着它在垂直方向上也会填满所在父容器的全部高度空间这样该控件在整个布局中的尺寸就会尽可能地大占据父容器的绝大部分空间 -->
<!-- 用于设置该EditText控件的提示文本内容这里通过引用名为hint_foler_name的字符串资源一般在项目的strings.xml文件中定义具体的字符串值来显示提示信息当该控件中没有用户输入的文本时就会显示这个提示文本以此来提示用户应该输入什么样的内容从名称来看大概率是提示用户输入文件夹名称相关的内容 -->
<!-- 设置了该EditText控件的宽度属性将其设置为fill_parent表示这个控件在水平方向上会填满它所在的父容器的全部宽度空间使其能够在布局中占据足够的横向空间 -->
<!-- 为这个EditText控件定义了一个唯一的标识符id在安卓开发过程中其他代码部分比如在Java或者Kotlin代码中可以通过这个id来准确地引用到该控件进而对其进行各种操作例如获取用户输入的文本、设置控件的显示状态等这里将其命名为et_foler_name从命名推测可能是用于和文件夹名称相关的输入操作 -->
<!-- 引入了安卓的命名空间xmlns这是在安卓开发中XML布局文件里必不可少的部分通过这个命名空间后续就能够使用安卓系统所定义的众多属性来对该EditText控件进行各种配置 -->

@ -1,29 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,所采用的编码格式为 utf-8这是 XML 文件开头的标准标识,用于告知解析器如何正确解析该文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:minHeight="50dip" >
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<!-- 设置线性布局LinearLayout的宽度与父容器宽度相匹配也就是占满父容器的整个宽度 -->
<!-- 设置线性布局的高度与父容器高度相匹配,即占满父容器的整个高度 -->
<!-- 定义线性布局的最小高度为 50 设备独立像素dip确保该布局在任何情况下高度都不会低于这个值可用于保证一定的显示效果 -->
<!-- 引入安卓的命名空间,后续通过此命名空间来使用安卓系统定义的各种布局属性及控件相关属性,是安卓 XML 布局文件必备的部分 -->
android:minHeight="50dip"
<TextView
android:id="@+id/tv_folder_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textAppearance="@style/TextAppearancePrimaryItem" />
</LinearLayout>
android:id="@+id/tv_folder_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textAppearance="@style/TextAppearancePrimaryItem"
/>
</LinearLayout>
<!-- 为该 TextView 控件定义一个唯一标识符id方便在代码中对其进行引用、操作从命名推测可能是用于显示文件夹名称相关内容 -->
<!-- 设置 TextView 控件的宽度与父容器(这里是外层的 LinearLayout宽度相匹配使其能占满水平方向的空间 -->
<!-- 设置 TextView 控件的高度与父容器高度相匹配,占满垂直方向的空间 -->
<!-- 设置文本在该 TextView 控件内的对齐方式为居中对齐,使文本在水平和垂直方向都处于中间位置 -->
<!-- 应用名为 TextAppearancePrimaryItem 的样式来设置文本的外观,该样式通常在样式资源文件中定义,可控制文本的字体、字号、颜色等外观属性 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
此处为版权相关声明,说明代码所属的开源社区是 The MiCode Open Source Community版权时间范围是 2010 - 2011 年。
Licensed under the Apache License, Version 2.0 (the "License");
表示该代码遵循 Apache License 2.0 开源协议进行授权许可,意味着使用该代码需要遵循此协议规定。
you may not use this file except in compliance with the License.
强调若要使用这个文件,必须按照上述 Apache License 2.0 协议来操作。
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过访问此网址http://www.apache.org/licenses/LICENSE-2.0)获取 Apache License 2.0 协议的详细内容。
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”进行分发,不提供额外保证等情况。
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
进一步指出该代码不附带任何明示或暗示的保证及相关条件,使用者需自行承担使用风险。
See the License for the specific language governing permissions and
limitations under the License.
提示查看协议以了解关于权限及限制方面的详细内容。 -->

@ -16,395 +16,704 @@
-->
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/list_background"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
android:background="@drawable/list_background"
xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:id="@+id/note_title"
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content">
android:layout_height="fill_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/note_title"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_modified_date"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="left|center_vertical"
android:layout_marginRight="8dip"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
android:id="@+id/tv_modified_date"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="left|center_vertical"
android:layout_marginRight="8dip"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
<ImageView
android:id="@+id/iv_alert_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="@drawable/title_alert" />
android:id="@+id/iv_alert_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="@drawable/title_alert" />
<TextView
android:id="@+id/tv_alert_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="2dip"
android:layout_marginRight="8dip"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
android:id="@+id/tv_alert_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="2dip"
android:layout_marginRight="8dip"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
<ImageButton
android:id="@+id/menu_more"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:onClick="OnOpenMenu"
android:background="@drawable/ic_menu_more_dark" />
android:id="@+id/menu_more"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:onClick="OnOpenMenu"
android:background="@drawable/ic_menu_more_dark" />
<ImageView
android:id="@+id/btn_set_bg_color"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_gravity="center"
android:background="@drawable/bg_btn_set_color" />
android:id="@+id/btn_set_bg_color"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_gravity="center"
android:background="@drawable/bg_btn_set_color" />
</LinearLayout>
<LinearLayout
android:id="@+id/sv_note_edit"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
android:id="@+id/sv_note_edit"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<ImageView
android:layout_width="fill_parent"
android:layout_height="7dip"
android:background="@drawable/bg_color_btn_mask" />
android:layout_width="fill_parent"
android:layout_height="7dip"
android:background="@drawable/bg_color_btn_mask" />
<ScrollView
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:scrollbars="none"
android:overScrollMode="never"
android:layout_gravity="left|top"
android:fadingEdgeLength="0dip">
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:scrollbars="none"
android:overScrollMode="never"
android:layout_gravity="left|top"
android:fadingEdgeLength="0dip">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<net.micode.notes.ui.NoteEditText
android:id="@+id/note_edit_view"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="left|top"
android:background="@null"
android:autoLink="all"
android:linksClickable="false"
android:minLines="12"
android:textAppearance="@style/TextAppearancePrimaryItem"
android:lineSpacingMultiplier="1.2" />
android:id="@+id/note_edit_view"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="left|top"
android:background="@null"
android:autoLink="all"
android:linksClickable="false"
android:minLines="12"
android:textAppearance="@style/TextAppearancePrimaryItem"
android:lineSpacingMultiplier="1.2" />
<LinearLayout
android:id="@+id/note_edit_list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginLeft="-10dip"
android:visibility="gone" />
android:id="@+id/note_edit_list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginLeft="-10dip"
android:visibility="gone" />
</LinearLayout>
</ScrollView>
<ImageView
android:layout_width="fill_parent"
android:layout_height="7dip"
android:background="@drawable/bg_color_btn_mask" />
android:layout_width="fill_parent"
android:layout_height="7dip"
android:background="@drawable/bg_color_btn_mask" />
</LinearLayout>
</LinearLayout>
<ImageView
android:layout_height="43dip"
android:layout_width="wrap_content"
android:background="@drawable/bg_color_btn_mask"
android:layout_gravity="top|right" />
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"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/note_edit_color_selector_panel"
android:layout_marginTop="30dip"
android:layout_marginRight="8dip"
android:layout_gravity="top|right"
android:visibility="gone">
android:id="@+id/note_bg_color_selector"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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">
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_bg_yellow"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:id="@+id/iv_bg_yellow"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/iv_bg_yellow_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" />
android:id="@+id/iv_bg_yellow_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">
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_bg_blue"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:id="@+id/iv_bg_blue"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/iv_bg_blue_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="3dip"
android:src="@drawable/selected" />
android:id="@+id/iv_bg_blue_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="3dip"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_bg_white"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:id="@+id/iv_bg_white"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/iv_bg_white_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="2dip"
android:src="@drawable/selected" />
android:id="@+id/iv_bg_white_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="2dip"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_bg_green"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:id="@+id/iv_bg_green"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/iv_bg_green_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" />
android:id="@+id/iv_bg_green_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>
<FrameLayout
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
android:layout_width="0dip"
android:layout_height="match_parent"
android:layout_weight="1">
<ImageView
android:id="@+id/iv_bg_red"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:id="@+id/iv_bg_red"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/iv_bg_red_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" />
android:id="@+id/iv_bg_red_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"
android:layout_height="wrap_content"
android:background="@drawable/font_size_selector_bg"
android:layout_gravity="bottom"
android:visibility="gone">
<FrameLayout
android:id="@+id/ll_font_small"
android:layout_width="0dip"
android:id="@+id/font_size_selector"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1">
android:background="@drawable/font_size_selector_bg"
android:layout_gravity="bottom"
android:visibility="gone">
<LinearLayout
android:layout_width="wrap_content"
<FrameLayout
android:id="@+id/ll_font_small"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
android:layout_weight="1">
<ImageView
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font_small"
android:layout_marginBottom="5dip" />
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font_small"
android:layout_marginBottom="5dip" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_font_small"
android:textAppearance="@style/TextAppearanceUnderMenuIcon" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_font_small"
android:textAppearance="@style/TextAppearanceUnderMenuIcon" />
</LinearLayout>
<ImageView
android:id="@+id/iv_small_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginRight="6dip"
android:layout_marginBottom="-7dip"
android:focusable="false"
android:visibility="gone"
android:src="@drawable/selected" />
android:id="@+id/iv_small_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:layout_marginRight="6dip"
android:layout_marginBottom="-7dip"
android:focusable="false"
android:visibility="gone"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:id="@+id/ll_font_normal"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1">
<LinearLayout
android:layout_width="wrap_content"
android:id="@+id/ll_font_normal"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
android:layout_weight="1">
<ImageView
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font_normal"
android:layout_marginBottom="5dip" />
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font_normal"
android:layout_marginBottom="5dip" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_font_normal"
android:textAppearance="@style/TextAppearanceUnderMenuIcon" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_font_normal"
android:textAppearance="@style/TextAppearanceUnderMenuIcon" />
</LinearLayout>
<ImageView
android:id="@+id/iv_medium_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="6dip"
android:layout_marginBottom="-7dip"
android:src="@drawable/selected" />
android:id="@+id/iv_medium_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="6dip"
android:layout_marginBottom="-7dip"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:id="@+id/ll_font_large"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1">
<LinearLayout
android:layout_width="wrap_content"
android:id="@+id/ll_font_large"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
android:layout_weight="1">
<ImageView
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font_large"
android:layout_marginBottom="5dip" />
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font_large"
android:layout_marginBottom="5dip" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_font_large"
android:textAppearance="@style/TextAppearanceUnderMenuIcon" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_font_large"
android:textAppearance="@style/TextAppearanceUnderMenuIcon" />
</LinearLayout>
<ImageView
android:id="@+id/iv_large_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="6dip"
android:layout_marginBottom="-7dip"
android:src="@drawable/selected" />
android:id="@+id/iv_large_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="6dip"
android:layout_marginBottom="-7dip"
android:src="@drawable/selected" />
</FrameLayout>
<FrameLayout
android:id="@+id/ll_font_super"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1">
<LinearLayout
android:layout_width="wrap_content"
android:id="@+id/ll_font_super"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
android:layout_weight="1">
<ImageView
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font_super"
android:layout_marginBottom="5dip" />
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/font_super"
android:layout_marginBottom="5dip" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_font_super"
android:textAppearance="@style/TextAppearanceUnderMenuIcon" />
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/menu_font_super"
android:textAppearance="@style/TextAppearanceUnderMenuIcon" />
</LinearLayout>
<ImageView
android:id="@+id/iv_super_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="6dip"
android:layout_marginBottom="-7dip"
android:src="@drawable/selected" />
android:id="@+id/iv_super_select"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|right"
android:focusable="false"
android:visibility="gone"
android:layout_marginRight="6dip"
android:layout_marginBottom="-7dip"
android:src="@drawable/selected" />
</FrameLayout>
</LinearLayout>
</FrameLayout>
<!-- XML声明表明该XML文件遵循的版本是1.0使用的编码格式是utf-8这是XML文件开头的标准标识用于告知解析器如何正确解析该文件 -->
<!-- 引入安卓的命名空间,后续通过这个命名空间就能使用安卓系统中定义的各种布局属性以及控件相关的属性了 -->
<!-- 设置FrameLayout帧布局的宽度占满父容器意味着它在水平方向上会填满所在父容器的全部可用空间 -->
<!-- 设置FrameLayout的高度占满父容器也就是在垂直方向上会填满所在父容器的全部可用空间 -->
<!-- 设置FrameLayout的背景这里引用了名为"list_background"的可绘制资源(通常可以是图片、颜色等能作为背景的资源)来作为该布局的背景 -->
<!-- 设置LinearLayout线性布局的宽度占满父容器此处父容器为外层的FrameLayout使其在水平方向上填满整个父布局空间 -->
<!-- 设置LinearLayout的高度占满父容器让它在垂直方向上也填满整个父布局空间 -->
<!-- 设置该线性布局内子元素的排列方向为垂直方向,意味着子元素会按照从上到下的顺序依次排列 -->
<!-- 为这个内层的LinearLayout定义一个唯一标识符id方便在后续的代码中比如Java或Kotlin代码对该布局进行引用、操作等从命名来看可能与笔记的标题部分相关 -->
<!-- 设置这个LinearLayout的宽度占满父容器这里的父容器是外层的LinearLayout使其在水平方向上占据全部空间 -->
<!-- 设置该LinearLayout的高度根据其内部子元素的大小自适应也就是刚好能包裹住内部子元素所需的高度 -->
<!-- 为这个TextView控件定义一个唯一标识符id从名字推测可能是用于显示笔记的修改日期相关信息 -->
<!-- 初始设置该TextView的宽度为0设备独立像素dip后续会结合layout_weight属性来按比例分配水平方向的空间 -->
<!-- 设置该TextView的高度根据其要显示的文本内容自适应即刚好能包裹住文本的高度 -->
<!-- 设置权重为1在水平方向上按比例分配剩余空间通常意味着它会占据相对较多的水平空间与同层级其他设置了权重的控件一起分配父容器的宽度 -->
<!-- 设置文本在该TextView控件内的对齐方式水平方向居左对齐、垂直方向居中对齐 -->
<!-- 设置该TextView控件距离其右边相邻元素的间距为8设备独立像素dip -->
<!-- 应用名为TextAppearanceSecondaryItem的样式来设置文本的外观像字体、字号、颜色等属性通常在对应的样式资源文件中定义好了 -->
<!-- 为这个ImageView控件定义一个唯一标识符id从名字推测可能是用于显示与提醒相关的图标 -->
<!-- 设置该ImageView的宽度根据其内部要显示的图片内容自适应也就是刚好能包裹住图片的宽度 -->
<!-- 设置该ImageView的高度根据其内部要显示的图片内容自适应刚好能包裹住图片的高度 -->
<!-- 设置图片在该ImageView控件内的对齐方式为垂直居中对齐 -->
<!-- 设置该ImageView的背景引用名为title_alert的可绘制资源通常是图片资源作为背景 -->
<!-- 为这个TextView控件定义一个唯一标识符id从名字推测可能是用于显示提醒相关的日期信息 -->
<!-- 设置该TextView的宽度根据其要显示的文本内容自适应能包裹住文本的宽度 -->
<!-- 设置该TextView的高度根据其要显示的文本内容自适应能包裹住文本的高度 -->
<!-- 设置文本在该TextView控件内的对齐方式为垂直居中对齐 -->
<!-- 设置该TextView控件距离其左边相邻元素的间距为2设备独立像素dip -->
<!-- 设置该TextView控件距离其右边相邻元素的间距为8设备独立像素dip -->
<!-- 应用名为TextAppearanceSecondaryItem的样式来设置文本的外观 -->
<!-- 为这个ImageButton控件定义一个唯一标识符id从名字推测可能是用于点击后打开更多菜单之类的操作 -->
<!-- 设置该ImageButton的宽度根据其内部要显示的图片内容自适应能包裹住图片的宽度 -->
<!-- 设置该ImageButton的高度根据其内部要显示的图片内容自适应能包裹住图片的高度 -->
<!-- 设置图片在该ImageButton控件内的对齐方式为居中对齐 -->
<!-- 设置点击该ImageButton时触发的方法名为OnOpenMenu需要在对应的代码逻辑中实现这个方法来处理点击事件 -->
<!-- 设置该ImageButton的背景引用名为ic_menu_more_dark的可绘制资源通常是图片资源作为背景 -->
<!-- 为这个ImageView控件定义一个唯一标识符id从名字推测可能与设置背景颜色相关的操作有关 -->
<!-- 设置该ImageView的宽度根据其内部要显示的图片内容自适应能包裹住图片的宽度 -->
<!-- 设置该ImageView的高度根据其内部要显示的图片内容自适应能包裹住图片的高度 -->
<!-- 设置该ImageView的内边距为10设备独立像素dp也就是在图片的四周留出一定的空白空间 -->
<!-- 设置图片在该ImageView控件内的对齐方式为居中对齐 -->
<!-- 设置该ImageView的背景引用名为bg_btn_set_color的可绘制资源通常是图片资源作为背景 -->
<!-- 结束前面开始的 FrameLayout 布局定义,与开头的 <FrameLayout> 标签对应,构成一个完整的帧布局结构 -->
<!-- 为该 LinearLayout 定义一个唯一标识符id方便在代码中对其进行引用、操作从命名推测可能与笔记编辑相关的布局 -->
<!-- 设置该 LinearLayout 的宽度占满父容器(这里的父容器应该是外层与之对应的布局元素),使其在水平方向上填满整个父容器空间 -->
<!-- 设置该 LinearLayout 的高度占满父容器,在垂直方向上填满整个父容器空间 -->
<!-- 设置线性布局内子元素的排列方向为垂直方向,意味着子元素将从上到下依次排列 -->
<!-- 设置该 ImageView 的宽度占满父容器(这里的父容器是当前的 LinearLayout水平方向填满整个空间 -->
<!-- 设置该 ImageView 的高度为 7 设备独立像素dip -->
<!-- 设置 ImageView 的背景,引用名为 bg_color_btn_mask 的可绘制资源(通常是图片资源)作为背景 -->
<!-- 设置 ScrollView滚动视图的宽度占满父容器这里的父容器是当前的 LinearLayout使其在水平方向上填满整个空间 -->
<!-- 初始设置高度为 0 设备独立像素dip后续会结合 layout_weight 属性来分配垂直方向的空间 -->
<!-- 设置权重为 1在垂直方向上按比例分配剩余空间用于占据较大的可滚动区域 -->
<!-- 设置不显示滚动条 -->
<!-- 设置禁止过度滚动效果 -->
<!-- 设置在父容器中的对齐方式为左上角对齐 -->
<!-- 设置渐变边缘的长度为 0 设备独立像素dip即不显示渐变边缘效果 -->
<!-- 设置该 LinearLayout 的宽度占满父容器(这里的父容器是外层的 ScrollView在水平方向上填满整个空间 -->
<!-- 设置该 LinearLayout 的高度占满父容器,在垂直方向上填满整个空间 -->
<!-- 设置线性布局内子元素的排列方向为垂直方向 -->
<!-- 为该 NoteEditText 控件定义一个唯一标识符id从命名推测是用于编辑笔记内容的主要输入框 -->
<!-- 设置宽度占满父容器(这里的父容器是当前的 LinearLayout水平方向填满空间 -->
<!-- 设置高度根据文本内容自适应,包裹住文本内容 -->
<!-- 设置文本在该控件内的对齐方式为左上角对齐 -->
<!-- 设置背景为空,即不显示默认的背景样式 -->
<!-- 设置自动识别链接,会识别所有类型的链接(如网址、邮箱等) -->
<!-- 设置虽然识别了链接,但链接不可点击 -->
<!-- 设置最小行数为 12 行,确保输入框有一定的初始高度 -->
<!-- 应用名为 TextAppearancePrimaryItem 的样式来设置文本外观,例如字体、字号、颜色等属性 -->
<!-- 设置行间距倍数为 1.2,增加文本行之间的间距 -->
<!-- 为该 LinearLayout 定义一个唯一标识符id从命名推测可能与笔记编辑相关的列表部分当前设置为不可见 -->
<!-- 设置宽度占满父容器(这里的父容器是当前的 LinearLayout水平方向填满空间 -->
<!-- 设置高度根据其内部子元素自适应,包裹住子元素 -->
<!-- 设置线性布局内子元素的排列方向为垂直方向 -->
<!-- 设置该布局初始状态为不可见 -->
<!-- 结束前面开始的LinearLayout布局定义与对应的<LinearLayout>开头标签相匹配,完成该线性布局的结构 -->
<!-- 设置ImageView的高度为7设备独立像素dip使其具有固定的垂直高度 -->
<!-- 设置ImageView的背景引用名为bg_color_btn_mask的可绘制资源通常是图片、颜色等用于作为背景的资源来作为该ImageView的背景 -->
<!-- 设置ImageView的宽度占满父容器也就是在水平方向上填满其所在父布局的全部可用空间 -->
<!-- 设置ImageView的高度为43设备独立像素dip确定其垂直方向上的尺寸大小 -->
<!-- 设置ImageView的宽度根据其内部要显示的内容通常是图片自适应即刚好能包裹住图片的宽度 -->
<!-- 同样是设置该ImageView的背景使用名为bg_color_btn_mask的可绘制资源作为背景 -->
<!-- 设置该ImageView在其父容器中的对齐方式为右上角对齐使其显示在父容器的右上角位置 -->
<!-- 为该LinearLayout定义一个唯一标识符id方便在代码中对其进行引用、操作从命名来看可能是用于选择笔记背景颜色的相关布局 -->
<!-- 设置该LinearLayout的宽度根据其内部子元素自适应也就是刚好能包裹住内部子元素所需的宽度 -->
<!-- 设置该LinearLayout的高度根据其内部子元素自适应包裹住内部子元素的高度 -->
<!-- 设置该LinearLayout的背景引用名为note_edit_color_selector_panel的可绘制资源作为背景 -->
<!-- 设置该LinearLayout距离其父容器顶部的间距为30设备独立像素dip用于调整其垂直方向上的位置 -->
<!-- 设置该LinearLayout距离其父容器右侧的间距为8设备独立像素dip用于调整其水平方向上的位置 -->
<!-- 设置该LinearLayout在其父容器中的对齐方式为右上角对齐使其显示在父容器的右上角位置 -->
<!-- 设置该LinearLayout初始状态为不可见可能在满足某些条件后才会显示出来 -->
<!-- 初始设置FrameLayout的宽度为0设备独立像素dip后续会结合layout_weight属性来分配水平方向上的空间 -->
<!-- 设置FrameLayout的高度与父容器高度匹配使其在垂直方向上填满整个父容器空间 -->
<!-- 设置权重为1在水平方向上按比例分配剩余空间用于多个同层级FrameLayout按比例划分宽度 -->
<!-- 为该ImageView控件定义一个唯一标识符id从命名推测是用于显示黄色背景相关的图片或样式 -->
<!-- 设置该ImageView的宽度与父容器这里是外层的FrameLayout宽度匹配在水平方向上填满整个父容器空间 -->
<!-- 设置该ImageView的高度与父容器高度匹配在垂直方向上填满整个父容器空间 -->
<!-- 为该ImageView控件定义一个唯一标识符id推测是用于表示黄色背景是否被选中的相关显示元素初始设置为不可见 -->
<!-- 设置该ImageView的宽度根据其内部要显示的内容自适应包裹住内容的宽度 -->
<!-- 设置该ImageView的高度根据其内部要显示的内容自适应包裹住内容的高度 -->
<!-- 设置该ImageView在其父容器这里是外层的FrameLayout中的对齐方式为右下角对齐 -->
<!-- 设置该ImageView距离其父容器右侧的间距为5设备独立像素dip -->
<!-- 设置该ImageView不可获取焦点意味着用户不能通过焦点操作来与之交互 -->
<!-- 设置该ImageView初始状态为不可见可能在相关逻辑控制下变为可见以表示选中状态等 -->
<!-- 设置该ImageView要显示的图片资源引用名为selected的可绘制资源通常是图片资源 -->
<!-- 结束前面对应的 FrameLayout 标签所开始的布局定义,与开头的 <FrameLayout> 相呼应,完成一个 FrameLayout 布局结构的声明 -->
<!-- 初始设置该 FrameLayout 的宽度为 0 设备独立像素dip后续会结合 layout_weight 属性在水平方向上按比例分配空间 -->
<!-- 设置该 FrameLayout 的高度与父容器高度相匹配,使其在垂直方向上填满整个父容器空间 -->
<!-- 设置权重为 1意味着在水平方向上它会按照比例去分配剩余的空间通常用于和同层级的其他设置了权重的布局共同划分父容器的宽度 -->
<!-- 为这个 ImageView 控件定义一个唯一标识符id从命名来看可能是用于显示蓝色背景相关的图片资源用于界面展示相关用途 -->
<!-- 设置该 ImageView 的宽度与父容器(这里就是外层的 FrameLayout宽度相匹配使其在水平方向上填满整个父容器空间 -->
<!-- 设置该 ImageView 的高度与父容器高度相匹配,使其在垂直方向上填满整个父容器空间 -->
<!-- 为这个 ImageView 控件定义一个唯一标识符id推测是用于表示蓝色背景是否被选中的一个指示性图片比如选中后显示特定样式初始设置为不可见 -->
<!-- 设置该 ImageView 的宽度根据其内部要显示的图片内容自适应,也就是刚好能包裹住图片的宽度 -->
<!-- 设置该 ImageView 的高度根据其内部要显示的图片内容自适应,刚好能包裹住图片的高度 -->
<!-- 设置该 ImageView 在其父容器(外层的 FrameLayout内的对齐方式为右下角对齐确定其显示位置 -->
<!-- 设置该 ImageView 不可获取焦点,意味着用户不能通过焦点操作(比如通过方向键、触摸焦点等方式)与它进行交互 -->
<!-- 设置该 ImageView 初始状态为不可见,可能在后续满足某些条件(比如用户选择了蓝色背景选项等)时,通过代码将其设置为可见来表示相应的选中状态 -->
<!-- 设置该 ImageView 距离其父容器(外层的 FrameLayout右侧的间距为 3 设备独立像素dip用于微调其在水平方向上的位置 -->
<!-- 设置该 ImageView 要显示的图片资源引用名为“selected”的可绘制资源通常是图片资源这个图片应该是用于体现选中状态的特定样式 -->
<!-- 初始设置该FrameLayout的宽度为0设备独立像素dip后续会结合layout_weight属性来按比例分配水平方向上的空间通常用于多个同层级FrameLayout按权重划分父容器宽度 -->
<!-- 设置该FrameLayout的高度与父容器高度相匹配使其在垂直方向上填满整个父容器空间 -->
<!-- 设置权重为1意味着在水平方向上它会按照比例去分配剩余空间和同层级其他设置了权重的布局一起确定各自 <!-- 为该ImageView控件定义一个唯一标识符id从命名推测是用于展示白色背景相关的图片或视觉元素在界面布局中起到对应的显示作用 -->
<!-- 设置该ImageView的宽度与父容器这里就是外层的FrameLayout宽度相匹配使其在水平方向上填满整个父容器空间 -->
<!-- 设置该ImageView的高度与父容器高度相匹配使其在垂直方向上填满整个父容器空间 -->
<!-- 为该ImageView控件定义一个唯一标识符id推测是用于表示黄色背景是否被选中的相关显示元素初始设置为不可见 -->
<!-- 设置该ImageView的宽度根据其内部要显示的内容自适应包裹住内容的宽度 -->
<!-- 设置该ImageView的高度根据其内部要显示的内容自适应包裹住内容的高度 -->
<!-- 设置该ImageView在其父容器这里是外层的FrameLayout中的对齐方式为右下角对齐 -->
<!-- 设置该ImageView距离其父容器右侧的间距为5设备独立像素dip -->
<!-- 设置该ImageView不可获取焦点意味着用户不能通过焦点操作来与之交互 -->
<!-- 设置该ImageView初始状态为不可见可能在相关逻辑控制下变为可见以表示选中状态等 -->
<!-- 设置该ImageView要显示的图片资源引用名为selected的可绘制资源通常是图片资源 -->
<!-- 结束前面对应的 FrameLayout 标签所开始的布局定义,与开头的 <FrameLayout> 相呼应,完成一个 FrameLayout 布局结构的声明 -->
<!-- 初始设置该 FrameLayout 的宽度为 0 设备独立像素dip后续会结合 layout_weight 属性在水平方向上按比例分配空间 -->
<!-- 设置该 FrameLayout 的高度与父容器高度相匹配,使其在垂直方向上填满整个父容器空间 -->
<!-- 设置权重为 1意味着在水平方向上它会按照比例去分配剩余的空间通常用于和同层级的其他设置了权重的布局共同划分父容器的宽度 -->
<!-- 为这个 ImageView 控件定义一个唯一标识符id从命名来看可能是用于显示蓝色背景相关的图片资源用于界面展示相关用途 -->
<!-- 设置该 ImageView 的宽度与父容器(这里就是外层的 FrameLayout宽度相匹配使其在水平方向上填满整个父容器空间 -->
<!-- 设置该 ImageView 的高度与父容器高度相匹配,使其在垂直方向上填满整个父容器空间 -->
<!-- 为这个 ImageView 控件定义一个唯一标识符id推测是用于表示蓝色背景是否被选中的一个指示性图片比如选中后显示特定样式初始设置为不可见 -->
<!-- 设置该 ImageView 的宽度根据其内部要显示的图片内容自适应,也就是刚好能包裹住图片的宽度 -->
<!-- 设置该 ImageView 的高度根据其内部要显示的图片内容自适应,刚好能包裹住图片的高度 -->
<!-- 设置该 ImageView 在其父容器(外层的 FrameLayout内的对齐方式为右下角对齐确定其显示位置 -->
<!-- 设置该 ImageView 不可获取焦点,意味着用户不能通过焦点操作(比如通过方向键、触摸焦点等方式)与它进行交互 -->
<!-- 设置该 ImageView 初始状态为不可见,可能在后续满足某些条件(比如用户选择了蓝色背景选项等)时,通过代码将其设置为可见来表示相应的选中状态 -->
<!-- 设置该 ImageView 距离其父容器(外层的 FrameLayout右侧的间距为 3 设备独立像素dip用于微调其在水平方向上的位置 -->
<!-- 设置该 ImageView 要显示的图片资源引用名为“selected”的可绘制资源通常是图片资源这个图片应该是用于体现选中状态的特定样式 -->
<!-- 初始设置该FrameLayout的宽度为0设备独立像素dip后续会结合layout_weight属性来按比例分配水平方向上的空间通常用于多个同层级FrameLayout按权重划分父容器宽度 -->
<!-- 设置该FrameLayout的高度与父容器高度相匹配使其在垂直方向上填满整个父容器空间 -->
<!-- 设置权重为1意味着在水平方向上它会按照比例去分配剩余空间和同层级其他设置了权重的布局一起确定各自所占宽度比例 -->
<!-- 为该ImageView控件定义一个唯一标识符id从命名推测是用于展示白色背景相关的图片或视觉元素在界面布局中起到对应的显示作用 -->
<!-- 设置该ImageView的宽度与父容器这里就是外层的FrameLayout宽度相匹配使其在水平方向上填满整个父容器空间 -->
<!-- 设置该ImageView的高度与父容器高度相匹配使其在垂直方向上填满整个父容器空间 -->
<!-- 为该ImageView控件定义一个唯一标识符id推测是用于表示白色背景是否被选中的一个指示性元素初始状态通常是不可见的后续根据选择逻辑来控制显示与否 -->
<!-- 设置该ImageView的宽度根据其内部要显示的图片内容自适应也就是刚好能包裹住图片的宽度 -->
<!-- 设置该ImageView在其父容器外层的FrameLayout内的对齐方式为右下角对齐确定其在布局中的显示位置 -->
<!-- 设置该ImageView不可获取焦点意味着用户不能通过焦点相关操作如使用方向键、触摸焦点等方式与它进行交互 -->
<!-- 设置该ImageView初始状态为不可见等待满足特定条件比如用户选择了白色背景选项等情况再通过代码将其设置为可见来体现相应的选中状态 -->
<!-- 设置该ImageView距离其父容器外层的FrameLayout右侧的间距为2设备独立像素dip用于微调其在水平方向上的位置 -->
<!-- 设置该ImageView要显示的图片资源引用名为“selected”的可绘制资源通常是图片资源该图片一般用于表示选中的特定样式 -->
<!-- 为该ImageView控件定义一个唯一标识符id从命名来看是用于展示绿色背景相关的图片或视觉元素用于界面的相关显示需求 -->
<!-- 设置该ImageView的宽度与父容器这里的外层FrameLayout宽度相匹配在水平方向上填满整个父容器空间 -->
<!-- 设置该ImageView的高度与父容器高度相匹配在垂直方向上填满整个父容器空间 -->
<!-- 为该ImageView控件定义一个唯一标识符id推测是用于表示绿色背景是否被选中的指示性元素初始状态通常是不可见的 -->
<!-- 设置该ImageView的宽度根据其内部要显示的图片内容自适应能包裹住图片宽度 -->
<!-- 设置该ImageView的高度根据其内部要显示的图片内容自适应能包裹住图片高度 -->
<!-- 设置该ImageView在其父容器外层FrameLayout内的对齐方式为右下角对齐确定其显示位置 -->
<!-- 设置该ImageView不可获取焦点用户不能通过焦点操作与之交互 -->
<!-- 设置该ImageView初始状态为不可见待满足特定条件后可通过代码控制显示来体现选中状态 -->
<!-- 设置该ImageView要显示的图片资源引用名为“selected”的可绘制资源用于表示选中的样式 -->
<!-- 为该ImageView控件定义一个唯一标识符id从命名推测是用于展示红色背景相关的图片或视觉元素在界面布局中有对应的显示作用 -->
<!-- 设置该ImageView的宽度与父容器外层FrameLayout宽度相匹配在水平方向上填满整个父容器空间 -->
<!-- 设置该ImageView的高度与父容器高度相匹配在垂直方向上填满整个父容器空间 -->
<!-- 为该ImageView控件定义一个唯一标识符id推测是用于表示红色背景是否被选中的指示性元素初始状态一般是不可见的 -->
<!-- 设置该ImageView的宽度根据其内部要显示的图片内容自适应能包裹住图片宽度 -->
<!-- 设置该ImageView的高度根据其内部要显示的图片内容自适应能包裹住图片高度 -->
<!-- 设置该ImageView在其父容器外层FrameLayout内的对齐方式为右下角对齐确定其显示位置 -->
<!-- 设置该ImageView不可获取焦点用户无法通过焦点操作与之交互 -->
<!-- 设置该ImageView初始状态为不可见后续根据特定条件可通过代码控制显示来体现选中状态 -->
<!-- 设置该ImageView要显示的图片资源引用名为“selected”的可绘制资源用于表示选中的样式 -->
<!-- 结束前面的LinearLayout布局定义与对应的开始标签相匹配完成该线性布局结构的声明 -->
<!-- 为该LinearLayout定义一个唯一标识符id方便在代码中对其进行引用、操作从命名推测可能是用于字体大小选择相关的布局 -->
<!-- 设置该LinearLayout的宽度占满父容器使其在水平方向上填满整个父容器空间 -->
<!-- 设置该LinearLayout的高度根据其内部子元素自适应也就是刚好能包裹住内部子元素所需的高度 -->
<!-- 设置该LinearLayout的背景引用名为font_size_selector_bg的可绘制资源通常是图片、颜色等用于作为背景的资源作为背景 -->
<!-- 设置该LinearLayout在其父容器中的对齐方式为底部对齐使其显示在父容器的底部位置 -->
<!-- 设置该LinearLayout初始状态为不可见可能在满足某些条件比如用户点击相关按钮等操作后才会显示出来 -->
<!-- 为该FrameLayout定义一个唯一标识符id从命名推测可能与字体大小为小的相关设置布局有关 -->
<!-- 初始设置该FrameLayout的宽度为0设备独立像素dip后续会结合layout_weight属性来按比例分配水平方向上的空间 -->
<!-- 设置该FrameLayout的高度根据其内部子元素自适应包裹住内部子元素的高度 -->
<!-- 设置权重为1在水平方向上按比例分配剩余空间通常用于和同层级其他设置了权重的布局共同划分父容器的宽度 -->
<!-- 设置该LinearLayout的宽度根据其内部子元素自适应也就是刚好能包裹住内部子元素所需的宽度 -->
<!-- 设置该LinearLayout的高度根据其内部子元素自适应包裹住内部子元素的高度 -->
<!-- 设置该线性布局内子元素的排列方向为垂直方向,意味着子元素将从上到下依次排列 -->
<!-- 设置该LinearLayout在其父容器这里是外层的FrameLayout内的对齐方式为居中对齐 -->
<!-- 设置该LinearLayout内部子元素的对齐方式为居中对齐使子元素在该布局内处于中心位置 -->
<!-- 设置该ImageView的宽度根据其内部要显示的图片内容自适应也就是刚好能包裹住图片的宽度 -->
<!-- 设置该ImageView的高度根据其内部要显示的图片内容自适应刚好能包裹住图片的高度 -->
<!-- 设置该ImageView要显示的图片资源引用名为font_small的可绘制资源通常是图片资源从命名推测是表示小字体相关的图标 -->
<!-- 设置该ImageView距离其底部相邻元素的间距为5设备独立像素dip用于调整其垂直方向上的位置 -->
<!-- 设置该TextView的宽度根据其要显示的文本内容自适应能包裹住文本的宽度 -->
<!-- 设置该TextView的高度根据其要显示的文本内容自适应能包裹住文本的高度 -->
<!-- 设置该TextView要显示的文本内容引用名为menu_font_small的字符串资源从命名推测是用于表示小字体相关的文字描述 -->
<!-- 应用名为TextAppearanceUnderMenuIcon的样式来设置文本的外观例如字体、字号、颜色等属性该样式通常在样式资源文件中定义 -->
<!-- 为该ImageView定义一个唯一标识符id从命名推测可能是用于表示小字体选项是否被选中的相关显示元素初始状态通常是不可见的 -->
<!-- 设置该ImageView的宽度根据其内部要显示的图片内容自适应能包裹住图片的宽度 -->
<!-- 设置该ImageView的高度根据其内部要显示的图片内容自适应能包裹住图片的 height -->
<!-- 设置该ImageView在其父容器外层的FrameLayout内的对齐方式为右下角对齐确定其在布局中的显示位置 -->
<!-- 设置该ImageView距离其父容器右侧的间距为6设备独立像素dip用于微调其在水平方向上的位置 -->
<!-- 设置该ImageView距离其底部相邻元素的间距为 -7设备独立像素dip这里的负数值可能用于特殊的布局调整效果 -->
<!-- 设置该ImageView不可获取焦点意味着用户不能通过焦点相关操作如使用方向键、触摸焦点等方式与它进行交互 -->
<!-- 设置该ImageView初始状态为不可见等待满足特定条件比如用户选择了小字体选项等情况再通过代码将其设置为可见来体现相应的选中状态 -->
<!-- 设置该ImageView要显示的图片资源引用名为“selected”的可绘制资源通常是图片资源该图片一般用于表示选中的特定样式 -->
<!-- 结束前面的FrameLayout布局定义与对应的开始标签相匹配完成该帧布局结构的声明 -->
<!-- 为该FrameLayout定义一个唯一标识符id从命名推测可能与字体大小为正常常规的相关设置布局有关方便后续在代码中引用操作 -->
<!-- 初始设置该FrameLayout的宽度为0设备独立像素dip后续会结合layout_weight属性来按比例分配水平方向上的空间 -->
<!-- 设置该FrameLayout的高度根据其内部子元素自适应也就是刚好能包裹住内部子元素所需的高度 -->
<!-- 设置权重为1在水平方向上按比例分配剩余空间通常用于和同层级其他设置了权重的布局共同划分父容器这里是外层的LinearLayout的宽度 -->
<!-- 设置该LinearLayout的宽度根据其内部子元素自适应即刚好能包裹住内部子元素所需要的宽度 -->
<!-- 设置该LinearLayout的高度根据其内部子元素自适应包裹住内部子元素的高度 -->
<!-- 设置该线性布局内子元素的排列方向为垂直方向,意味着子元素将从上到下依次排列 -->
<!-- 设置该LinearLayout在其父容器这里是外层的FrameLayout内的对齐方式为居中对齐 -->
<!-- 设置该LinearLayout内部子元素的对齐方式为居中对齐使子元素在该布局内处于中心位置 -->
<!-- 设置该LinearLayout的宽度根据其内部子元素自适应即刚好能包裹住内部子元素所需要的宽度 -->
<!-- 设置该LinearLayout的高度根据其内部子元素自适应包裹住内部子元素的高度 -->
<!-- 设置该线性布局内子元素的排列方向为垂直方向,意味着子元素将从上到下依次排列 -->
<!-- 设置该LinearLayout在其父容器这里是外层的FrameLayout内的对齐方式为居中对齐 -->
<!-- 设置该LinearLayout内部子元素的对齐方式为居中对齐使子元素在该布局内处于中心位置 -->
<!-- 设置该ImageView的宽度根据其内部要显示的图片内容自适应也就是刚好能包裹住图片的宽度 -->
<!-- 设置该ImageView的高度根据其内部要显示的图片内容自适应刚好能包裹住图片的高度 -->
<!-- 设置该ImageView要显示的图片资源引用名为font_normal的可绘制资源通常是图片资源从命名推测是表示正常常规字体相关的图标 -->
<!-- 设置该ImageView距离其底部相邻元素的间距为5设备独立像素dip用于调整其垂直方向上的位置 -->
<!-- 设置该TextView的宽度根据其要显示的文本内容自适应能包裹住文本的宽度 -->
<!-- 设置该TextView的高度根据其要显示的文本内容自适应能包裹住文本的高度 -->
<!-- 设置该TextView要显示的文本内容引用名为menu_font_normal的字符串资源从命名推测是用于表示正常常规字体相关的文字描述 -->
<!-- 应用名为TextAppearanceUnderMenuIcon的样式来设置文本的外观例如字体、字号、颜色等属性该样式通常在样式资源文件中定义 -->
<!-- 为该ImageView定义一个唯一标识符id从命名推测可能是用于表示正常常规字体选项是否被选中的相关显示元素初始状态通常是不可见的 -->
<!-- 设置该ImageView的宽度根据其内部要显示的图片内容自适应能包裹住图片的宽度 -->
<!-- 设置该ImageView的高度根据其内部要显示的图片内容自适应能包裹住图片的高度 -->
<!-- 设置该ImageView在其父容器外层的FrameLayout内的对齐方式为右下角对齐确定其在布局中的显示位置 -->
<!-- 设置该ImageView不可获取焦点意味着用户不能通过焦点相关操作如使用方向键、触摸焦点等方式与它进行交互 -->
<!-- 设置该ImageView初始状态为不可见等待满足特定条件比如用户选择了正常字体选项等情况再通过代码将其设置为可见来体现相应的选中状态 -->
<!-- 设置该ImageView距离其父容器右侧的间距为6设备独立像素dip用于微调其在水平方向上的位置 -->
<!-- 设置该ImageView距离其底部相邻元素的间距为 -7设备独立像素dip这里的负数值可能用于特殊的布局调整效果 -->
<!-- 设置该ImageView要显示的图片资源引用名为“selected”的可绘制资源通常是图片资源该图片一般用于表示选中的特定样式 -->
<!-- 为该FrameLayout定义一个唯一标识符id从命名推测可能与字体大小为大的相关设置布局有关便于后续代码中对其进行引用等操作 -->
<!-- 初始设置该FrameLayout的宽度为0设备独立像素dip后续会结合layout_weight属性来按比例分配水平方向上的空间 -->
<!-- 初始设置该FrameLayout的宽度为0设备独立像素dip后续会结合layout_weight属性来按比例分配水平方向上的空间 -->
<!-- 设置权重为1在水平方向上按比例分配剩余空间用于和同层级其他设置了权重的布局共同划分父容器的宽度 -->
<!-- 设置该LinearLayout的宽度根据其内部子元素自适应能包裹住内部子元素所需的宽度 -->
<!-- 设置该LinearLayout的高度根据其内部子元素自适应能包裹住内部子元素的高度 -->
<!-- 设置该线性布局内子元素的排列方向为垂直方向,子元素按从上到下顺序排列 -->
<!-- 设置该LinearLayout在其父容器外层的FrameLayout内的对齐方式为居中对齐 -->
<!-- 设置该LinearLayout内部子元素的对齐方式为居中对齐让子元素处于该布局中心位置 -->
<!-- 设置该ImageView的宽度根据其内部要显示的图片内容自适应包裹住图片宽度 -->
<!-- 设置该ImageView的高度根据其内部要显示的图片内容自适应包裹住图片高度 -->
<!-- 设置该ImageView要显示的图片资源引用名为font_large的可绘制资源通常是图片资源推测是表示大字体相关的图标 -->
<!-- 设置该ImageView距离其底部相邻元素的间距为5设备独立像素dip调整垂直位置 -->
<!-- 设置该TextView的宽度根据其要显示的文本内容自适应包裹住文本宽度 -->
<!-- 设置该TextView的高度根据其要显示的文本内容自适应包裹住文本高度 -->
<!-- 设置该TextView要显示的文本内容引用名为menu_font_large的字符串资源用于表示大字体相关文字描述 -->
<!-- 应用样式设置文本外观,样式在对应资源文件中定义 -->
<!-- 结束前面的LinearLayout布局定义与对应的开始标签相匹配完成该线性布局结构的声明 -->
<!-- 为该ImageView定义一个唯一标识符id从命名推测是用于表示大字体选项是否被选中的相关显示元素方便在代码中对其进行操作和引用 -->
<!-- 设置该ImageView的宽度根据其内部要显示的图片内容自适应也就是刚好能包裹住图片的宽度 -->
<!-- 设置该ImageView的高度根据其内部要显示的图片内容自适应刚好能包裹住图片的高度 -->
<!-- 设置该ImageView在其父容器这里的父容器应该是外层与之对应的布局元素可能是上一层的FrameLayout内的对齐方式为右下角对齐确定其显示位置 -->
<!-- 设置该ImageView不可获取焦点意味着用户不能通过焦点相关操作比如使用方向键、触摸焦点等方式与它进行交互 -->
<!-- 设置该ImageView初始状态为不可见等待满足特定条件比如用户选择了大字体选项等情况再通过代码将其设置为可见来体现相应的选中状态 -->
<!-- 设置该ImageView距离其父容器右侧的间距为6设备独立像素dip用于微调其在水平方向上的位置 -->
<!-- 设置该ImageView距离其底部相邻元素的间距为 -7设备独立像素dip这里的负数值可能用于特殊的布局调整效果使其在垂直方向上能达到特定的布局位置 -->
<!-- 设置该ImageView要显示的图片资源引用名为“selected”的可绘制资源通常是图片资源该图片一般用于表示选中的特定样式 -->
<!-- 结束前面的FrameLayout布局定义与对应的开始标签相匹配完成该帧布局结构的声明 -->
<!-- 为该FrameLayout定义一个唯一标识符id从命名推测可能与字体大小为超大的相关设置布局有关便于后续在代码中对其进行引用和操作 -->
<!-- 初始设置该FrameLayout的宽度为0设备独立像素dip后续会结合layout_weight属性来按比例分配水平方向上的空间 -->
<!-- 设置该FrameLayout的高度根据其内部子元素自适应也就是刚好能包裹住内部子元素所需的高度 -->
<!-- 设置权重为1在水平方向上按比例分配剩余空间通常用于和同层级其他设置了权重的布局共同划分父容器这里应该是外层的LinearLayout的宽度 -->
<!-- 设置该LinearLayout的宽度根据其内部子元素自适应也就是刚好能包裹住内部子元素所需的宽度 -->
<!-- 设置该LinearLayout的高度根据其内部子元素自适应包裹住内部子元素的高度 -->
<!-- 设置该线性布局内子元素的排列方向为垂直方向,意味着子元素将从上到下依次排列 -->
<!-- 设置该LinearLayout在其父容器这里是外层的FrameLayout内的对齐方式为居中对齐 -->
<!-- 设置该LinearLayout内部子元素的对齐方式为居中对齐使子元素在该布局内处于中心位置 -->
<!-- 设置该ImageView的宽度根据其内部要显示的图片内容自适应也就是刚好能包裹住图片的宽度 -->
<!-- 设置该ImageView的高度根据其内部要显示的图片内容自适应刚好能包裹住图片的高度 -->
<!-- 设置该ImageView要显示的图片资源引用名为font_super的可绘制资源通常是图片资源从命名推测是表示超大字体相关的图标 -->
<!-- 设置该ImageView距离其底部相邻元素的间距为5设备独立像素dip用于调整其垂直方向上的位置 -->
<!-- 设置该TextView的宽度根据其要显示的文本内容自适应能包裹住文本的宽度 -->
<!-- 设置该TextView的高度根据其要显示的文本内容自适应能包裹住文本的高度 -->
<!-- 设置该TextView要显示的文本内容引用名为menu_font_super的字符串资源从命名推测是用于表示超大字体相关的文字描述 -->
<!-- 应用名为TextAppearanceUnderMenuIcon的样式来设置文本的外观例如字体、字号、颜色等属性该样式通常在样式资源文件中定义 -->
<!-- 为该ImageView定义一个唯一标识符id从命名推测可能是用于表示超大字体选项是否被选中的相关显示元素初始状态通常是不可见的 -->
<!-- 设置该ImageView的宽度根据其内部要显示的图片内容自适应能包裹住图片的宽度 -->
<!-- 设置该ImageView的高度根据其内部要显示的图片内容自适应能包裹住图片的高度 -->
<!-- 设置该ImageView在其父容器外层的FrameLayout内的对齐方式为右下角对齐确定其在布局中的显示位置 -->
<!-- 设置该ImageView不可获取焦点意味着用户不能通过焦点相关操作与它进行交互 -->
<!-- 设置该ImageView初始状态为不可见等待满足特定条件比如用户选择了超大字体选项等情况再通过代码将其设置为可见来体现相应的选中状态 -->
<!-- 设置该ImageView距离其父容器右侧的间距为6设备独立像素dip用于微调其在水平方向上的位置 -->
<!-- 设置该ImageView距离其底部相邻元素的间距为 -7设备独立像素dip用于特殊的布局调整效果 -->
<!-- 设置该ImageView要显示的图片资源引用名为“selected”的可绘制资源通常是图片资源用于表示选中的特定样式 -->
<!-- 结束最外层的FrameLayout布局定义与整个XML文件开头的<FrameLayout>标签相对应,完成整个布局结构的声明 -->
<!-- 结束前面的LinearLayout布局定义与对应的开始标签相匹配完成该线性布局结构的声明 -->

@ -1,39 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该XML文件遵循的版本是1.0使用的编码格式为utf-8这是XML文件开头的标准标识用于告知解析器如何正确解析该文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<!-- 引入安卓的命名空间,后续通过这个命名空间就能使用安卓系统中定义的各种布局属性以及控件相关的属性了 -->
<!-- 设置LinearLayout线性布局的宽度占满父容器使其在水平方向上填满整个父容器空间 -->
<!-- 设置LinearLayout的高度根据其内部子元素自适应也就是刚好能包裹住内部子元素所需的高度 -->
<CheckBox
android:id="@+id/cb_edit_item"
android:layout_width="wrap_content"
android:layout_height="28dip"
android:checked="false"
android:focusable="false"
android:layout_gravity="top|left" />
android:id="@+id/cb_edit_item"
android:layout_width="wrap_content"
android:layout_height="28dip"
android:checked="false"
android:focusable="false"
android:layout_gravity="top|left"
/>
<!-- 为该CheckBox控件定义一个唯一标识符id方便在后续代码中对其进行引用、操作等 -->
<!-- 设置CheckBox的宽度根据其自身内容如勾选框图标等自适应也就是刚好能包裹住其内容的宽度 -->
<!-- 设置CheckBox的高度为28设备独立像素dip确定其垂直方向上的固定尺寸 -->
<!-- 设置该CheckBox初始状态为未勾选 -->
<!-- 设置该CheckBox不可获取焦点意味着用户不能通过焦点操作比如使用方向键等方式使其获得焦点 -->
<!-- 设置该CheckBox在其父容器这里是外层的LinearLayout内的对齐方式为左上角对齐确定其在布局中的位置 -->
<net.micode.notes.ui.NoteEditText
android:id="@+id/et_edit_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:lineSpacingMultiplier="1.2"
android:layout_gravity="center_vertical"
android:textAppearance="@style/TextAppearancePrimaryItem"
android:background="@null" />
android:id="@+id/et_edit_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:lineSpacingMultiplier="1.2"
android:layout_gravity="center_vertical"
android:textAppearance="@style/TextAppearancePrimaryItem"
android:background="@null"
/>
<!-- 为该NoteEditText控件定义一个唯一标识符id从命名推测可能是用于编辑文本内容的输入框 -->
<!-- 设置该NoteEditText的宽度占满父容器这里的父容器是外层的LinearLayout使其在水平方向上填满整个可用空间 -->
<!-- 设置该NoteEditText的高度根据其内部文本内容自适应也就是刚好能包裹住文本的高度 -->
<!-- 设置文本行间距倍数为1.2,用于增加文本行之间的间距,使文本显示更清晰美观 -->
<!-- 设置该NoteEditText在其父容器内的对齐方式为垂直居中对齐确保其在垂直方向上处于合适位置 -->
<!-- 应用名为TextAppearancePrimaryItem的样式来设置文本的外观例如字体、字号、颜色等属性该样式通常在样式资源文件中定义 -->
<!-- 设置背景为空,即不显示默认的背景样式,可能是为了让输入框更好地融入整体界面或者根据需要自定义背景效果 -->
</LinearLayout>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
此处是版权相关声明说明代码所属的开源社区是The MiCode Open Source Community版权时间范围为2010 - 2011年
Licensed under the Apache License, Version 2.0 (the "License");
表示该代码遵循Apache License 2.0开源协议进行授权许可,意味着使用该代码需要遵循此协议规定
you may not use this file except in compliance with the License.
强调若要使用这个文件必须按照上述Apache License 2.0协议来操作
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过访问此网址http://www.apache.org/licenses/LICENSE-2.0获取Apache License 2.0协议的详细内容
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”进行分发,不提供额外保证等情况
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
进一步指出该代码不附带任何明示或暗示的保证及相关条件,使用者需自行承担使用风险
See the License for the specific language governing permissions and
limitations under the License.
提示查看协议以了解关于权限及限制方面的详细内容 -->

@ -1,78 +1,133 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该XML文件遵循的版本是1.0使用的编码格式为utf-8这是XML文件开头的标准标识用于告知解析器按照此版本和编码来正确解析该文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
此处为版权相关声明说明该代码隶属于The MiCode Open Source Community这个开源社区版权有效期在2010至2011年之间
Licensed under the Apache License, Version 2.0 (the "License");
表示此代码遵循Apache License 2.0开源协议进行授权许可,意味着使用该代码时必须遵守此协议所规定的各项要求
you may not use this file except in compliance with the License.
着重强调若要使用本文件必须严格按照上述的Apache License 2.0协议来操作
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可通过访问这个网址http://www.apache.org/licenses/LICENSE-2.0来获取Apache License 2.0协议的完整详细内容
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以其当前的状态(“原样”)进行分发,不会额外提供诸如质量保证等相关内容
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
进一步明确指出该代码不存在任何明示或暗示的保证以及相关条件,使用者需自行承担使用该代码所带来的风险
See the License for the specific language governing permissions and
limitations under the License.
-->
提示查看协议内容,以便了解关于权限以及限制方面的详细规定 -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/note_item"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/note_item"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<!-- 引入安卓的命名空间,后续可借助此命名空间使用安卓系统所定义的各类布局属性和控件相关属性 -->
<!-- 为该FrameLayout定义一个唯一标识符id方便在代码中对其进行引用、操作从命名推测可能与笔记相关的某个项目布局 -->
<!-- 设置FrameLayout的宽度占满父容器使其在水平方向上填满所在父容器的全部可用空间 -->
<!-- 设置FrameLayout的高度占满父容器即在垂直方向上填满所在父容器的全部可用空间 -->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_vertical">
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
>
<!-- 设置LinearLayout的宽度占满父容器这里的父容器就是外层的FrameLayout使其在水平方向上填满整个父布局空间 -->
<!-- 设置LinearLayout的高度根据其内部子元素自适应也就是刚好能包裹住内部子元素所需的高度 -->
<!-- 设置该LinearLayout在其父容器FrameLayout内的对齐方式为垂直居中对齐确保其在垂直方向上处于合适位置 -->
<!-- 设置该LinearLayout内部子元素的对齐方式为垂直居中对齐使子元素在该线性布局内垂直方向上处于中心位置 -->
<LinearLayout
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
>
<!-- 初始设置该LinearLayout的宽度为0设备独立像素dip后续会结合layout_weight属性来按比例分配水平方向上的空间 -->
<!-- 设置该LinearLayout的高度根据其内部子元素自适应包裹住内部子元素的高度 -->
<!-- 设置该LinearLayout的高度根据其内部子元素自适应包裹住内部子元素的高度 -->
<!-- 设置权重为1在水平方向上按比例分配剩余空间用于和同层级其他设置了权重的布局共同划分父容器的宽度 -->
<!-- 设置该线性布局内子元素的排列方向为垂直方向,意味着子元素将从上到下依次排列 -->
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="0dip"
android:layout_weight="1"
android:textAppearance="@style/TextAppearancePrimaryItem"
android:visibility="gone" />
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="0dip"
android:layout_weight="1"
android:textAppearance="@style/TextAppearancePrimaryItem"
android:visibility="gone"
/>
<!-- 为该TextView控件定义一个唯一标识符id从命名推测可能用于显示名称相关信息不过当前设置为不可见 -->
<!-- 设置该TextView的宽度根据其要显示的文本内容自适应能包裹住文本的宽度 -->
<!-- 初始设置该TextView的高度为0设备独立像素dip后续结合layout_weight属性分配垂直方向空间 -->
<!-- 设置权重为1在垂直方向上按比例分配剩余空间通常用于占据较多垂直空间 -->
<!-- 应用名为TextAppearancePrimaryItem的样式来设置文本的外观像字体、字号、颜色等属性在对应的样式资源文件中定义 -->
<!-- 设置该TextView初始状态为不可见可能在满足某些条件后才显示出来 -->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical">
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
>
<!-- 设置该LinearLayout的宽度占满父容器这里的父容器是外层的垂直方向的LinearLayout在水平方向上填满空间 -->
<!-- 设置该LinearLayout的高度根据其内部子元素自适应包裹住内部子元素的高度 -->
<!-- 设置该LinearLayout在其父容器内的对齐方式为垂直居中对齐 -->
<TextView
android:id="@+id/tv_title"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="true" />
android:id="@+id/tv_title"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="true"
/>
<!-- 为该TextView控件定义一个唯一标识符id从命名推测可能用于显示标题相关信息 -->
<!-- 初始设置该TextView的宽度为0设备独立像素dip后续结合layout_weight属性分配水平方向空间 -->
<!-- 设置该TextView的高度根据其要显示的文本内容自适应能包裹住文本的高度 -->
<!-- 设置权重为1在水平方向上按比例分配剩余空间通常占据较多水平空间 -->
<!-- 设置该TextView只能显示单行文本多余文本可能会截断显示 -->
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearanceSecondaryItem" />
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearanceSecondaryItem"
/>
<!-- 为该TextView控件定义一个唯一标识符id从命名推测可能用于显示时间相关信息 -->
<!-- 设置该TextView的宽度根据其要显示的文本内容自适应能包裹住文本的宽度 -->
<!-- 设置该TextView的高度根据其要显示的文本内容自适应能包裹住文本的高度 -->
<!-- 应用名为TextAppearanceSecondaryItem的样式来设置文本的外观 -->
</LinearLayout>
</LinearLayout>
<CheckBox
android:id="@android:id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
android:clickable="false"
android:visibility="gone" />
android:id="@android:id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
android:clickable="false"
android:visibility="gone"
/>
</LinearLayout>
<!-- 为该CheckBox控件定义一个唯一标识符id这里使用了安卓系统内置的命名空间下的id可能是遵循某种规范来定义用于一些标准的勾选操作相关功能 -->
<!-- 设置CheckBox的宽度根据其自身内容如勾选框图标等自适应包裹住其内容的宽度 -->
<!-- 设置CheckBox的高度根据其自身内容自适应包裹住其内容的高度 -->
<!-- 设置该CheckBox不可获取焦点意味着用户不能通过焦点操作如使用方向键等方式使其获得焦点 -->
<!-- 设置该CheckBox不可点击用户无法通过点击操作来改变其勾选状态等 -->
<!-- 设置该CheckBox初始状态为不可见可能在特定条件下才会显示并发挥作用 -->
<ImageView
android:id="@+id/iv_alert_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|right"/>
android:id="@+id/iv_alert_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|right"
/>
</FrameLayout>
<!-- 为该ImageView控件定义一个唯一标识符id从命名推测可能用于显示提醒相关的图标 -->
<!-- 设置该ImageView的宽度根据其内部要显示的图片内容自适应包裹住图片的宽度 -->
<!-- 设置该ImageView的高度根据其内部要显示的图片内容自适应包裹住图片的 height -->
<!-- 设置该ImageView在其父容器FrameLayout内的对齐方式为右上角对齐确定其在布局中的显示位置 -->

@ -1,69 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式是 utf-8这是 XML 文件开头的标准标识,用于告知解析器按照此版本和编码规则来正确解析该文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@drawable/list_background">
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
android:background="@drawable/list_background"
>
<!-- 设置 FrameLayout帧布局的宽度占满父容器意味着它在水平方向上会填满所在父容器的全部可用空间 -->
<!-- 设置 FrameLayout 的高度占满父容器,也就是在垂直方向上会填满所在父容器的全部可用空间 -->
<!-- 设置 FrameLayout 的背景这里引用了名为“list_background”的可绘制资源通常可以是图片、颜色等能作为背景的资源来作为该布局的背景 -->
<!-- 引入安卓的命名空间,后续通过这个命名空间就能使用安卓系统中定义的各种布局属性以及控件相关的属性了 -->
<TextView
android:id="@+id/tv_title_bar"
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/title_bar_bg"
android:visibility="gone"
android:gravity="center_vertical"
android:singleLine="true"
android:textColor="#FFEAD1AE"
android:textSize="@dimen/text_font_size_medium" />
android:layout_height="fill_parent"
android:orientation="vertical"
>
<!-- 设置 LinearLayout线性布局的宽度占满父容器此处父容器为外层的 FrameLayout使其在水平方向上填满整个父布局空间 -->
<!-- 设置 LinearLayout 的高度占满父容器,让它在垂直方向上也填满整个父布局空间 -->
<!-- 设置该线性布局内子元素的排列方向为垂直方向,意味着子元素会按照从上到下的顺序依次排列 -->
<TextView
android:id="@+id/tv_title_bar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/title_bar_bg"
android:visibility="gone"
android:gravity="center_vertical"
android:singleLine="true"
android:textColor="#FFEAD1AE"
android:textSize="@dimen/text_font_size_medium"
/>
<!-- 为这个 TextView 控件定义一个唯一标识符id从命名推测可能用于显示标题栏相关的文本信息 -->
<!-- 设置该 TextView 的宽度占满父容器(这里的父容器是外层的 LinearLayout使其在水平方向上填满整个父容器空间 -->
<!-- 设置该 TextView 的高度根据其要显示的文本内容自适应,也就是刚好能包裹住文本的高度 -->
<!-- 设置该 TextView 的背景引用名为“title_bar_bg”的可绘制资源通常是图片、颜色等用于作为背景的资源作为背景 -->
<!-- 设置该 TextView 初始状态为不可见,可能在满足某些条件(比如特定页面显示、用户操作后等)后才会显示出来 -->
<!-- 设置文本在该 TextView 控件内的对齐方式为垂直居中对齐,使文本在垂直方向上处于中间位置 -->
<!-- 设置该 TextView 只能显示单行文本,多余的文本内容可能会被截断显示 -->
<!-- 设置该 TextView 显示文本的颜色,这里使用十六进制颜色值 #FFEAD1AE 来指定颜色 -->
<!-- 设置该 TextView 显示文本的字号大小引用名为“text_font_size_medium”的尺寸资源来确定具体字号该资源通常在对应的资源文件中定义 -->
<ListView
android:id="@+id/notes_list"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:cacheColorHint="@null"
android:listSelector="@android:color/transparent"
android:divider="@null"
android:fadingEdge="@null" />
android:id="@+id/notes_list"
android:layout_width="fill_parent"
android:layout_height="0dip"
android:layout_weight="1"
android:cacheColorHint="@null"
android:listSelector="@android:color/transparent"
android:divider="@null"
/>
</LinearLayout>
<!-- 为这个 ListView 控件定义一个唯一标识符id从命名推测是用于展示笔记列表相关内容的列表视图 -->
<!-- 设置该 ListView 的宽度占满父容器(这里的父容器是外层的 LinearLayout使其在水平方向上填满整个父容器空间 -->
<!-- 初始设置该 ListView 的高度为 0 设备独立像素dip后续会结合 layout_weight 属性来按比例分配垂直方向上的空间 -->
<!-- 设置权重为 1在垂直方向上按比例分配剩余空间通常用于使其占据较大的垂直空间以展示列表内容 -->
<!-- 设置缓存颜色提示为 null这样可以避免 ListView 在滚动等操作时出现颜色相关的显示问题(比如闪烁等) -->
<!-- 设置列表项被选中时的背景颜色为透明,让列表项在选中时视觉上更自然,不会出现默认的选中背景色 -->
<!-- 设置列表项之间的分割线为 null即不显示分割线使列表外观更简洁 -->
<Button
android:id="@+id/btn_new_note"
android:background="@drawable/new_note"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="false"
android:layout_gravity="bottom" />
android:id="@+id/btn_new_note"
android:background="@drawable/new_note"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:focusable="false"
android:layout_gravity="bottom"
/>
<!-- 为这个 Button 控件定义一个唯一标识符id从命名推测可能是用于点击创建新笔记的按钮 -->
<!-- 设置该 Button 的背景引用名为“new_note”的可绘制资源通常是图片资源作为背景比如按钮的外观样式图片等 -->
<!-- 设置该 Button 的宽度与父容器宽度匹配,使其在水平方向上填满整个父容器空间 -->
<!-- 设置该 Button 的高度根据其内部文本或图标等内容自适应,包裹住内容的高度 -->
<!-- 设置该 Button 不可获取焦点,意味着用户不能通过焦点操作(比如使用方向键等方式)使其获得焦点 -->
<!-- 设置该 Button 在其父容器(这里的父容器是外层的 FrameLayout内的对齐方式为底部对齐使其显示在底部位置 -->
<Button
android:id="@+id/btn_set"
android:background="@drawable/ic_menu_more"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
android:padding="10dp"
android:layout_margin="10dp"
android:onClick="OnOpenMenu"
android:layout_gravity="bottom|right" />
android:id="@+id/btn_set"
android:background="@drawable/ic_menu_more"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
android:padding="10dp"
android:layout_margin="10dp"
android:onClick="OnOpenMenu"
android:layout_gravity="bottom|right"
/>
</FrameLayout>
<!-- 为这个 Button 控件定义一个唯一标识符id从命名推测可能是用于点击打开设置相关菜单之类的按钮 -->
<!-- 设置该 Button 的背景引用名为“ic_menu_more”的可绘制资源通常是图片资源作为背景 -->
<!-- 设置该 Button 的宽度根据其内部文本或图标等内容自适应,包裹住内容的宽度 -->
<!-- 设置该 Button 的高度根据其内部文本或图标等内容自适应,包裹住内容的高度 -->
<!-- 设置该 Button 不可获取焦点,意味着用户不能通过焦点操作与它进行交互 -->
<!-- 设置该 Button 的内边距为 10 设备独立像素dp也就是在按钮内容文本、图标等的四周留出一定的空白空间 -->
<!-- 设置该 Button 的外边距为 10 设备独立像素dp用于调整按钮与周围元素在水平和垂直方向上的间距 -->
<!-- 设置点击该 Button 时触发的方法名为“OnOpenMenu”需要在对应的代码逻辑中实现这个方法来处理点击事件 -->
<!-- 设置该 Button 在其父容器(外层的 FrameLayout内的对齐方式为右下角对齐使其显示在右下角位置 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
这里是版权相关声明,说明代码所属的开源社区是 The MiCode Open Source Community版权时间范围是 2010 - 2011 年。
Licensed under the Apache License, Version 2.0 (the "License");
表示此文件遵循 Apache License 2.0 开源协议进行授权许可,意味着若要使用该文件,必须遵守此协议的相关规定。
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过访问这个网址http://www.apache.org/licenses/LICENSE-2.0)获取 Apache License 2.0 协议的详细内容。
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”的状态进行分发,不会附带额外的保证等情况。
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
进一步强调该代码不存在任何明示或暗示的保证以及相关条件,使用者需要自行承担使用代码带来的风险。
See the License for the specific language governing permissions and
limitations under the License.
提示查看协议内容,以了解关于权限以及限制方面的详细规定。 -->

@ -1,26 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式是 utf-8这是 XML 文件开头的标准标识,用于告知解析器按照此版本和编码规则来正确解析文件内容 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/navigation_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
android:orientation="horizontal"
>
<!-- 引入安卓的命名空间,后续通过这个命名空间就能使用安卓系统中定义的各种布局属性以及控件相关的属性了 -->
<!-- 为该 LinearLayout 定义一个唯一标识符id方便在后续代码中对其进行引用、操作等从命名来看可能是用于导航栏相关的布局 -->
<!-- 设置 LinearLayout线性布局的宽度与父容器宽度相匹配使其在水平方向上填满整个父容器空间 -->
<!-- 设置 LinearLayout 的高度与父容器高度相匹配,使其在垂直方向上填满整个父容器空间 -->
<!-- 设置该线性布局内子元素的排列方向为水平方向,意味着子元素会按照从左到右的顺序依次排列 -->
<Button android:id="@+id/selection_menu"
android:divider="?android:attr/listDividerAlertDialog"
@ -28,5 +22,29 @@
android:gravity="left|center_vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
style="?android:attr/borderlessButtonStyle" />
</LinearLayout>
style="?android:attr/borderlessButtonStyle"
/>
<!-- 为该 Button 控件定义一个唯一标识符id从命名推测可能是用于选择菜单相关操作的按钮 -->
<!-- 设置按钮的分割线样式,这里引用了安卓系统内置属性(?android:attr/listDividerAlertDialog来指定分割线的样式通常用于在按钮相关显示上呈现分割效果 -->
<!-- 设置该 Button 只能显示单行文本,若文本内容过长可能会被截断显示 -->
<!-- 设置文本在该 Button 控件内的对齐方式,水平方向居左对齐、垂直方向居中对齐,让文本在按钮内处于合适位置 -->
<!-- 设置该 Button 的宽度根据其内部要显示的文本或图标等内容自适应,也就是刚好能包裹住这些内容的宽度 -->
<!-- 设置该 Button 的高度与父容器(这里就是外层的 LinearLayout高度相匹配使其在垂直方向上填满整个父容器空间 -->
<!-- 应用安卓系统内置的样式(?android:attr/borderlessButtonStyle来设置按钮的外观风格通常这种样式会使按钮呈现无边框的效果 -->
</LinearLayout>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
此处是版权相关声明,说明代码所属的开源社区是 The MiCode Open Source Community版权时间范围在 2010 - 2011 年。
Licensed under the Apache License, Version 2.0 (the "License");
表明这份代码遵循 Apache License 2.0 开源协议进行授权许可,意味着若要使用该文件,必须遵守此协议规定的相关要求。
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
提示可以通过访问这个网址http://www.apache.org/licenses/LICENSE-2.0)获取 Apache License 2.0 协议的详细内容。
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”的状态进行分发,不会附带额外的保证、条件等情况。
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
进一步强调该代码不存在任何明示或暗示的保证以及相关条件,使用者需要自行承担使用代码带来的风险。
See the License for the specific language governing permissions and
limitations under the License.
提醒查看协议内容,以了解关于权限以及限制方面的详细规定。 -->

@ -1,24 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式是 utf-8这是 XML 文件开头的标准标识,用于告知解析器按照相应版本和编码规则来正确解析该文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
<View
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="100dip"
android:visibility="invisible"
android:focusable="false"
android:background="@drawable/list_footer_bg"
/>
<!-- 引入安卓的命名空间,后续借助此命名空间就能使用安卓系统所定义的各类布局属性以及控件相关属性 -->
<!-- 设置 View视图的宽度占满父容器使其在水平方向上填满所在父容器的全部可用空间 -->
<!-- 设置 View 的高度为 100 设备独立像素dip确定其在垂直方向上的固定尺寸大小 -->
<!-- 设置该 View 的初始可见性为不可见状态,不过它依然会占据布局空间,只是用户看不到它,与 "gone" 不同的是,它的空间不会被回收 -->
<!-- 设置该 View 不可获取焦点,意味着用户不能通过焦点操作(例如使用方向键、触摸焦点等方式)使其获得焦点 -->
<!-- 设置该 View 的背景,引用名为 "list_footer_bg" 的可绘制资源(通常是图片、颜色等可作为背景的资源)来作为该 View 的背景 -->
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<View
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="100dip"
android:visibility="invisible"
android:focusable="false"
android:background="@drawable/list_footer_bg" />
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
这里是版权相关声明,说明该代码隶属于 The MiCode Open Source Community 这个开源社区,版权时间范围在 2010 - 2011 年。
Licensed under the Apache License, Version 2.0 (the "License");
表明这份代码遵循 Apache License 2.0 开源协议进行授权许可,意味着若要使用该文件,必须遵守此协议所规定的各项要求。
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可通过访问这个网址http://www.apache.org/licenses/LICENSE-2.0)获取 Apache License 2.0 协议的详细内容。
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”的状态进行分发,不会额外附带各种保证、条件等情况。
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
进一步强调该代码不存在任何明示或暗示的保证以及相关条件,使用者需要自行承担使用代码带来的风险。
See the License for the specific language governing permissions and
limitations under the License.
提示查看协议内容,以了解关于权限以及限制方面的详细规定。 -->

@ -1,41 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式是 utf-8这是 XML 文件开头的标准标识,用于告知解析器按照此版本和编码规则来正确解析文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
此处为版权相关声明,说明代码所属的开源社区是 The MiCode Open Source Community版权时间范围在 2010 - 2011 年。
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
表示此文件遵循 Apache License 2.0 开源协议进行授权许可,意味着若要使用该文件,必须遵守该协议的相关规定。
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过访问网址http://www.apache.org/licenses/LICENSE-2.0)获取 Apache License 2.0 协议的详细内容。
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”的状态进行分发,不会附带额外的保证等情况。
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
强调该代码不存在任何明示或暗示的保证以及相关条件,使用者需自行承担使用代码带来的风险。
See the License for the specific language governing permissions and
limitations under the License.
-->
提示查看协议内容,以了解关于权限及限制方面的详细规定。 -->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:android="http://schemas.android.com/apk/res/android"
>
<!-- 设置LinearLayout线性布局的宽度占满父容器使其在水平方向上填满整个父容器空间 -->
<!-- 设置LinearLayout的高度根据其内部子元素自适应也就是刚好能包裹住内部子元素所需的高度 -->
<!-- 设置该线性布局内子元素的排列方向为垂直方向,意味着子元素会按照从上到下的顺序依次排列 -->
<!-- 引入安卓的命名空间,后续通过这个命名空间就能使用安卓系统中定义的各种布局属性以及控件相关的属性了 -->
<Button
android:id="@+id/preference_sync_button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dip"
android:layout_marginLeft="30dip"
android:layout_marginRight="30dip"
style="?android:attr/textAppearanceMedium"
android:text="@string/preferences_button_sync_immediately"/>
android:id="@+id/preference_sync_button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="15dip"
android:layout_marginLeft="30dip"
android:layout_marginRight="30dip"
style="?android:attr/textAppearanceMedium"
android:text="@string/preferences_button_sync_immediately"
/>
<!-- 为该Button控件定义一个唯一标识符id从命名推测可能是用于执行偏好设置preference中同步相关操作的按钮 -->
<!-- 设置该Button的宽度占满父容器这里的父容器是外层的LinearLayout使其在水平方向上填满整个父容器空间 -->
<!-- 设置该Button的高度根据其内部文本或图标等内容自适应也就是刚好能包裹住这些内容的高度 -->
<!-- 设置该Button距离其顶部相邻元素的间距为15设备独立像素dip用于调整其在垂直方向上的位置 -->
<!-- 设置该Button距离其左边相邻元素的间距为30设备独立像素dip用于调整其在水平方向上的位置 -->
<!-- 设置该Button距离其右边相邻元素的间距为30设备独立像素dip用于调整其在水平方向上的位置 -->
<!-- 应用安卓系统内置属性(?android:attr/textAppearanceMedium来设置按钮上文本的外观样式例如字体、字号、颜色等属性使其呈现出中等外观的样式 -->
<!-- 设置该Button要显示的文本内容引用名为“preferences_button_sync_immediately”的字符串资源该资源通常在对应的字符串资源文件中定义从命名推测是提示立即同步相关的文字描述 -->
<TextView
android:id="@+id/prefenerece_sync_status_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"/>
</LinearLayout>
android:id="@+id/prefenerece_sync_status_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"
/>
</LinearLayout>
<!-- 为该TextView控件定义一个唯一标识符id从命名推测可能是用于显示偏好设置preference中同步状态相关信息的文本视图 -->
<!-- 设置该TextView的宽度根据其要显示的文本内容自适应也就是刚好能包裹住文本的宽度 -->
<!-- 设置该TextView的高度根据其要显示的文本内容自适应也就是刚好能包裹住文本的高度 -->
<!-- 设置该TextView在其父容器外层的LinearLayout内的对齐方式为居中对齐使其在布局中处于中心位置 -->
<!-- 设置该TextView初始状态为不可见可能在满足某些条件比如同步操作进行中、完成等情况后才会显示出来 -->

@ -35,3 +35,20 @@
android:maxLines="6"
android:lineSpacingMultiplier="1.2" />
</FrameLayout>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式是 utf-8这是 XML 文件开头的标准标识,用于告知解析器按照此版本和编码规则来正确解析该文件 -->
<!-- 设置文本行间距倍数为 1.2,用于增加文本行之间的间距,使文本显示更清晰美观 -->
<!-- 引入安卓的命名空间,后续通过这个命名空间就能使用安卓系统中定义的各种布局属性以及控件相关的属性了 -->
<!-- 设置 FrameLayout帧布局的宽度占满父容器意味着它在水平方向上会填满所在父容器的全部可用空间 -->
<!-- 设置 FrameLayout 的高度占满父容器,也就是在垂直方向上会填满所在父容器的全部可用空间 -->
<!-- 为该 ImageView 控件定义一个唯一标识符id从命名推测可能是用于显示部件widget背景相关图片的视图 -->
<!-- 设置该 ImageView 的宽度占满父容器(这里的父容器是外层的 FrameLayout使其在水平方向上填满整个父容器空间 -->
<!-- 设置该 ImageView 的高度占满父容器,让它在垂直方向上也填满整个父容器空间 -->
<!-- 为该 TextView 控件定义一个唯一标识符id从命名推测可能是用于在部件widget上显示文本内容的视图 -->
<!-- 设置该 TextView 的宽度占满父容器(这里的父容器是外层的 FrameLayout使其在水平方向上填满整个父容器空间 -->
<!-- 设置该 TextView 的高度占满父容器,让它在垂直方向上也填满整个父容器空间 -->
<!-- 设置该 TextView 顶部的内边距为 30 设备独立像素dip也就是在文本内容上方留出一定的空白空间 -->
<!-- 设置该 TextView 左侧的内边距为 8 设备独立像素dip在文本内容左侧留出空白空间 -->
<!-- 设置该 TextView 显示文本的字号大小为 14 可缩放像素sp可根据用户手机系统设置的字体大小进行相应缩放 -->
<!-- 设置该 TextView 显示文本的颜色,这里使用十六进制颜色值 #FF663300 来指定颜色 -->
<!-- 设置该 TextView 最多能显示的行数为 6 行,超出部分可能会被截断或者通过其他方式处理(比如显示省略号等) -->
<!-- 设置文本行间距倍数为 1.2,用于增加文本行之间的间距,使文本显示更清晰美观 -->

@ -1,39 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式是 utf-8这是 XML 文件开头的标准标识,用于告知解析器按照此版本和编码规则来正确解析该文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
此处是版权相关声明,说明代码所属的开源社区是 The MiCode Open Source Community版权时间范围在 2010 - 2011 年。
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
表示此文件遵循 Apache License 2.0 开源协议进行授权许可,意味着若要使用该文件,必须遵守该协议的相关规定。
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过访问网址http://www.apache.org/licenses/LICENSE-2.0)获取 Apache License 2.0 协议的详细内容。
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”的状态进行分发,不会附带额外的保证等情况。
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
强调该代码不存在任何明示或暗示的保证以及相关条件,使用者需自行承担使用代码带来的风险。
See the License for the specific language governing permissions and
limitations under the License.
-->
提示查看协议内容,以了解关于权限及限制方面的详细规定。 -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<!-- 引入安卓的命名空间,后续通过这个命名空间就能使用安卓系统中定义的各种布局属性以及控件相关的属性了 -->
<!-- 设置 FrameLayout帧布局的宽度占满父容器使其在水平方向上填满所在父容器的全部可用空间 -->
<!-- 设置 FrameLayout 的高度占满父容器,让它在垂直方向上也填满所在父容器的全部可用空间 -->
<ImageView
android:id="@+id/widget_bg_image"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
android:id="@+id/widget_bg_image"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
<!-- 为该 ImageView 控件定义一个唯一标识符id从命名推测可能是用于显示部件widget的背景图片的视图 -->
<!-- 设置该 ImageView 的宽度占满父容器(这里的父容器是外层的 FrameLayout使其在水平方向上填满整个父容器空间 -->
<!-- 设置该 ImageView 的高度占满父容器,让它在垂直方向上也填满整个父容器空间 -->
<TextView
android:id="@+id/widget_text"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingTop="33dip"
android:paddingLeft="10dip"
android:paddingRight="10dip"
android:textSize="14sp"
android:textColor="#FF663300"
android:maxLines="14"
android:lineSpacingMultiplier="1.2" />
android:id="@+id/widget_text"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:paddingTop="33dip"
android:paddingLeft="10dip"
android:paddingRight="10dip"
android:textSize="14sp"
android:textColor="#FF663300"
android:maxLines="14"
android:lineSpacingMultiplier="1.2"
/>
</FrameLayout>
<!-- 为该 TextView 控件定义一个唯一标识符id从命名推测可能是用于在部件widget上显示文本内容的视图 -->
<!-- 设置该 TextView 的宽度占满父容器(这里的父容器是外层的 FrameLayout使其在水平方向上填满整个父容器空间 -->
<!-- 设置该 TextView 的高度占满父容器,让它在垂直方向上也填满整个父容器空间 -->
<!-- 设置该 TextView 顶部的内边距为 33 设备独立像素dip也就是在文本内容上方留出一定的空白空间 -->
<!-- 设置该 TextView 左侧的内边距为 10 设备独立像素dip在文本内容左侧留出空白空间 -->
<!-- 设置该 TextView 右侧的内边距为 10 设备独立像素dip在文本内容右侧留出空白空间 -->
<!-- 设置该 TextView 显示文本的颜色,这里使用十六进制颜色值 #FF663300 来指定颜色 -->
<!-- 设置该 TextView 最多能显示的行数为 14 行,超出的文本内容可能会被截断或者通过其他方式处理(比如显示省略号等) -->
<!-- 设置文本行间距倍数为 1.2,用于增加文本行之间的间距,使文本显示更清晰美观 -->

@ -1,48 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式是 utf-8这是 XML 文件开头的标准标识,用于告知解析器按照此版本和编码规则来正确解析该文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:android="http://schemas.android.com/apk/res/android"
>
<!-- 引入安卓的命名空间,后续通过这个命名空间就能使用安卓系统中定义的各种菜单相关的属性 -->
<item
android:id="@+id/menu_delete"
android:title="@string/menu_delete" />
android:id="@+id/menu_delete"
android:title="@string/menu_delete"
/>
<!-- 为该菜单项定义一个唯一标识符id从命名“menu_delete”来看可能是用于执行删除相关操作的菜单项 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_delete”的字符串资源该资源通常在对应的字符串资源文件中定义用于明确提示用户该项的功能与删除有关 -->
<item
android:id="@+id/menu_font_size"
android:title="@string/menu_font_size"/>
android:id="@+id/menu_font_size"
android:title="@string/menu_font_size"
/>
<!-- 为该菜单项定义一个唯一标识符id推测是用于操作字体大小相关设置的菜单项 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_font_size”的字符串资源提示用户点击该项可进行字体大小相关的操作 -->
<item
android:id="@+id/menu_list_mode"
android:title="@string/menu_list_mode" />
android:id="@+id/menu_list_mode"
android:title="@string/menu_list_mode"
/>
<!-- 为该菜单项定义一个唯一标识符id可能是用于切换列表模式相关功能的菜单项 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_list_mode”的字符串资源告知用户该项与列表模式操作有关 -->
<item
android:id="@+id/menu_share"
android:title="@string/menu_share"/>
android:id="@+id/menu_share"
android:title="@string/menu_share"
/>
<!-- 为该菜单项定义一个唯一标识符id显然是用于实现分享功能的菜单项 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_share”的字符串资源让用户明白点击该项可进行分享操作 -->
<item
android:id="@+id/menu_send_to_desktop"
android:title="@string/menu_send_to_desktop"/>
android:id="@+id/menu_send_to_desktop"
android:title="@string/menu_send_to_desktop"
/>
<!-- 为该菜单项定义一个唯一标识符id推测是用于将相关内容发送到桌面等操作的菜单项 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_send_to_desktop”的字符串资源提示用户该项功能与发送到桌面有关 -->
<item
android:id="@+id/menu_alert"
android:title="@string/menu_alert" />
android:id="@+id/menu_alert"
android:title="@string/menu_alert"
/>
<!-- 为该菜单项定义一个唯一标识符id可能是用于设置提醒相关功能的菜单项 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_alert”的字符串资源告知用户点击该项可进行提醒相关操作 -->
<item
android:id="@+id/menu_delete_remind"
android:title="@string/menu_remove_remind" />
android:id="@+id/menu_delete_remind"
android:title="@string/menu_remove_remind"
/>
<!-- 为该菜单项定义一个唯一标识符id从命名推测是用于删除提醒相关操作的菜单项 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_remove_remind”的字符串资源提示用户该项功能与删除提醒有关 -->
</menu>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
此处为版权相关声明,说明代码所属的开源社区是 The MiCode Open Source Community版权时间范围在 2010 - 2011 年。
Licensed under the Apache License, Version 2.0 (the "License");
表示此文件遵循 Apache License 2.0 开源协议进行授权许可,意味着若要使用该文件,必须遵守该协议的相关规定。
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过访问网址http://www.apache.org/licenses/LICENSE-2.0)获取 Apache License 2.0 协议的详细内容。
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”的状态进行分发,不会附带额外的保证等情况。
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
强调该代码不存在任何明示或暗示的保证以及相关条件,使用者需自行承担使用代码带来的风险。
See the License for the specific language governing permissions and
limitations under the License.
提示查看协议内容,以了解关于权限及限制方面的详细规定。 -->

@ -1,23 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式是 utf-8这是 XML 文件开头的标准标识,用于告知解析器按照此版本和编码规则来正确解析该文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:android="http://schemas.android.com/apk/res/android"
>
<item
android:id="@+id/menu_search"
android:title="@string/menu_search" />
android:id="@+id/menu_search"
android:title="@string/menu_search"
/>
</menu>
<!-- 引入安卓的命名空间,后续通过这个命名空间就能使用安卓系统中定义的各种菜单相关的属性 -->
<!-- 为该菜单项定义一个唯一标识符id从命名“menu_search”可以看出这个菜单项大概率是用于触发搜索相关操作的 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_search”的字符串资源这个字符串资源通常在对应的字符串资源文件中定义以此来明确地向用户提示该菜单项的功能是进行搜索 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
此处是版权相关声明,说明代码所属的开源社区是 The MiCode Open Source Community版权时间范围在 2010 - 2011 年。
Licensed under the Apache License, Version 2.0 (the "License");
表示此文件遵循 Apache License 2.0 开源协议进行授权许可,意味着若要使用该文件,必须遵守该协议的相关规定。
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过访问网址http://www.apache.org/licenses/LICENSE-2.0)获取 Apache License 2.0 协议的详细内容。
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”的状态进行分发,不会附带额外的保证等情况。
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
强调该代码不存在任何明示或暗示的保证以及相关条件,使用者需自行承担使用代码带来的风险。
See the License for the specific language governing permissions and
limitations under the License.
提示查看协议内容,以了解关于权限及限制方面的详细规定。 -->

@ -1,52 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式是 utf-8这是 XML 文件开头的标准标识,用于告知解析器按照此版本和编码规则来正确解析该文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:android="http://schemas.android.com/apk/res/android"
>
<!-- 引入安卓的命名空间,后续通过这个命名空间就能使用安卓系统中定义的各种菜单相关属性 -->
<item
android:id="@+id/menu_new_note"
android:title="@string/notelist_menu_new"/>
android:id="@+id/menu_new_note"
android:title="@string/notelist_menu_new"
/>
<!-- 为该菜单项定义一个唯一标识符id从命名“menu_new_note”推测这个菜单项大概率是用于创建新笔记相关操作的功能入口 -->
<!-- 设置该菜单项显示的标题文本引用名为“notelist_menu_new”的字符串资源该字符串资源通常在对应的字符串资源文件中定义用于向用户提示点击此项可进行创建新笔记的操作 -->
<item
android:id="@+id/menu_delete"
android:title="@string/menu_delete"/>
android:id="@+id/menu_delete"
android:title="@string/menu_delete"
/>
<!-- 为该菜单项定义一个唯一标识符id“menu_delete”表明这个菜单项很可能是用于执行删除相关操作的功能入口 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_delete”的字符串资源明确提示用户点击该项可执行删除操作 -->
<item
android:id="@+id/menu_font_size"
android:title="@string/menu_font_size"/>
android:id="@+id/menu_font_size"
android:title="@string/menu_font_size"
/>
<!-- 为该菜单项定义一个唯一标识符id根据“menu_font_size”命名可推测这个菜单项是用于调整字体大小相关设置的功能入口 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_font_size”的字符串资源提示用户点击该项可进行字体大小相关操作 -->
<item
android:id="@+id/menu_list_mode"
android:title="@string/menu_list_mode" />
android:id="@+id/menu_list_mode"
android:title="@string/menu_list_mode"
/>
<!-- 为该菜单项定义一个唯一标识符id从“menu_list_mode”来看这个菜单项可能是用于切换列表显示模式相关操作的功能入口 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_list_mode”的字符串资源告知用户点击该项可进行列表模式相关操作 -->
<item
android:id="@+id/menu_share"
android:title="@string/menu_share"/>
android:id="@+id/menu_share"
android:title="@string/menu_share"
/>
<!-- 为该菜单项定义一个唯一标识符id“menu_share”清晰地表明这个菜单项是用于实现分享功能的功能入口 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_share”的字符串资源让用户明白点击该项可进行分享操作 -->
<item
android:id="@+id/menu_send_to_desktop"
android:title="@string/menu_send_to_desktop"/>
android:id="@+id/menu_send_to_desktop"
android:title="@string/menu_send_to_desktop"
/>
<!-- 为该菜单项定义一个唯一标识符id由“menu_send_to_desktop”推测这个菜单项可能是用于将相关内容发送到桌面等操作的功能入口 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_send_to_desktop”的字符串资源提示用户点击该项可进行发送到桌面相关操作 -->
<item
android:id="@+id/menu_alert"
android:title="@string/menu_alert" />
android:id="@+id/menu_alert"
android:title="@string/menu_alert"
/>
<!-- 为该菜单项定义一个唯一标识符id“menu_alert”表明这个菜单项大概率是用于设置提醒相关功能的功能入口 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_alert”的字符串资源告知用户点击该项可进行提醒相关操作 -->
<item
android:id="@+id/menu_delete_remind"
android:title="@string/menu_remove_remind" />
</menu>
android:id="@+id/menu_delete_remind"
android:title="@string/menu_remove_remind"
/>
</menu>
<!-- 为该菜单项定义一个唯一标识符id从“menu_delete_remind”可推测这个菜单项是用于删除提醒相关操作的功能入口 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_remove_remind”的字符串资源提示用户点击该项可进行删除提醒相关操作 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
这里是版权相关声明,说明代码所属的开源社区是 The MiCode Open Source Community版权时间范围在 2010 - 2011 年。
Licensed under the Apache License, Version 2.0 (the "License");
表示此文件遵循 Apache License 2.0 开源协议进行授权许可,意味着若要使用该文件,必须遵守该协议的相关规定。
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过访问网址http://www.apache.org/licenses/LICENSE-2.0)获取 Apache License 2.0 协议的详细内容。
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”的状态进行分发,不会附带额外的保证等情况。
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
强调该代码不存在任何明示或暗示的保证以及相关条件,使用者需自行承担使用代码带来的风险。
See the License for the specific language governing permissions and
limitations under the License.
提示查看协议内容,以了解关于权限及限制方面的详细规定。 -->

@ -1,39 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式是 utf-8这是 XML 文件开头的标准标识,用于告知解析器按照此版本和编码规则来正确解析该文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:android="http://schemas.android.com/apk/res/android"
>
<!-- 引入安卓的命名空间,后续通过这个命名空间就能使用安卓系统中定义的各种菜单相关的属性 -->
<item
android:id="@+id/menu_new_folder"
android:title="@string/menu_create_folder"/>
android:id="@+id/menu_new_folder"
android:title="@string/menu_create_folder"
/>
<!-- 为该菜单项定义一个唯一标识符id从命名“menu_new_folder”可以推测出这个菜单项大概率是用于创建新文件夹相关操作的功能入口 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_create_folder”的字符串资源该字符串资源通常在对应的字符串资源文件中定义以此来明确地向用户提示点击此项可进行创建新文件夹的操作 -->
<item
android:id="@+id/menu_export_text"
android:title="@string/menu_export_text"/>
android:id="@+id/menu_export_text"
android:title="@string/menu_export_text"
/>
<!-- 为该菜单项定义一个唯一标识符id“menu_export_text”表明这个菜单项很可能是用于执行文本导出相关操作的功能入口 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_export_text”的字符串资源提示用户点击该项可进行文本导出操作 -->
<item
android:id="@+id/menu_sync"
android:title="@string/menu_sync"/>
android:id="@+id/menu_sync"
android:title="@string/menu_sync"
/>
<!-- 为该菜单项定义一个唯一标识符id根据“menu_sync”命名可推测这个菜单项是用于进行数据同步相关操作的功能入口 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_sync”的字符串资源告知用户点击该项可进行同步操作 -->
<item
android:id="@+id/menu_setting"
android:title="@string/menu_setting" />
android:id="@+id/menu_setting"
android:title="@string/menu_setting"
/>
<!-- 为该菜单项定义一个唯一标识符id“menu_setting”清晰地表明这个菜单项是用于打开设置相关界面或执行设置相关操作的功能入口 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_setting”的字符串资源让用户明白点击该项可进入设置相关功能 -->
<item
android:id="@+id/menu_search"
android:title="@string/menu_search"/>
android:id="@+id/menu_search"
android:title="@string/menu_search"
/>
<!-- 为该菜单项定义一个唯一标识符id“menu_search”表明这个菜单项大概率是用于触发搜索相关操作的功能入口 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_search”的字符串资源提示用户点击该项可进行搜索操作 -->
</menu>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
此处是版权相关声明,说明代码所属的开源社区是 The MiCode Open Source Community版权时间范围在 2010 - 2011 年。
Licensed under the Apache License, Version 2.0 (the "License");
表示此文件遵循 Apache License 2.0 开源协议进行授权许可,意味着若要使用该文件,必须遵守该协议的相关规定。
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过访问网址http://www.apache.org/licenses/LICENSE-2.0)获取 Apache License 2.0 协议的详细内容。
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”的状态进行分发,不会附带额外的保证等情况。
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
强调该代码不存在任何明示或暗示的保证以及相关条件,使用者需自行承担使用代码带来的风险。
See the License for the specific language governing permissions and
limitations under the License.
提示查看协议内容,以了解关于权限及限制方面的详细规定。 -->

@ -1,20 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式是 utf-8这是 XML 文件开头的标准标识,用于告知解析器按照此版本和编码规则来正确解析该文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/action_select_all" android:title="@string/menu_select_all" />
</menu>
<item android:id="@+id/action_select_all"
android:title="@string/menu_select_all"
/>
</menu>
<!-- 引入安卓的命名空间,后续通过这个命名空间就能使用安卓系统中定义的各种菜单相关的属性 -->
<!-- 为该菜单项定义一个唯一标识符id从命名“action_select_all”可以推测出这个菜单项大概率是用于执行全选相关操作的功能入口 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_select_all”的字符串资源该字符串资源通常在对应的字符串资源文件中定义以此来明确地向用户提示点击此项可进行全选操作 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
此处是版权相关声明,说明代码所属的开源社区是 The MiCode Open Source Community版权时间范围在 2010 - 2011 年。
Licensed under the Apache License, Version 2.0 (the "License");
表示此文件遵循 Apache License 2.0 开源协议进行授权许可,意味着若要使用该文件,必须遵守该协议的相关规定。
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过访问网址http://www.apache.org/licenses/LICENSE-2.0)获取 Apache License 2.0 协议的详细内容。
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”的状态进行分发,不会附带额外的保证等情况。
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
强调该代码不存在任何明示或暗示的保证以及相关条件,使用者需自行承担使用代码带来的风险。
See the License for the specific language governing permissions and
limitations under the License.
提示查看协议内容,以了解关于权限及限制方面的详细规定。 -->

@ -1,31 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式是 utf-8这是 XML 文件开头的标准标识,用于告知解析器按照此版本和编码规则来正确解析该文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:android="http://schemas.android.com/apk/res/android"
>
<!-- 引入安卓的命名空间,后续通过这个命名空间就能使用安卓系统中定义的各种菜单相关的属性 -->
<item
android:id="@+id/move"
android:title="@string/menu_move"
android:icon="@drawable/menu_move"
android:showAsAction="always|withText" />
android:id="@+id/move"
android:title="@string/menu_move"
android:icon="@drawable/menu_move"
android:showAsAction="always|withText"
/>
<!-- 为该菜单项定义一个唯一标识符id从命名“move”可以推测出这个菜单项大概率是用于执行移动相关操作的功能入口 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_move”的字符串资源该字符串资源通常在对应的字符串资源文件中定义以此来明确地向用户提示点击此项可进行移动操作 -->
<!-- 设置该菜单项要显示的图标引用名为“menu_move”的可绘制资源通常是图片资源用于在界面上直观地展示该菜单项对应的功能方便用户识别 -->
<!-- 设置该菜单项在界面上的显示方式“always”表示总是显示在界面上比如在ActionBar等相关位置“withText”表示同时显示图标和文本内容让用户能更清晰地看到菜单项的功能和名称 -->
<item
android:id="@+id/delete"
android:title="@string/menu_delete"
android:icon="@drawable/menu_delete"
android:showAsAction="always|withText" />
</menu>
android:id="@+id/delete"
android:title="@string/menu_delete"
android:icon="@drawable/menu_delete"
android:showAsAction="always|withText"
/>
<!-- 为该菜单项定义一个唯一标识符id“delete”表明这个菜单项很可能是用于执行删除相关操作的功能入口 -->
<!-- 设置该菜单项显示的标题文本引用名为“menu_delete”的字符串资源提示用户点击该项可进行删除操作 -->
<!-- 设置该菜单项要显示的图标引用名为“menu_delete”的可绘制资源通常是图片资源用于辅助用户直观地识别该菜单项的功能 -->
<!-- 同样设置该菜单项在界面上的显示方式,使其总是显示并且同时展示图标和文本内容 -->
</menu>
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
此处是版权相关声明,说明代码所属的开源社区是 The MiCode Open Source Community版权时间范围在 2010 - 2011 年。
Licensed under the Apache License, Version 2.0 (the "License");
表示此文件遵循 Apache License 2.0 开源协议进行授权许可,意味着若要使用该文件,必须遵守该协议的相关规定。
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过访问网址http://www.apache.org/licenses/LICENSE-2.0)获取 Apache License 2.0 协议的详细内容。
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”的状态进行分发,不会附带额外的保证等情况。
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
强调该代码不存在任何明示或暗示的保证以及相关条件,使用者需自行承担使用代码带来的风险。
See the License for the specific language governing permissions and
limitations under the License.
提示查看协议内容,以了解关于权限及限制方面的详细规定。 -->

@ -1,24 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式是 utf-8这是 XML 文件开头的标准标识,用于告知解析器按照此版本和编码规则来正确解析该文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
此处是版权相关声明,说明代码所属的开源社区是 The MiCode Open Source Community版权时间范围在 2010 - 2011 年。
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
表示此文件遵循 Apache License 2.0 开源协议进行授权许可,意味着若要使用该文件,必须遵守该协议的相关规定。
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过访问网址http://www.apache.org/licenses/LICENSE-2.0)获取 Apache License 2.0 协议的详细内容。
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”的状态进行分发,不会附带额外的保证等情况。
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
强调该代码不存在任何明示或暗示的保证以及相关条件,使用者需自行承担使用代码带来的风险。
See the License for the specific language governing permissions and
limitations under the License.
-->
提示查看协议内容,以了解关于权限及限制方面的详细规定。 -->
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:android="http://schemas.android.com/apk/res/android"
>
<!-- 引入安卓的命名空间,后续通过这个命名空间就能使用安卓系统中定义的各种菜单相关属性 -->
<item
android:id="@+id/menu_new_note"
android:title="@string/notelist_menu_new"/>
</menu>
android:id="@+id/menu_new_note"
android:title="@string/notelist_menu_new"
/>
</menu>
<!-- 为该菜单项定义一个唯一标识符id从命名“menu_new_note”来看这个菜单项大概率是用于触发创建新笔记相关操作的功能入口 -->
<!-- 设置该菜单项显示的标题文本引用名为“notelist_menu_new”的字符串资源该字符串资源通常在对应的字符串资源文件中定义用于向用户提示点击此项可进行创建新笔记的操作 -->

@ -1,23 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式是 utf-8这是 XML 文件开头的标准标识,用于告知解析器按照此版本和编码规则来正确解析该文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
此处是版权相关声明,说明代码所属的开源社区是 The MiCode Open Source Community版权时间范围在 2010 - 2011 年。
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
表示此文件遵循 Apache License 2.0 开源协议进行授权许可,意味着若要使用该文件,必须遵守该协议的相关规定。
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过访问网址http://www.apache.org/licenses/LICENSE-2.0)获取 Apache License 2.0 协议的详细内容。
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”的状态进行分发,不会附带额外的保证等情况。
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
强调该代码不存在任何明示或暗示的保证以及相关条件,使用者需自行承担使用代码带来的风险。
See the License for the specific language governing permissions and
limitations under the License.
-->
提示查看协议内容,以了解关于权限及限制方面的详细规定。 -->
<resources>
<!-- <resources>标签是安卓资源文件中的根标签,用于定义各种类型的资源,比如字符串、数组、颜色等资源都可以放在这里面定义 -->
<string-array name="menu_share_ways">
<!-- 定义一个名为“menu_share_ways”的字符串数组资源从命名来看这个数组大概率是用于存储菜单中分享相关的方式选项内容 -->
<item>短信</item>
<!-- 字符串数组中的一个元素,表示一种分享方式,这里是“短信”,意味着在对应的分享菜单中,用户可以选择通过短信来分享相关内容 -->
<item>邮件</item>
<!-- 字符串数组中的另一个元素,表示另一种分享方式,即用户还可以通过邮件的方式来分享相关内容 -->
</string-array>
</resources>

@ -1,126 +1,263 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式是 utf-8这是 XML 文件开头的标准标识,用于告知解析器按照此版本和编码规则来正确解析该文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
此处是版权相关声明,说明代码所属的开源社区是 The MiCode Open Source Community版权时间范围在 2010 - 2011 年。
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
表示此文件遵循 Apache License 2.0 开源协议进行授权许可,意味着若要使用该文件,必须遵守该协议的相关规定。
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过访问网址http://www.apache.org/licenses/LICENSE-2.0)获取 Apache License 2.0 协议的详细内容。
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”的状态进行分发,不会附带额外的保证等情况。
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
强调该代码不存在任何明示或暗示的保证以及相关条件,使用者需自行承担使用代码带来的风险。
See the License for the specific language governing permissions and
limitations under the License.
-->
提示查看协议内容,以了解关于权限及限制方面的详细规定。 -->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- <resources>是安卓资源文件的根标签用于定义各种资源这里引入了两个命名空间“android”用于安卓相关的资源属性设置等操作“xliff”常用于国际化文本处理相关场景 -->
<string name="app_name">便签</string>
<!-- 定义名为“app_name”的字符串资源此资源大概率用于表示整个应用程序的名称在这里被设置为“便签”表明这是一款与便签功能相关的应用 -->
<string name="app_widget2x2">便签2x2</string>
<!-- 定义名为“app_widget2x2”的字符串资源推测是用于描述应用中尺寸为 2x2 的小部件对应的显示名称方便用户识别和区分不同尺寸的小部件这里显示为“便签2x2” -->
<string name="app_widget4x4">便签4x4</string>
<!-- 定义名为“app_widget4x4”的字符串资源同理应该是用于表示应用里尺寸为 4x4 的小部件的显示名称起到标识该特定尺寸小部件的作用名称为“便签4x4” -->
<string name="widget_havenot_content">没有关联内容,点击新建便签。</string>
<!-- 定义名为“widget_havenot_content”的字符串资源从其内容来看可能是在小部件上显示的提示信息当没有与之关联的便签内容时提示用户点击可以去创建新的便签 -->
<string name="widget_under_visit_mode">访客模式下,便签内容不可见</string>
<!-- 定义名为“widget_under_visit_mode”的字符串资源推测是用于小部件处于访客模式下时向用户展示的提示文本告知用户在此模式下无法查看便签的具体内容 -->
<string name="notelist_string_info">...</string>
<!-- 定义名为“notelist_string_info”的字符串资源仅从“...”不太明确其确切用途,不过可能是在便签列表相关界面中用于展示某种简略信息或者占位性质的文本内容 -->
<string name="notelist_menu_new">新建便签</string>
<!-- 定义名为“notelist_menu_new”的字符串资源用于在便签列表对应的菜单里作为创建新便签这一菜单项所显示的文字内容明确提示用户点击该菜单项可进行添加便签的操作 -->
<string name="delete_remind_time_message">成功删除提醒</string>
<!-- 定义名为“delete_remind_time_message”的字符串资源大概率是在成功删除便签的提醒时间后向用户展示的提示消息告知用户相应的删除提醒操作已经顺利完成 -->
<string name="set_remind_time_message">创建提醒</string>
<!-- 定义名为“set_remind_time_message”的字符串资源应该是在执行设置便签提醒时间相关操作时向用户呈现的提示文本表明当前正在进行设置提醒的动作 -->
<string name="note_alert_expired">已过期</string>
<!-- 定义名为“note_alert_expired”的字符串资源可能是在便签的提醒功能过期时用于显示给用户的提示文字比如在提醒相关界面展示“已过期”字样让用户知晓提醒已过期 -->
<string name="format_date_ymd">yyyyMMdd</string>
<!-- 定义名为“format_date_ymd”的字符串资源这是定义了一种日期格式采用“yyyyMMdd”这种形式常用于对日期数据进行格式化输出比如按照此格式来存储、展示便签相关的日期信息等 -->
<string name="format_datetime_mdhm">MM月dd日 kk:mm</string>
<!-- 定义名为“format_datetime_mdhm”的字符串资源同样是一种日期时间格式的定义按照“MM月dd日 kk:mm”的格式来规范日期时间数据的展示方便在应用中统一处理和显示这类信息 -->
<string name="notealert_ok">知道了</string>
<!-- 定义名为“notealert_ok”的字符串资源推测是在便签相关提醒的操作场景里当用户确认知晓或者完成某个操作后对应的按钮或者提示文本会显示“知道了”表示用户明白了、已确认的意思 -->
<string name="notealert_enter">查看</string>
<!-- 定义名为“notealert_enter”的字符串资源可能是在便签提醒相关的交互中用于引导用户点击去查看具体便签内容的提示文字提示用户点击相应区域可以查看详情 -->
<string name="note_link_tel">呼叫电话</string>
<!-- 定义名为“note_link_tel”的字符串资源大概率是当便签中包含电话号码链接等情况时在相应位置显示的操作提示文本告知用户点击此处可以进行拨打电话的操作 -->
<string name="note_link_email">发送邮件</string>
<!-- 定义名为“note_link_email”的字符串资源应该是在便签里有邮件链接相关内容时显示给用户的提示文字提示用户点击可触发发送邮件的操作 -->
<string name="note_link_web">浏览网页</string>
<!-- 定义名为“note_link_web”的字符串资源可能是在便签中存在网页链接的情况下向用户展示的提示文本告知用户点击该链接可以浏览对应的网页内容 -->
<string name="note_link_other">打开地图</string>
<!-- 定义名为“note_link_other”的字符串资源推测是在便签涉及到地图相关链接等情况时显示的提示文字提示用户点击可以打开地图应用进行相关操作 -->
<!-- note list string -->
<!-- 这是一个注释说明,告知下面的字符串资源是与便签列表相关的一些字符串内容 -->
<string name="menu_create_folder">新建文件夹</string>
<!-- 定义名为“menu_create_folder”的字符串资源用于在应用的菜单中作为创建新文件夹这一菜单项所显示的文字内容清晰地提示用户点击该菜单项可进行新建文件夹的操作 -->
<string name="menu_export_text">导出文本</string>
<!-- 定义名为“menu_export_text”的字符串资源用于在菜单里作为导出文本这个菜单项显示的文本内容告知用户点击该菜单项就能执行文本导出的相关操作 -->
<string name="menu_sync">同步</string>
<!-- 定义名为“menu_sync”的字符串资源用于在菜单中作为同步相关操作的菜单项所显示的文字内容提示用户点击它可以发起数据同步的操作 -->
<string name="menu_sync_cancel">取消同步</string>
<!-- 定义名为“menu_sync_cancel”的字符串资源用于在正在进行同步操作的过程中在菜单里作为取消同步这一菜单项显示的文本内容方便用户点击来终止同步操作 -->
<string name="menu_setting">设置</string>
<!-- 定义名为“menu_setting”的字符串资源用于在菜单中作为进入设置界面或者执行设置相关操作的菜单项所显示的文字内容引导用户点击该菜单项以进入设置相关功能区域 -->
<string name="menu_search">搜索</string>
<!-- 定义名为“menu_search”的字符串资源用于在菜单中作为搜索相关操作的菜单项显示的文本内容提示用户点击该菜单项可以进行搜索相关的操作比如查找便签内容等 -->
<string name="menu_delete">删除</string>
<!-- 定义名为“menu_delete”的字符串资源用于在菜单中作为删除相关操作的菜单项显示的文本内容告知用户点击该菜单项可以执行删除相应内容的操作例如删除便签、文件夹等 -->
<string name="menu_move">移动到文件夹</string>
<!-- 定义名为“menu_move”的字符串资源用于在菜单中作为将相关内容移动到文件夹这一菜单项显示的文本内容提示用户点击该菜单项可进行把某些内容移动到指定文件夹的操作 -->
<string name="menu_select_title">选中了 %d 项</string>
<!-- 定义名为“menu_select_title”的字符串资源从格式“选中了 %d 项”来看,它是用于在有多个项目被选中后,展示选中数量相关提示信息的文本,会根据实际选中的项目数量替换“%d”来准确告知用户选中情况 -->
<string name="menu_select_none">没有选中项,操作无效</string>
<!-- 定义名为“menu_select_none”的字符串资源用于在没有任何项目被选中的情况下向用户显示的提示文本告知用户由于没有选中内容当前要执行的操作是无效的 -->
<string name="menu_select_all">全选</string>
<!-- 定义名为“menu_select_all”的字符串资源用于在菜单中作为全选相关操作的菜单项显示的文本内容提示用户点击该菜单项可以一次性选中所有相关的项目 -->
<string name="menu_deselect_all">取消全选</string>
<!-- 定义名为“menu_deselect_all”的字符串资源用于在菜单中作为取消全选相关操作的菜单项显示的文本内容提示用户点击该菜单项可以将已经全选的状态取消即取消选中所有项目 -->
<string name="menu_font_size">文字大小</string>
<!-- 定义名为“menu_font_size”的字符串资源用于在菜单中作为调整文字大小相关操作的菜单项显示的文本内容引导用户点击该菜单项来进行文字大小相关的设置操作 -->
<string name="menu_font_small"></string>
<!-- 定义名为“menu_font_small”的字符串资源可能是在文字大小设置的相关菜单选项里作为表示小字号这一选项所显示的文字内容便于用户选择小字体来显示文本 -->
<string name="menu_font_normal">正常</string>
<!-- 定义名为“menu_font_normal”的字符串资源推测是在文字大小设置相关的菜单中作为表示正常字号这一选项显示的文字内容方便用户将文本字体设置为正常大小 -->
<string name="menu_font_large"></string>
<!-- 定义名为“menu_font_large”的字符串资源应该是在文字大小设置相关的菜单里作为表示大字号这一选项所展示的文字内容让用户可以选择大字体来显示文本 -->
<string name="menu_font_super">超大</string>
<!-- 定义名为“menu_font_super”的字符串资源可能是在文字大小设置相关的菜单中作为表示超大字号这一选项显示的文字内容供用户选择超大字体来展示文本 -->
<string name="menu_list_mode">进入清单模式</string>
<!-- 定义名为“menu_list_mode”的字符串资源用于在菜单中作为进入清单模式比如待办事项清单等类似的模式相关操作的菜单项显示的文本内容提示用户点击该菜单项可进入对应的清单模式方便对相关内容进行清单式管理 -->
<string name="menu_normal_mode">退出清单模式</string>
<!-- 定义名为“menu_normal_mode”的字符串资源用于在菜单中作为离开清单模式例如待办事项清单等相关操作的菜单项显示的文本内容提示用户点击该菜单项可退出当前所处的清单模式恢复到常规显示状态 -->
<string name="menu_folder_view">查看文件夹</string>
<!-- 定义名为“menu_folder_view”的字符串资源用于在菜单里作为查看文件夹相关操作的菜单项所显示的文本内容告知用户点击该菜单项后可以查看指定文件夹内的内容比如查看文件夹里包含的便签等信息 -->
<string name="menu_folder_delete">刪除文件夹</string>
<!-- 定义名为“menu_folder_delete”的字符串资源用于在菜单中作为删除文件夹相关操作的菜单项显示的文本内容提示用户点击该菜单项会执行删除文件夹的操作通常可能还会有相应确认提示以防误删 -->
<string name="menu_folder_change_name">修改文件夹名称</string>
<!-- 定义名为“menu_folder_change_name”的字符串资源用于在菜单中作为更改文件夹名称相关操作的菜单项显示的文本内容引导用户点击该菜单项来对已有的文件夹重新命名使其名称更符合需求或便于识别 -->
<string name="folder_exist">文件夹 %1$s 已存在,请重新命名</string>
<!-- 定义名为“folder_exist”的字符串资源从格式“文件夹 %1$s 已存在,请重新命名”来看,它是一种带有占位符(%1$s的提示文本通常在创建文件夹时如果要创建的文件夹名称已存在就会用实际的文件夹名称替换占位符显示该提示信息告知用户需要重命名后再进行操作 -->
<string name="menu_share">分享</string>
<!-- 定义名为“menu_share”的字符串资源用于在菜单中作为分享相关操作的菜单项显示的文本内容提示用户点击该菜单项可进行分享操作比如分享便签内容到其他应用或者平台等 -->
<string name="menu_send_to_desktop">发送到桌面</string>
<!-- 定义名为“menu_send_to_desktop”的字符串资源用于在菜单中作为将相关内容发送到桌面例如创建桌面快捷方式等相关操作的菜单项显示的文本内容告知用户点击该菜单项可把对应的内容发送到桌面方便快速访问 -->
<string name="menu_alert">提醒我</string>
<!-- 定义名为“menu_alert”的字符串资源用于在菜单中作为设置提醒相关操作的菜单项显示的文本内容提示用户点击该菜单项可进行提醒相关设置比如设置某个便签的提醒时间等 -->
<string name="menu_remove_remind">删除提醒</string>
<!-- 定义名为“menu_remove_remind”的字符串资源用于在菜单中作为删除提醒相关操作的菜单项显示的文本内容提示用户点击该菜单项可删除已设置好的提醒取消之前设定的提醒功能 -->
<string name="menu_title_select_folder">选择文件夹</string>
<!-- 定义名为“menu_title_select_folder”的字符串资源用于在执行选择文件夹相关操作时作为提示用户当前操作内容的文本比如在弹出的选择文件夹界面上方显示该文字让用户明确知晓正在进行选择文件夹的动作 -->
<string name="menu_move_parent_folder">上一级文件夹</string>
<!-- 定义名为“menu_move_parent_folder”的字符串资源通常用于在涉及将内容移动到当前所在文件夹的上一级文件夹的操作场景中作为提示用户可进行此操作的文本告知用户点击相应按钮或选项可将文件、便签等内容移动到上一级文件夹中 -->
<string name="info_note_enter_desktop">已添加到桌面</string>
<!-- 定义名为“info_note_enter_desktop”的字符串资源一般是在便签成功添加到桌面例如创建了便签对应的桌面快捷方式向用户显示的提示信息告知用户对应的便签已经添加到桌面了方便用户知晓操作结果 -->
<string name="alert_title_delete">删除</string>
<!-- 定义名为“alert_title_delete”的字符串资源用于在弹出与删除操作相关的提示框时作为提示框的标题文本简洁明了地告知用户当前操作是关于删除相关内容让用户快速了解提示框所关联的操作性质 -->
<string name="alert_message_delete_notes">确认要删除所选的 %d 条便签吗?</string>
<!-- 定义名为“alert_message_delete_notes”的字符串资源从格式“确认要删除所选的 %d 条便签吗?”来看,它带有一个占位符“%d”在用户执行删除多条便签的操作时会根据实际选中要删除的便签数量替换该占位符用于弹出提示框询问用户是否确认执行删除操作以避免误删 -->
<string name="alert_message_delete_note">确认要删除该条便签吗?</string>
<!-- 定义名为“alert_message_delete_note”的字符串资源用于在用户尝试删除单条便签时弹出提示框显示的询问信息询问用户是否确实要删除当前这一条便签获取用户的明确操作意向避免误操作导致便签丢失 -->
<string name="alert_message_delete_folder">确认删除文件夹及所包含的便签吗?</string>
<!-- 定义名为“alert_message_delete_folder”的字符串资源在用户进行删除文件夹操作时该文件夹下通常包含有便签等内容弹出提示框显示的询问文本询问用户是否确认执行删除该文件夹以及里面所有便签的操作需要用户明确回应以保障数据安全 -->
<string name="format_move_notes_to_folder">已将所选 %1$d 条便签移到 %2$s 文件夹</string>
<!-- 定义名为“format_move_notes_to_folder”的字符串资源从格式来看它带有两个占位符%1$d 和 %2$s在成功将选中的便签移动到指定文件夹后会用实际移动便签的数量替换“%1$d”用具体的文件夹名称替换“%2$s”用于显示提示信息告知用户移动操作的具体情况让用户清楚知晓便签的移动结果 -->
<!-- export text -->
<!-- 这是一个注释说明,告知下面的字符串资源是与文本导出相关的一些提示信息 -->
<string name="error_sdcard_unmounted">SD卡被占用不能操作</string>
<!-- 定义名为“error_sdcard_unmounted”的字符串资源当 SD 卡处于被其他程序占用等忙碌状态,导致当前应用无法对其进行操作(例如导出文本到 SD 卡等操作)时,显示该提示信息,告知用户当前 SD 卡不可用,无法进行相应操作 -->
<string name="error_sdcard_export">导出文本时发生错误请检查SD卡</string>
<!-- 定义名为“error_sdcard_export”的字符串资源在执行导出文本到 SD 卡的操作过程中,如果出现错误(可能是 SD 卡本身故障、空间不足等原因),就会显示此提示信息,提示用户去检查 SD 卡的相关情况,以便排查问题所在 -->
<string name="error_note_not_exist">要查看的便签不存在</string>
<!-- 定义名为“error_note_not_exist”的字符串资源当用户尝试查看某个便签但该便签在系统中实际并不存在例如被误删除或者从未创建等情况显示该提示信息告知用户想要查看的便签找不到了操作无法进行 -->
<string name="error_note_empty_for_clock">不能为空便签设置闹钟提醒</string>
<!-- 定义名为“error_note_empty_for_clock”的字符串资源用于在用户试图对没有任何内容的空便签设置闹钟提醒比如设置提醒时间等与时间提醒相关操作显示的提示信息告知用户不能在空白便签上进行此类操作需要先添加便签内容等 -->
<string name="error_note_empty_for_send_to_desktop">不能将空便签发送到桌面</string>
<!-- 定义名为“error_note_empty_for_send_to_desktop”的字符串资源在用户尝试把内容为空的便签发送到桌面例如创建桌面快捷方式显示该提示信息告知用户不可以对空便签执行此操作可能需要先完善便签内容后再进行发送 -->
<string name="success_sdcard_export">导出成功</string>
<!-- 定义名为“success_sdcard_export”的字符串资源在文本成功从应用导出到 SD 卡后,用于显示的提示信息,告知用户导出操作已经顺利完成,让用户知晓操作结果 -->
<string name="failed_sdcard_export">导出失败</string>
<!-- 定义名为“failed_sdcard_export”的字符串资源当文本导出到 SD 卡的操作未能成功完成时,显示该提示信息,简单告知用户导出失败了,不过通常还需要结合其他相关错误提示进一步排查具体原因 -->
<string name="format_exported_file_location">已将文本文件(%1$s)输出至SD卡(%2$s)目录</string>
<!-- 定义名为“format_exported_file_location”的字符串资源从格式来看它带有两个占位符%1$s 和 %2$s在需要告知用户文本导出文件在 SD 卡中的具体存放位置时,会用实际的文件名替换“%1$s”用 SD 卡的具体目录路径替换“%2$s”清晰展示导出文件的所在位置情况 -->
<!-- Sync -->
<!-- 这是一个注释说明告知下面的字符串资源是与便签同步Sync相关的一些提示信息 -->
<string name="ticker_syncing">同步便签...</string>
<!-- 定义名为“ticker_syncing”的字符串资源在便签同步操作开始进行时用于显示的提示信息告知用户当前正在同步便签一般可能会以滚动提示条文字等形式展示让用户知晓操作正在进行中 -->
<string name="ticker_success">同步成功</string>
<!-- 定义名为“ticker_success”的字符串资源在便签同步操作顺利完成后用于显示的提示信息告知用户同步已经成功让用户了解操作结果 -->
<string name="ticker_fail">同步失败</string>
<!-- 定义名为“ticker_fail”的字符串资源当便签同步操作未能成功出现错误时用于显示的提示信息告知用户同步失败了通常还需要结合其他相关错误提示进一步排查具体原因 -->
<string name="ticker_cancel">同步已取消</string>
<!-- 定义名为“ticker_cancel”的字符串资源在便签同步操作被取消比如用户手动点击取消按钮或者因程序异常等情况自动取消用于显示的提示信息告知用户同步操作已经被取消让用户知晓当前状态变化 -->
<string name="success_sync_account">与%1$s同步成功</string>
<!-- 定义名为“success_sync_account”的字符串资源从格式“与%1$s同步成功”来看它带有一个占位符“%1$s”在使用特定账户成功完成便签同步操作后会用实际的账户名称替换该占位符告知用户是与哪个账户同步成功了明确同步相关情况 -->
<string name="error_sync_network">同步失败,请检查网络和帐号设置</string>
<!-- 定义名为“error_sync_network”的字符串资源当便签同步操作失败且原因可能与网络连接不稳定或者账号设置有误例如账号密码错误、账号未授权等情况相关时显示该提示信息提示用户去检查网络以及账号设置情况以排查同步失败的问题所在 -->
<string name="error_sync_internal">同步失败,发生内部错误</string>
<!-- 定义名为“error_sync_internal”的字符串资源在便签同步操作因应用自身内部出现问题比如代码逻辑错误、与服务器交互出现异常等情况导致失败时显示该提示信息告知用户同步失败是由于内部发生了错误不过具体原因可能还需要进一步深入排查分析 -->
<string name="error_sync_cancelled">同步已取消</string>
<!-- 定义名为“error_sync_cancelled”的字符串资源在同步操作被取消例如因系统资源不足等原因导致程序自动取消同步用于显示的提示信息告知用户同步操作已被取消与前面手动取消同步操作后显示的提示信息类似只是触发取消的原因有所不同 -->
<string name="sync_progress_login">登录%1$s...</string>
<!-- 定义名为“sync_progress_login”的字符串资源从格式“登录%1$s...”来看,它带有一个占位符“%1$s”在便签同步操作过程中当正在登录相关同步账户例如登录到云端服务账户用于同步便签会用实际的账户名称替换该占位符告知用户正在登录哪个账户让用户了解同步操作的进展情况 -->
<string name="sync_progress_init_list">正在获取服务器便签列表...</string>
<!-- 定义名为“sync_progress_init_list”的字符串资源在便签同步操作里当正在从服务器获取便签列表比如从云端存储获取便签记录以便和本地便签进行对比同步用于显示的提示信息告知用户当前正在进行获取服务器便签列表的操作体现同步操作的进度状态 -->
<string name="sync_progress_syncing">正在同步本地便签...</string>
<!-- 定义名为“sync_progress_syncing”的字符串资源在便签同步操作进行到将本地便签与服务器端可能是与谷歌相关服务等如 google task的便签进行同步的实际阶段时用于显示的提示信息告知用户当前正在进行具体的便签同步操作步骤让用户知晓同步进展 -->
<!-- Preferences -->
<!-- 这是一个注释说明告知下面的字符串资源是与应用偏好设置Preferences相关的一些提示信息 -->
<string name="preferences_title">设置</string>
<!-- 定义名为“preferences_title”的字符串资源用于在应用的偏好设置界面或者相关菜单中作为显示标题的文本内容告知用户当前进入的是设置相关的功能区域方便用户识别和操作 -->
<string name="preferences_account_title">同步账号</string>
<!-- 定义名为“preferences_account_title”的字符串资源用于在偏好设置中与同步账号相关的设置区域作为显示标题的文本内容提示用户该部分设置是针对同步账号进行操作的比如选择、更改、添加或删除同步账号等 -->
<string name="preferences_account_summary">与google task同步便签记录</string>
<!-- 定义名为“preferences_account_summary”的字符串资源用于在同步账号相关设置区域作为对该设置功能的简要说明文本内容告知用户在这里可以将便签记录与谷歌任务google task进行同步让用户了解该设置选项的作用 -->
<string name="preferences_last_sync_time">上次同步于 %1$s</string>
<!-- 定义名为“preferences_last_sync_time”的字符串资源从格式“上次同步于 %1$s”来看它带有一个占位符“%1$s”通常用于在显示上次便签同步操作的具体时间信息时会用实际的时间数据按照一定格式存储的时间字符串替换该占位符准确告知用户上次同步是什么时候进行的 -->
<string name="preferences_add_account">添加账号</string>
<!-- 定义名为“preferences_add_account”的字符串资源用于在偏好设置中作为添加账户比如添加用于同步便签的新账户相关操作的菜单项或按钮显示的文本内容提示用户点击可进行添加账户的操作 -->
<string name="preferences_menu_change_account">更换账号</string>
<!-- 定义名为“preferences_menu_change_account”的字符串资源用于在偏好设置的菜单中作为更改同步账号相关操作的菜单项显示的文本内容提示用户点击该菜单项可进行更改当前同步账号的操作 -->
<string name="preferences_menu_remove_account">删除账号</string>
<!-- 定义名为“preferences_menu_remove_account”的字符串资源用于在偏好设置的菜单中作为移除同步账号相关操作的菜单项显示的文本内容提示用户点击该菜单项可执行移除已有的同步账号的操作 -->
<string name="preferences_menu_cancel">取消</string>
<!-- 定义名为“preferences_menu_cancel”的字符串资源用于在偏好设置相关操作过程中比如正在添加、更改、移除账户等操作时作为取消当前操作的菜单项或按钮显示的文本内容提示用户点击可取消正在进行的操作回到之前的状态 -->
<string name="preferences_button_sync_immediately">立即同步</string>
<!-- 定义名为“preferences_button_sync_immediately”的字符串资源用于在偏好设置界面或者相关区域作为立即进行同步操作的按钮显示的文本内容提示用户点击该按钮可马上发起便签同步操作无需等待其他定时同步等情况 -->
<string name="preferences_button_sync_cancel">取消同步</string>
<!-- 定义名为“preferences_button_sync_cancel”的字符串资源用于在正在进行同步操作时在偏好设置界面或者相关区域作为取消同步操作的按钮显示的文本内容提示用户点击该按钮可终止当前正在进行的同步操作 -->
<string name="preferences_dialog_change_account_title">当前帐号 %1$s</string>
<!-- 定义名为“preferences_dialog_change_account_title”的字符串资源从格式“当前帐号 %1$s”来看它带有一个占位符“%1$s”在弹出更改账号相关的对话框时会用实际的当前账号名称替换该占位符作为对话框的标题显示明确告知用户当前正在操作的是哪个账号 -->
<string name="preferences_dialog_change_account_warn_msg">如更换同步帐号,过去的帐号同步信息将被清空,再次切换的同时可能会造成数据重复</string>
<!-- 定义名为“preferences_dialog_change_account_warn_msg”的字符串资源用于在弹出更改账号相关对话框时作为显示警告信息的文本内容告知用户如果更换同步账号那么之前该账号相关的同步信息都会被删除并且在后续再次切换账号等操作时有可能会出现数据重复的情况提醒用户谨慎操作 -->
<string name="preferences_dialog_select_account_title">同步便签</string>
<!-- 定义名为“preferences_dialog_select_account_title”的字符串资源用于在弹出选择账号用于同步便签相关的对话框时作为对话框的标题显示的文本内容告知用户当前对话框是用于选择同步便签的账号让用户明确操作意图 -->
<string name="preferences_dialog_select_account_tips">请选择google帐号便签将与该帐号的google task内容同步。</string>
<!-- 定义名为“preferences_dialog_select_account_tips”的字符串资源用于在弹出选择账号相关对话框时作为提示信息的文本内容告知用户需要选择一个谷歌账号并且提示用户选择该账号后本地便签将会与该账号对应的 google task 内容进行同步 -->
<string name="preferences_toast_cannot_change_account">正在同步中,不能修改同步帐号</string>
<!-- 定义名为“preferences_toast_cannot_change_account”的字符串资源用于在便签正在进行同步操作的过程中当用户尝试更改同步账号时以弹出提示框Toast的形式显示该提示信息告知用户由于同步正在进行此时不允许修改同步账号需等待同步完成后再操作 -->
<string name="preferences_toast_success_set_accout">同步帐号已设置为%1$s</string>
<!-- 定义名为“preferences_toast_success_set_accout”的字符串资源从格式“同步帐号已设置为%1$s”来看它带有一个占位符“%1$s”在成功将某个账号设置为同步账号后会用实际的账号名称替换该占位符以弹出提示框Toast的形式告知用户同步账号已经设置成功以及具体设置的是哪个账号 -->
<string name="preferences_bg_random_appear_title">新建便签背景颜色随机</string>
<!-- 定义名为“preferences_bg_random_appear_title”的字符串资源用于在应用中与新建便签相关的设置区域作为一个功能标题显示的文本内容提示用户可以设置新建便签时其背景颜色是否随机出现让用户了解该设置选项的作用 -->
<string name="button_delete">删除</string>
<!-- 定义名为“button_delete”的字符串资源通常用于在界面上作为按钮显示的文本内容提示用户点击该按钮可执行删除相关的操作比如删除某个便签、文件夹或者其他数据项等 -->
<string name="call_record_folder_name">通话便签</string>
<!-- 定义名为“call_record_folder_name”的字符串资源推测是用于表示存放与通话相关记录的便签所在的文件夹名称方便在应用中对这类特定内容的便签进行归类和管理使其更易于识别和查找 -->
<string name="hint_foler_name">请输入名称</string>
<string name="search_label">正在搜索便签</string>
<string name="search_hint">搜索便签</string>
<string name="search_setting_description">便签中的文字</string>
<string name="search">便签</string>
<string name="datetime_dialog_ok">设置</string>
<string name="datetime_dialog_cancel">取消</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>
</resources>
<!-- 定义名为“hint_foler_name”的字符串资源大概率是用于在需要用户输入文件夹名称的场景下作为提示信息显示给用户告知用户在此处输入相应的

@ -1,23 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式是 utf-8这是 XML 文件开头的标准标识,用于告知解析器按照此版本和编码规则来正确解析该文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
此处是版权相关声明,说明代码所属的开源社区是 The MiCode Open Source Community版权时间范围在 2010 - 2011 年。
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
表示此文件遵循 Apache License 2.0 开源协议进行授权许可,意味着若要使用该文件,必须遵守该协议的相关规定。
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过访问网址http://www.apache.org/licenses/LICENSE-2.0)获取 Apache License 2.0 协议的详细内容。
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”的状态进行分发,不会附带额外的保证等情况。
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
强调该代码不存在任何明示或暗示的保证以及相关条件,使用者需自行承担使用代码带来的风险。
See the License for the specific language governing permissions and
limitations under the License.
-->
提示查看协议内容,以了解关于权限及限制方面的详细规定。 -->
<resources>
<!-- <resources> 是安卓资源文件中的根标签,用于集中定义各类资源,像字符串、颜色、数组等不同类型的资源都可以在这个标签内部进行定义 -->
<string-array name="menu_share_ways">
<!-- 定义一个名为“menu_share_ways”的字符串数组资源从名字可以推测这个数组是用来存放菜单里分享功能相关的可选方式的 -->
<item>短信</item>
<!-- 这是字符串数组中的一个元素,表示一种分享途径,即用户可以通过发送短信的方式来分享相关内容 -->
<item>郵件</item>
<!-- 同样是字符串数组中的元素,此处应该是“邮件”的意思(可能是书写上稍有误,正确写法通常为“邮件”),意味着用户也能够选择通过发送邮件这种方式来分享相关内容 -->
</string-array>
</resources>

@ -1,127 +1,285 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式是 utf-8这是 XML 文件开头的标准标识,用于告知解析器按照此版本和编码规则来正确解析该文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
此处是版权相关声明,说明代码所属的开源社区是 The MiCode Open Source Community版权时间范围在 2010 - 2011 年。
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
表示此文件遵循 Apache License 2.0 开源协议进行授权许可,意味着若要使用该文件,必须遵守该协议的相关规定。
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过访问网址http://www.apache.org/licenses/LICENSE-2.0)获取 Apache License 2.0 协议的详细内容。
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”的状态进行分发,不会附带额外的保证等情况。
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
强调该代码不存在任何明示或暗示的保证以及相关条件,使用者需自行承担使用代码带来的风险。
See the License for the specific language governing permissions and
limitations under the License.
-->
提示查看协议内容,以了解关于权限及限制方面的详细规定。 -->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- <resources>标签是安卓资源文件的根标签用于定义各种资源这里引入了两个命名空间“android”用于安卓相关资源属性设置等操作“xliff”常用于国际化文本处理相关场景 -->
<string name="app_name">便簽</string>
<!-- 定义名为“app_name”的字符串资源此资源用于表示整个应用程序的名称在这里被设置为“便簽”表明这是一款与便签功能相关的应用 -->
<string name="app_widget2x2">便簽2x2</string>
<string name="app_widget4x4">便簽4x4</string>
<!-- 定义名为“app_widget2x2”的字符串资源推测是用于描述应用中尺寸为 2x2 的小部件对应的显示名称方便用户识别和区分不同尺寸的小部件这里显示为“便簽2x2” -->
<string name="app_widget4x2">便簽4x4</string>
<!-- 定义名为“app_widget4x4”的字符串资源同理应该是用于表示应用里尺寸为 4x4 的小部件的显示名称起到标识该特定尺寸小部件的作用名称为“便簽4x4” -->
<string name="widget_havenot_content">沒有關聯內容,點擊新建便簽。</string>
<!-- 定义名为“widget_havenot_content”的字符串资源从其内容来看可能是在小部件上显示的提示信息当没有与之关联的便签内容时提示用户点击可以去创建新的便签 -->
<string name="widget_under_visit_mode">訪客模式下,便籤內容不可見</string>
<!-- 定义名为“widget_under_visit_mode”的字符串资源推测是用于小部件处于访客模式下时向用户展示的提示文本告知用户在此模式下无法查看便签的具体内容 -->
<string name="notelist_string_info">...</string>
<!-- 定义名为“notelist_string_info”的字符串资源仅从“...”不太明确其确切用途,不过可能是在便签列表相关界面中用于展示某种简略信息或者占位性质的文本内容 -->
<string name="notelist_menu_new">新建便簽</string>
<!-- 定义名为“notelist_menu_new”的字符串资源用于在便签列表对应的菜单里作为创建新便签这一菜单项所显示的文字内容明确提示用户点击该菜单项可进行添加便签的操作 -->
<string name="delete_remind_time_message">成功刪除提醒</string>
<!-- 定义名为“delete_remind_time_message”的字符串资源大概率是在成功删除便签的提醒时间后向用户展示的提示消息告知用户相应的删除提醒操作已经顺利完成 -->
<string name="set_remind_time_message">創建提醒</string>
<!-- 定义名为“set_remind_time_message”的字符串资源应该是在执行设置便签提醒时间相关操作时向用户呈现的提示文本表明当前正在进行设置提醒的动作 -->
<string name="note_alert_expired">已過期</string>
<!-- 定义名为“note_alert_expired”的字符串资源可能是在便签的提醒功能过期时用于显示给用户的提示文字比如在提醒相关界面展示“已過期”字样让用户知晓提醒已过期 -->
<string name="format_date_ymd">yyyyMMdd</string>
<!-- 定义名为“format_date_ymd”的字符串资源这是定义了一种日期格式采用“yyyyMMdd”这种形式常用于对日期数据进行格式化输出比如按照此格式来存储、展示便签相关的日期信息等 -->
<string name="format_datetime_mdhm">MM月dd日 kk:mm</string>
<!-- 定义名为“format_datetime_mdhm”的字符串资源同样是一种日期时间格式的定义按照“MM月dd日 kk:mm”的格式来规范日期时间数据的展示方便在应用中统一处理和显示这类信息 -->
<string name="notealert_ok">知道了</string>
<!-- 定义名为“notealert_ok”的字符串资源推测是在便签相关提醒的操作场景里当用户确认知晓或者完成某个操作后对应的按钮或者提示文本会显示“知道了”表示用户明白了、已确认的意思 -->
<string name="notealert_enter">查看</string>
<!-- 定义名为“notealert_enter”的字符串资源可能是在便签提醒相关的交互中用于引导用户点击去查看具体便签内容的提示文字提示用户点击相应区域可以查看详情 -->
<string name="note_link_tel">呼叫電話</string>
<!-- 定义名为“note_link_tel”的字符串资源大概率是当便签中包含电话号码链接等情况时在相应位置显示的操作提示文本告知用户点击此处可以进行拨打电话的操作 -->
<string name="note_link_email">發送郵件</string>
<!-- 定义名为“note_link_email”的字符串资源应该是在便签里有邮件链接相关内容时显示给用户的提示文字提示用户点击可触发发送邮件的操作 -->
<string name="note_link_web">浏覽網頁</string>
<!-- 定义名为“note_link_web”的字符串资源可能是在便签中存在网页链接的情况下向用户展示的提示文本告知用户点击该链接可以浏览对应的网页内容 -->
<string name="note_link_other">打開地圖</string>
<!-- 定义名为“note_link_other”的字符串资源推测是在便签涉及到地图相关链接等情况时显示的提示文字提示用户点击可以打开地图应用进行相关操作 -->
<string name="format_move_notes_to_folder">已將所選 %1$d 便籤移到 %2$s 文件夾</string>
<!-- 定义名为“format_move_notes_to_folder”的字符串资源从格式来看它带有两个占位符%1$d 和 %2$s在成功将选中的便签移动到指定文件夹后会用实际移动便签的数量替换“%1$d”用具体的文件夹名称替换“%2$s”用于显示提示信息告知用户移动操作的具体情况让用户清楚知晓便签的移动结果 -->
<!-- note list string -->
<!-- 这是一个注释说明,告知下面的字符串资源是与便签列表相关的一些字符串内容 -->
<string name="menu_create_folder">新建文件夾</string>
<!-- 定义名为“menu_create_folder”的字符串资源用于在应用的菜单中作为创建新文件夹这一菜单项所显示的文字内容清晰地提示用户点击该菜单项可进行新建文件夹的操作 -->
<string name="menu_export_text">導出文本</string>
<!-- 定义名为“menu_export_text”的字符串资源用于在菜单里作为导出文本这个菜单项显示的文本内容告知用户点击该菜单项就能执行文本导出的相关操作 -->
<string name="menu_sync">同步</string>
<!-- 定义名为“menu_sync”的字符串资源用于在菜单中作为同步相关操作的菜单项所显示的文字内容提示用户点击它可以发起数据同步的操作 -->
<string name="menu_sync_cancel">取消同步</string>
<!-- 定义名为“menu_sync_cancel”的字符串资源用于在正在进行同步操作的过程中在菜单里作为取消同步这一菜单项显示的文本内容方便用户点击来终止同步操作 -->
<string name="menu_setting">設置</string>
<!-- 定义名为“menu_setting”的字符串资源用于在菜单中作为进入设置界面或者执行设置相关操作的菜单项所显示的文字内容引导用户点击该菜单项以进入设置相关功能区域 -->
<string name="menu_search">搜尋</string>
<!-- 定义名为“menu_search”的字符串资源用于在菜单中作为搜索相关操作的菜单项显示的文本内容提示用户点击该菜单项可以进行搜索相关的操作比如查找便签内容等 -->
<string name="menu_delete">刪除</string>
<!-- 定义名为“menu_delete”的字符串资源用于在菜单中作为删除相关操作的菜单项显示的文本内容告知用户点击该菜单项可以执行删除相应内容的操作例如删除便签、文件夹等 -->
<string name="menu_move">移動到文件夾</string>
<!-- 定义名为“menu_move”的字符串资源用于在菜单中作为将相关内容移动到文件夹这一菜单项显示的文本内容提示用户点击该菜单项可进行把某些内容移动到指定文件夹的操作 -->
<string name="menu_select_title">選中了 %d 項</string>
<!-- 定义名为“menu_select_title”的字符串资源从格式“選中了 %d 項”来看,它是用于在有多个项目被选中后,展示选中数量相关提示信息的文本,会根据实际选中的项目数量替换“%d”来准确告知用户选中情况 -->
<string name="menu_select_none">沒有選中項,操作無效</string>
<!-- 定义名为“menu_select_none”的字符串资源用于在没有任何项目被选中的情况下向用户显示的提示文本告知用户由于没有选中内容当前要执行的操作是无效的 -->
<string name="menu_select_all">全選</string>
<!-- 定义名为“menu_select_all”的字符串资源用于在菜单中作为全选相关操作的菜单项显示的文本内容提示用户点击该菜单项可以一次性选中所有相关的项目 -->
<string name="menu_deselect_all">取消全選</string>
<!-- 定义名为“menu_deselect_all”的字符串资源用于在菜单中作为取消全选相关操作的菜单项显示的文本内容提示用户点击该菜单项可以将已经全选的状态取消即取消选中所有项目 -->
<string name="menu_font_size">文字大小</string>
<!-- 定义名为“menu_font_size”的字符串资源用于在菜单中作为调整文字大小相关操作的菜单项显示的文本内容引导用户点击该菜单项来进行文字大小相关的设置操作 -->
<string name="menu_font_small"></string>
<!-- 定义名为“menu_font_small”的字符串资源可能是在文字大小设置的相关菜单选项里作为表示小字号这一选项所显示的文字内容便于用户选择小字体来显示文本 -->
<string name="menu_font_normal">正常</string>
<!-- 定义名为“menu_font_normal”的字符串资源推测是在文字大小设置相关的菜单中作为表示正常字号这一选项显示的文字内容方便用户将文本字体设置为正常大小 -->
<string name="menu_font_large"></string>
<!-- 定义名为“menu_font_large”的字符串资源应该是在文字大小设置相关的菜单里作为表示大字号这一选项所展示的文字内容让用户可以选择大字体来显示文本 -->
<string name="menu_font_super">超大</string>
<!-- 定义名为“menu_font_super”的字符串资源可能是在文字大小设置相关的菜单中作为表示超大字号这一选项显示的文字内容供用户选择超大字体来展示文本 -->
<string name="menu_list_mode">進入清單模式</string>
<!-- 定义名为“menu_list_mode”的字符串资源用于在菜单中作为进入清单模式比如待办事项清单等类似的模式相关操作的菜单项显示的文本内容提示用户点击该菜单项可进入对应的清单模式方便对相关内容进行清单式管理 -->
<string name="menu_normal_mode">退出清單模式</string>
<!-- 定义名为“menu_normal_mode”的字符串资源用于在菜单中作为离开清单模式例如待办事项清单等相关操作的菜单项显示的文本内容提示用户点击该菜单项可退出当前所处的清单模式恢复到常规显示状态 -->
<string name="menu_folder_view">查看文件夾</string>
<!-- 定义名为“menu_folder_view”的字符串资源用于在菜单里作为查看文件夹相关操作的菜单项所显示的文本内容告知用户点击该菜单项后可以查看指定文件夹内的内容比如查看文件夹里包含的便签等信息 -->
<string name="menu_folder_delete">刪除文件夾</string>
<!-- 定义名为“menu_folder_delete”的字符串资源用于在菜单中作为删除文件夹相关操作的菜单项显示的文本内容提示用户点击该菜单项会执行删除文件夹的操作通常可能还会有相应确认提示以防误删 -->
<string name="menu_folder_change_name">修改文件夾名稱</string>
<!-- 定义名为“menu_folder_change_name”的字符串资源用于在菜单中作为更改文件夹名称相关操作的菜单项显示的文本内容引导用户点击该菜单项来对已有的文件夹重新命名使其名称更符合需求或便于识别 -->
<string name="folder_exist">文件夾 %1$s 已存在,請重新命名</string>
<!-- 定义名为“folder_exist”的字符串资源从格式“文件夾 %1$s 已存在,請重新命名”来看,它是一种带有占位符(%1$s的提示文本通常在创建文件夹时如果要创建的文件夹名称已存在就会用实际的文件夹名称替换占位符显示该提示信息告知用户需要重命名后再进行操作 -->
<string name="menu_share">分享</string>
<!-- 定义名为“menu_share”的字符串资源用于在菜单中作为分享相关操作的菜单项显示的文本内容提示用户点击该菜单项可进行分享操作比如分享便签内容到其他应用或者平台等 -->
<string name="menu_send_to_desktop">發送到桌面</string>
<!-- 定义名为“menu_send_to_desktop”的字符串资源用于在菜单中作为将相关内容发送到桌面例如创建桌面快捷方式等相关操作的菜单项显示的文本内容告知用户点击该菜单项可把对应的内容发送到桌面方便快速访问 -->
<string name="menu_alert">提醒我</string>
<!-- 定义名为“menu_alert”的字符串资源用于在菜单中作为设置提醒相关操作的菜单项显示的文本内容提示用户点击该菜单项可进行提醒相关设置比如设置某个便签的提醒时间等 -->
<string name="menu_remove_remind">刪除提醒</string>
// 定义了一个名为"menu_remove_remind"的字符串资源,其显示的文本内容为"删除提醒",可能用于菜单选项等场景中表示删除相关提醒的操作提示。
<string name="menu_title_select_folder">選擇文件夾</string>
// 定义名为"menu_title_select_folder"的字符串资源,文本内容是"选择文件夹",推测是用于菜单标题中,提示用户进行文件夹选择的相关操作。
<string name="menu_move_parent_folder">上一級文件夾</string>
// 此字符串资源名为"menu_move_parent_folder",内容为"上一级文件夹",通常用于表示在文件管理相关操作中,可执行返回上一级文件夹的操作提示。
<string name="info_note_enter_desktop">已添加到桌面</string>
// 名为"info_note_enter_desktop"的字符串资源,文本表示某个笔记(便签)已经被添加到桌面了,用于给用户提示相应操作结果。
<string name="alert_title_delete">刪除</string>
// 定义的"alert_title_delete"字符串资源,文本是"删除",可能作为弹出提示框(如确认删除操作时)的标题,让用户明确操作类型是删除相关。
<string name="alert_message_delete_notes">确认要刪除所選的 %d 條便籤嗎?</string>
// 名为"alert_message_delete_notes"的字符串资源,包含了格式化占位符%d用于在需要确认删除多条便签时向用户显示询问是否删除所选具体数量通过占位符替换实际数字的便签的提示消息。
<string name="alert_message_delete_note">确认要删除該條便籤嗎?</string>
// 定义了"alert_message_delete_note"字符串资源,用于提示用户确认是否删除某一条特定便签,向用户发起删除单个便签的确认询问。
<string name="alert_message_delete_folder">確認刪除檔夾及所包含的便簽嗎?</string>
// 这个名为"alert_message_delete_folder"的字符串资源,用于在要删除文件夹及其包含的所有便签时,弹出提示框询问用户是否确认执行该删除操作。
<string name="error_sdcard_unmounted">SD卡被佔用不能操作</string>
// 定义的"error_sdcard_unmounted"字符串资源文本表示SD卡处于被占用状态无法进行相关操作用于向用户反馈SD卡操作受阻的原因。
<string name="error_sdcard_export">導出TXT時發生錯誤請檢查SD卡</string>
// 名为"error_sdcard_export"的字符串资源用于在导出TXT文件出现错误时提示用户去检查SD卡告知用户错误与SD卡相关并引导排查。
<string name="error_note_not_exist">要查看的便籤不存在</string>
// 定义了"error_note_not_exist"字符串资源,当用户尝试查看某个便签但该便签实际不存在时,用于向用户反馈相应的错误提示信息。
<string name="error_note_empty_for_clock">不能爲空便籤設置鬧鐘提醒</string>
// 此字符串资源名为"error_note_empty_for_clock",用于提示用户不能给内容为空的便签设置闹钟提醒,告知用户操作不符合要求的原因。
<string name="error_note_empty_for_send_to_desktop">不能將空便籤發送到桌面</string>
// 名为"error_note_empty_for_send_to_desktop"的字符串资源,提示用户不可以将内容为空的便签发送到桌面,说明相应操作限制情况。
<string name="success_sdcard_export">導出成功</string>
// 定义的"success_sdcard_export"字符串资源用于在成功导出SD卡相关文件比如TXT等向用户反馈操作成功的提示信息。
<string name="failed_sdcard_export">導出失敗</string>
// 名为"failed_sdcard_export"的字符串资源在导出SD卡相关文件失败时向用户显示操作失败的提示消息。
<string name="format_exported_file_location">已將文本文件(%1$s)導出至SD(%2$s)目錄</string>
// 定义了"format_exported_file_location"字符串资源,包含了格式化占位符%1$s和%2$s用于按照实际的文件名和SD卡目录路径等信息进行替换展示具体的导出文件位置情况给用户。
<!-- Sync -->
// 以下是一组和同步Sync相关的字符串资源定义这里的注释"Sync"可能是用于分组标识,方便代码阅读和维护,表明下面的字符串都和同步功能有关。
<string name="ticker_syncing">同步便簽...</string>
// 名为"ticker_syncing"的字符串资源,用于在同步便签过程中向用户展示正在同步的提示信息,告知用户当前正在执行同步操作。
<string name="ticker_success">同步成功</string>
// 定义的"ticker_success"字符串资源,在便签同步操作成功完成后,向用户反馈同步成功的提示消息。
<string name="ticker_fail">同步失敗</string>
// 名为"ticker_fail"的字符串资源,当便签同步操作失败时,用于向用户显示同步失败的提示内容。
<string name="ticker_cancel">同步已取消</string>
// 此字符串资源名为"ticker_cancel",用于在同步操作被取消后,告知用户同步已取消这一情况。
<string name="success_sync_account">與%1$s同步成功</string>
// 定义了"success_sync_account"字符串资源,包含格式化占位符%1$s用于在和特定账号实际账号名称替换占位符同步成功时向用户展示具体与哪个账号同步成功的提示信息。
<string name="error_sync_network">同步失敗,請檢查網絡和帳號設置</string>
// 名为"error_sync_network"的字符串资源,在同步失败且原因可能和网络、账号设置相关时,提示用户去检查网络连接以及账号相关设置情况。
<string name="error_sync_internal">同步失敗,發生內部錯誤</string>
// 定义的"error_sync_internal"字符串资源,用于在同步失败是由于内部错误导致时,向用户反馈同步失败是因为内部出现问题这一情况。
<string name="error_sync_cancelled">同步已取消</string>
// 此字符串资源和前面的"ticker_cancel"类似,用于在同步被取消后,向用户提示同步已取消的消息,可能在不同的代码逻辑位置使用来反馈这一情况。
<string name="sync_progress_login">登陸%1$s...</string>
// 名为"sync_progress_login"的字符串资源,包含格式化占位符%1$s用于在同步过程中登录某个账号具体账号名替换占位符向用户展示正在登录的提示信息。
<string name="sync_progress_init_list">正在獲取服務器便籤列表...</string>
// 定义了"sync_progress_init_list"字符串资源,用于在同步操作中,正在从服务器获取便签列表时,向用户反馈当前正在进行的操作阶段情况。
<string name="sync_progress_syncing">正在同步本地便籤...</string>
// 此字符串资源名为"sync_progress_syncing",用于向用户提示当前正在进行本地便签和服务器等之间的同步操作,告知用户同步操作的具体阶段。
<!-- Preferences -->
// 以下是一组和偏好设置Preferences相关的字符串资源定义注释"Preferences"用于分组标识,表明下面的字符串都与应用的偏好设置功能相关。
<string name="preferences_title">設置</string>
// 定义名为"preferences_title"的字符串资源,文本内容为"设置",可能用于作为整个应用设置界面的标题显示。
<string name="preferences_account_title">同步賬號</string>
// 名为"preferences_account_title"的字符串资源,文本表示"同步账号",推测是在设置界面中用于标识与同步账号相关设置板块的标题。
<string name="preferences_account_summary">与google task同步便簽記錄</string>
// 定义了"preferences_account_summary"字符串资源用于简要说明同步账号相关功能即告知用户通过该设置可以和google task进行便签记录的同步操作。
<string name="preferences_last_sync_time">上次同步于 %1$s</string>
// 此字符串资源名为"preferences_last_sync_time",包含格式化占位符%1$s用于显示上次同步操作具体发生的时间实际时间替换占位符方便用户了解同步历史情况。
<string name="preferences_add_account">添加賬號</string>
// 定义的"preferences_add_account"字符串资源,文本内容为"添加账号"用于在设置界面中提示用户可执行添加账号的操作比如添加用于同步的google账号等。
<string name="preferences_menu_change_account">更換賬號</string>
// 名为"preferences_menu_change_account"的字符串资源,提示用户在相关菜单中可以进行更换同步账号的操作,告知用户存在此功能选项。
<string name="preferences_menu_remove_account">刪除賬號</string>
// 定义了"preferences_menu_remove_account"字符串资源,用于提示用户在相应菜单中可执行删除账号的操作,比如删除已添加的用于同步的账号等。
<string name="preferences_menu_cancel">取消</string>
// 此字符串资源名为"preferences_menu_cancel",在相关操作场景下提示用户可以执行取消操作,比如取消正在进行的设置更改等操作。
<string name="preferences_button_sync_immediately">立即同步</string>
// 定义的"preferences_button_sync_immediately"字符串资源,文本为"立即同步",通常用于设置界面中的按钮显示文本,提示用户点击该按钮可马上触发同步操作。
<string name="preferences_button_sync_cancel">取消同步</string>
// 名为"preferences_button_sync_cancel"的字符串资源,用于设置界面中按钮显示文本,提示用户点击该按钮可取消正在进行的同步操作。
<string name="preferences_dialog_change_account_title">當前帳號 %1$s</string>
// 定义了"preferences_dialog_change_account_title"字符串资源,包含格式化占位符%1$s用于在弹出对话框中显示当前账号的具体名称实际账号名替换占位符比如在更换账号等相关操作的对话框中展示当前账号信息。
<string name="preferences_dialog_change_account_warn_msg">如更換同步帳號,過去的帳號同步信息將被清空,再次切換的同時可能會造成數據重復</string>
// 此字符串资源名为"preferences_dialog_change_account_warn_msg",用于在用户进行更换同步账号操作时,弹出提示框向用户警告更换账号会导致过去账号的同步信息被清空,以及再次切换账号可能出现数据重复的情况,提醒用户谨慎操作。
<string name="preferences_dialog_select_account_title">同步便簽</string>
// 定义的"preferences_dialog_select_account_title"字符串资源用于作为选择账号比如选择用于同步便签的google账号相关对话框的标题提示用户该对话框的用途是进行同步便签账号相关操作。
<string name="preferences_dialog_select_account_tips">請選擇google帳號便簽將與該帳號的google task內容同步。</string>
// 名为"preferences_dialog_select_account_tips"的字符串资源在选择账号的对话框中向用户提示具体操作要求即让用户选择google账号以便后续便签能和该账号对应的google task内容进行同步。
<string name="preferences_toast_cannot_change_account">正在同步中,不能修改同步帳號</string>
// 定义了"preferences_toast_cannot_change_account"字符串资源,用于在同步操作正在进行时,若用户尝试修改同步账号,弹出提示框告知用户当前不能进行修改账号操作,限制用户的不当操作。
<string name="preferences_toast_success_set_accout">同步帳號已設置為%1$s</string>
// 此字符串资源名为"preferences_toast_success_set_accout",包含格式化占位符%1$s用于在成功设置同步账号后向用户展示具体设置成了哪个账号实际账号名替换占位符的提示信息。
<string name="preferences_bg_random_appear_title">新建便籤背景顏色隨機</string>
// 定义的"preferences_bg_random_appear_title"字符串资源,文本表示新建便签时背景颜色会随机出现,用于在相关设置界面等地方提示用户有这样一个功能特性。
<string name="button_delete">刪除</string>
// 定义了一个名为"button_delete"的字符串资源,其文本内容为"删除",通常会被用作按钮上显示的文字,提示用户点击该按钮可执行删除相关的操作。
<string name="call_record_folder_name">通話便籤</string>
// 名为"call_record_folder_name"的字符串资源,文本表示"通话便签",可能用于标识与通话记录相关的便签所在文件夹的名称,方便用户知晓其用途和内容关联。
<string name="hint_foler_name">請輸入名稱</string>
// 此字符串资源名为"hint_foler_name",文本内容是"请输入名称",一般用于提示用户在相应的输入框等地方输入名称,比如创建文件夹或者给某个项目命名时引导用户操作。
<string name="search_label">正在搜索便籤</string>
// 定义了"search_label"字符串资源,文本显示为"正在搜索便签",常用来在界面上向用户反馈当前正在进行便签搜索这一操作状态,让用户知晓系统正在执行的任务。
<string name="search_hint">搜索便籤</string>
// 名为"search_hint"的字符串资源,内容是"搜索便签",多用于作为输入框的提示文字,提示用户在此处可以输入相关内容来进行便签的搜索操作。
<string name="search_setting_description">便籤中的文字</string>
// 定义的"search_setting_description"字符串资源,文本表明"便签中的文字",推测是用于对搜索设置相关功能进行描述,比如说明搜索是基于便签内包含的文字内容来进行的情况等。
<string name="search">便籤</string>
// 此字符串资源名为"search",文本为"便签",可能在不同的搜索相关场景下使用,比如作为菜单选项、功能名称等体现与便签搜索相关的概念。
<string name="datetime_dialog_ok">設置</string>
// 名为"datetime_dialog_ok"的字符串资源,文本是"设置",通常会用于日期时间相关对话框中的确认按钮文字显示,提示用户点击该按钮可进行相应的时间等设置操作。
<string name="datetime_dialog_cancel">取消</string>
// 定义了"datetime_dialog_cancel"字符串资源,文本为"取消",多用于日期时间相关对话框中的取消按钮文字显示,方便用户在不想进行设置等操作时点击取消当前操作流程。
<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>
</resources>
// 定义了一个名为"search_results_title"的复数形式plurals的字符串资源。
// 它里面的<item>元素用于根据数量情况来展示不同的文本内容这里quantity="other"表示在其他数量(非特定单一数量情况)时的显示规则。
// 其中包含了两个格式化占位符<xliff:g id="NUMBER">%1$s</xliff:g><xliff:g id="SEARCH">%2$s</xliff:g>,分别用于替换实际的符合结果数量以及搜索的具体内容,整体用于向用户展示搜索便签后得到的符合特定搜索条件的结果数量及相应搜索内容的提示信息。

@ -1,31 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式是 utf-8这是 XML 文件开头的标准标识,用于告知解析器按照此版本和编码规则来正确解析该文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
此处是版权相关声明,说明代码所属的开源社区是 The MiCode Open Source Community版权时间范围在 2010 - 2011 年。
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
表示此文件遵循 Apache License 2.0 开源协议进行授权许可,意味着若要使用该文件,必须遵守该协议的相关规定。
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过访问网址http://www.apache.org/licenses/LICENSE-2.0)获取 Apache License 2.0 协议的详细内容。
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”的状态进行分发,不会附带额外的保证等情况。
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
强调该代码不存在任何明示或暗示的保证以及相关条件,使用者需自行承担使用代码带来的风险。
See the License for the specific language governing permissions and
limitations under the License.
-->
提示查看协议内容,以了解关于权限及限制方面的详细规定。 -->
<resources>
<!-- 这是一个用于定义各种资源的根标签,在安卓开发中,像字符串、数组、颜色等资源都可以在这里面进行定义 -->
<!-- Backup format -->
<!-- 此处是一个注释,用于说明下面定义的资源数组与备份格式相关,方便开发者理解其用途 -->
<string-array name="format_for_exported_note">
<!-- 定义一个名为“format_for_exported_note”的字符串数组从命名推测可能是用于格式化导出笔记时相关信息的格式 -->
<item>-%s</item> <!-- format_folder_name -->
<!-- 数组中的一个元素,这里是一个字符串格式的占位符(%s从注释来看可能是用于格式化文件夹名称相关信息具体内容会在后续代码中替换该占位符 -->
<item>--%s</item> <!-- format_folder_note_date -->
<!-- 数组中的另一个元素,同样是字符串格式占位符,推测是用于格式化文件夹内笔记日期相关信息 -->
<item>--%s</item> <!-- format_note_date -->
<!-- 数组中的元素,用于格式化笔记本身日期相关信息的占位符 -->
<item>--%s</item> <!-- format_note_content -->
<!-- 数组中的元素,用于格式化笔记内容相关信息的占位符 -->
</string-array>
<string-array name="menu_share_ways">
<!-- 定义一个名为“menu_share_ways”的字符串数组从命名推测是用于表示分享菜单中可选择的分享方式相关信息 -->
<item>Messaging</item>
<!-- 数组中的一个元素代表一种分享方式这里是“Messaging”消息应用意味着用户可以通过消息应用来分享相关内容 -->
<item>Email</item>
<!-- 数组中的另一个元素,代表另一种分享方式,即通过电子邮件来分享相关内容 -->
</string-array>
</resources>

@ -1,20 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式是 utf-8这是 XML 文件开头的标准标识,用于告知解析器按照此版本和编码规则来正确解析该文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
此处是版权相关声明,说明代码所属的开源社区是 The MiCode Open Source Community版权时间范围在 2010 - 2011 年。
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
表示此文件遵循 Apache License 2.0 开源协议进行授权许可,意味着若要使用该文件,必须遵守该协议的相关规定。
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过访问网址http://www.apache.org/licenses/LICENSE-2.0)获取 Apache License 2.0 协议的详细内容。
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”的状态进行分发,不会附带额外的保证等情况。
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
强调该代码不存在任何明示或暗示的保证以及相关条件,使用者需自行承担使用代码带来的风险。
See the License for the specific language governing permissions and
limitations under the License.
-->
提示查看协议内容,以了解关于权限及限制方面的详细规定。 -->
<resources>
<color name="user_query_highlight">#335b5b5b</color>
</resources>
<!-- <resources>标签是安卓资源文件中的根标签,用于定义各种类型的资源,比如字符串、颜色、数组等资源都可以放在这里面定义 -->
<color name="user_query_highlight">
<!-- 定义一个名为“user_query_highlight”的颜色资源从命名推测可能是用于突出显示用户查询相关内容时的颜色设定 -->
#335b5b5b
<!-- 具体的颜色值,这里使用十六进制格式来表示颜色,格式为 #AARRGGBB其中 AA 表示透明度RR、GG、BB 分别表示红、绿、蓝三种颜色通道的值),在这个例子中,透明度为 51十六进制的 33红色、绿色、蓝色通道的值均为 91十六进制的 5b整体定义了一种偏灰色且带有一定透明度的颜色 -->
</color>
</resources>

@ -1,24 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式是 utf-8这是 XML 文件开头的标准标识,用于告知解析器按照此版本和编码规则来正确解析该文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
此处是版权相关声明,说明代码所属的开源社区是 The MiCode Open Source Community版权时间范围在 2010 - 2011 年。
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
表示此文件遵循 Apache License 2.0 开源协议进行授权许可,意味着若要使用该文件,必须遵守该协议的相关规定。
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过访问网址http://www.apache.org/licenses/LICENSE-2.0)获取 Apache License 2.0 协议的详细内容。
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”的状态进行分发,不会附带额外的保证等情况。
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
强调该代码不存在任何明示或暗示的保证以及相关条件,使用者需自行承担使用代码带来的风险。
See the License for the specific language governing permissions and
limitations under the License.
-->
提示查看协议内容,以了解关于权限及限制方面的详细规定。 -->
<resources>
<dimen name="text_font_size_super">33sp</dimen>
<dimen name="text_font_size_large">26sp</dimen>
<dimen name="text_font_size_medium">20sp</dimen>
<dimen name="text_font_size_normal">17sp</dimen>
<dimen name="text_font_size_small">14sp</dimen>
<!-- <resources>标签是安卓资源文件中的根标签,用于定义各种类型的资源,像尺寸、字符串、颜色等不同类型资源都可以在此标签内进行定义 -->
<dimen name="text_font_size_super">
<!-- 定义一个名为“text_font_size_super”的尺寸资源从命名推测是用于设置文本字体超级大的字号尺寸 -->
33sp
<!-- 具体的尺寸值这里使用“sp”可缩放像素作为单位意味着该字号大小会根据用户在设备上设置的字体缩放比例进行相应的缩放此处字号大小设定为 33 可缩放像素 -->
</dimen>
<dimen name="text_font_size_large">
<!-- 定义一个名为“text_font_size_large”的尺寸资源推测是用于设置文本字体较大的字号尺寸 -->
26sp
<!-- 具体的尺寸值为 26 可缩放像素,同样会随用户设备字体缩放设置而变化 -->
</dimen>
<dimen name="text_font_size_medium">
<!-- 定义一个名为“text_font_size_medium”的尺寸资源通常可用于设置文本字体中等大小的字号尺寸 -->
20sp
<!-- 对应的尺寸值是 20 可缩放像素,其字号大小会根据设备相关设置动态调整 -->
</dimen>
<dimen name="text_font_size_normal">
<!-- 定义一个名为“text_font_size_normal”的尺寸资源大概率是用于设置文本字体常规大小的字号尺寸 -->
17sp
<!-- 具体字号大小设定为 17 可缩放像素,会依据设备的字体缩放配置进行相应改变 -->
</dimen>
<dimen name="text_font_size_small">
<!-- 定义一个名为“text_font_size_small”的尺寸资源应该是用于设置文本字体较小的字号尺寸 -->
14sp
<!-- 尺寸值为 14 可缩放像素,其大小可随用户设备字体缩放情况而变动 -->
</dimen>
</resources>

@ -1,135 +1,302 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明表明该 XML 文件遵循的版本是 1.0,使用的编码格式是 utf-8这是 XML 文件开头的标准标识,用于告知解析器按照此版本和编码规则来正确解析该文件 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
此处是版权相关声明,说明代码所属的开源社区是 The MiCode Open Source Community版权时间范围在 2010 - 2011 年。
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
表示此文件遵循 Apache License 2.0 开源协议进行授权许可,意味着若要使用该文件,必须遵守该协议的相关规定。
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
告知可以通过访问网址http://www.apache.org/licenses/LICENSE-2.0)获取 Apache License 2.0 协议的详细内容。
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
说明依据该协议分发软件时,软件是以“原样”的状态进行分发,不会附带额外的保证等情况。
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
强调该代码不存在任何明示或暗示的保证以及相关条件,使用者需自行承担使用代码带来的风险。
See the License for the specific language governing permissions and
limitations under the License.
-->
提示查看协议内容,以了解关于权限及限制方面的详细规定。 -->
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- <resources>是安卓资源文件的根标签用于定义各类资源这里引入了两个命名空间“android”用于安卓相关的资源属性设置等操作“xliff”常用于国际化文本处理相关场景 -->
<string name="app_name">Notes</string>
<!-- 定义一个名为“app_name”的字符串资源该资源大概率用于表示整个应用程序的名称在这里被设置为“Notes”很可能是一款笔记类应用的名称显示 -->
<string name="app_widget2x2">Notes 2x2</string>
<!-- 定义名为“app_widget2x2”的字符串资源推测是用于描述应用中尺寸为 2x2 的小部件对应的显示名称,方便用户识别和区分不同尺寸的小部件 -->
<string name="app_widget4x4">Notes 4x4</string>
<!-- 定义名为“app_widget4x4”的字符串资源同理应该是用于表示应用里尺寸为 4x4 的小部件的显示名称,起到标识该特定尺寸小部件的作用 -->
<string name="widget_havenot_content">No associated note found, click to create associated note.</string>
<!-- 定义名为“widget_havenot_content”的字符串资源从其内容来看可能是在小部件上显示的提示信息当没有与之关联的笔记时提示用户点击可以去创建关联的笔记 -->
<string name="widget_under_visit_mode">Privacy modecan not see note content</string>
<!-- 定义名为“widget_under_visit_mode”的字符串资源推测是用于小部件处于隐私访问模式下时向用户展示的提示文本告知用户在此模式下无法查看笔记的具体内容 -->
<string name="notelist_string_info">...</string>
<!-- 定义名为“notelist_string_info”的字符串资源仅从“...”不太明确其确切用途,不过可能是在笔记列表相关界面中用于展示某种简略信息或者占位性质的文本内容 -->
<string name="notelist_menu_new">Add note</string>
<!-- 定义名为“notelist_menu_new”的字符串资源用于在笔记列表对应的菜单里作为创建新笔记这一菜单项所显示的文字内容明确提示用户点击该菜单项可进行添加笔记的操作 -->
<string name="delete_remind_time_message">Delete reminder successfully</string>
<!-- 定义名为“delete_remind_time_message”的字符串资源大概率是在成功删除提醒时间后向用户展示的提示消息告知用户相应的删除提醒操作已经顺利完成 -->
<string name="set_remind_time_message">Set reminder</string>
<!-- 定义名为“set_remind_time_message”的字符串资源应该是在执行设置提醒时间相关操作时向用户呈现的提示文本表明当前正在进行设置提醒的动作 -->
<string name="note_alert_expired">Expired</string>
<!-- 定义名为“note_alert_expired”的字符串资源可能是在笔记的提醒功能过期时用于显示给用户的提示文字比如在提醒相关界面展示“Expired”字样让用户知晓提醒已过期 -->
<string name="format_date_ymd">yyyyMMdd</string>
<!-- 定义名为“format_date_ymd”的字符串资源这是定义了一种日期格式采用“yyyyMMdd”这种形式常用于对日期数据进行格式化输出比如按照此格式来存储、展示笔记相关的日期信息等 -->
<string name="format_datetime_mdhm">MMMd kk:mm</string>
<!-- 定义名为“format_datetime_mdhm”的字符串资源同样是一种日期时间格式的定义按照“MMMd kk:mm”的格式来规范日期时间数据的展示方便在应用中统一处理和显示这类信息 -->
<string name="notealert_ok">Got it</string>
<!-- 定义名为“notealert_ok”的字符串资源推测是在笔记相关提醒的操作场景里当用户确认知晓或者完成某个操作后对应的按钮或者提示文本会显示“Got it”表示用户明白了、已确认的意思 -->
<string name="notealert_enter">Take a look</string>
<!-- 定义名为“notealert_enter”的字符串资源可能是在笔记提醒相关的交互中用于引导用户点击去查看具体笔记内容的提示文字提示用户点击相应区域可以查看详情 -->
<string name="note_link_tel">Call</string>
<!-- 定义名为“note_link_tel”的字符串资源大概率是当笔记中包含电话号码链接等情况时在相应位置显示的操作提示文本告知用户点击此处可以进行拨打电话的操作 -->
<string name="note_link_email">Send email</string>
<!-- 定义名为“note_link_email”的字符串资源应该是在笔记里有邮件链接相关内容时显示给用户的提示文字提示用户点击可触发发送邮件的操作 -->
<string name="note_link_web">Browse web</string>
<!-- 定义名为“note_link_web”的字符串资源可能是在笔记中存在网页链接的情况下向用户展示的提示文本告知用户点击该链接可以浏览对应的网页内容 -->
<string name="note_link_other">Open map</string>
<!-- 定义名为“note_link_other”的字符串资源推测是在笔记涉及到地图相关链接等情况时显示的提示文字提示用户点击可以打开地图应用进行相关操作 -->
<!-- Text export file information -->
<!-- 这是一个注释说明,告知下面的字符串资源是与文本导出文件相关的一些信息 -->
<string name="file_path">/MIUI/notes/</string>
<!-- 定义名为“file_path”的字符串资源此资源用于指定文本导出文件在设备中的存储路径这里设定的路径为“/MIUI/notes/”,也就是导出的文件会存放在该目录下 -->
<string name="file_name_txt_format">notes_%s.txt</string>
<!-- 定义名为“file_name_txt_format”的字符串资源它规定了文本导出文件的文件名格式其中“%s”是一个占位符后续会根据具体情况替换为相应内容以此生成实际的文件名整体格式为“notes_具体内容.txt” -->
<!-- notes list string -->
<!-- 注释说明下面的字符串资源是和笔记列表相关的字符串内容 -->
<string name="format_folder_files_count">(%d)</string>
<!-- 定义名为“format_folder_files_count”的字符串资源从格式“(%d)”来看,它是一种占位符形式的字符串,通常用于在展示文件夹内文件数量相关信息时,会将实际的文件数量替换“%d”从而准确显示数量情况 -->
<string name="menu_create_folder">New Folder</string>
<!-- 定义名为“menu_create_folder”的字符串资源用于在应用的菜单中作为创建新文件夹这一菜单项所显示的文字内容清晰地提示用户点击该菜单项可进行新建文件夹的操作 -->
<string name="menu_export_text">Export text</string>
<!-- 定义名为“menu_export_text”的字符串资源用于在菜单里作为导出文本这个菜单项显示的文本内容告知用户点击该菜单项就能执行文本导出的相关操作 -->
<string name="menu_sync">Sync</string>
<!-- 定义名为“menu_sync”的字符串资源用于在菜单中作为同步相关操作的菜单项所显示的文字内容提示用户点击它可以发起数据同步的操作 -->
<string name="menu_sync_cancel">Cancel syncing</string>
<!-- 定义名为“menu_sync_cancel”的字符串资源用于在正在进行同步操作的过程中在菜单里作为取消同步这一菜单项显示的文本内容方便用户点击来终止同步操作 -->
<string name="menu_setting">Settings</string>
<!-- 定义名为“menu_setting”的字符串资源用于在菜单中作为进入设置界面或者执行设置相关操作的菜单项所显示的文字内容引导用户点击该菜单项以进入设置相关功能区域 -->
<string name="menu_search">Search</string>
<!-- 定义名为“menu_search”的字符串资源用于在菜单中作为搜索相关操作的菜单项显示的文本内容提示用户点击该菜单项可以进行搜索相关的操作比如查找笔记内容等 -->
<string name="menu_delete">Delete</string>
<!-- 定义名为“menu_delete”的字符串资源用于在菜单中作为删除相关操作的菜单项显示的文本内容告知用户点击该菜单项可以执行删除相应内容的操作例如删除笔记、文件夹等 -->
<string name="menu_move">Move to folder</string>
<!-- 定义名为“menu_move”的字符串资源用于在菜单中作为将相关内容移动到文件夹这一菜单项显示的文本内容提示用户点击该菜单项可进行把某些内容移动到指定文件夹的操作 -->
<string name="menu_select_title">%d selected</string>
<!-- 定义名为“menu_select_title”的字符串资源从格式“%d selected”来看它是用于在有多个项目被选中后展示选中数量相关提示信息的文本会根据实际选中的项目数量替换“%d”来准确告知用户选中情况 -->
<string name="menu_select_none">Nothing selected, the operation is invalid</string>
<!-- 定义名为“menu_select_none”的字符串资源用于在没有任何项目被选中的情况下向用户显示的提示文本告知用户由于没有选中内容当前要执行的操作是无效的 -->
<string name="menu_select_all">Select all</string>
<!-- 定义名为“menu_select_all”的字符串资源用于在菜单中作为全选相关操作的菜单项显示的文本内容提示用户点击该菜单项可以一次性选中所有相关的项目 -->
<string name="menu_deselect_all">Deselect all</string>
<!-- 定义名为“menu_deselect_all”的字符串资源用于在菜单中作为取消全选相关操作的菜单项显示的文本内容提示用户点击该菜单项可以将已经全选的状态取消即取消选中所有项目 -->
<string name="menu_font_size">Font size</string>
<!-- 定义名为“menu_font_size”的字符串资源用于在菜单中作为调整字体大小相关操作的菜单项显示的文本内容引导用户点击该菜单项来进行字体大小相关的设置操作 -->
<string name="menu_font_small">Small</string>
<!-- 定义名为“menu_font_small”的字符串资源可能是在字体大小设置的相关菜单选项里作为表示小字号这一选项所显示的文字内容便于用户选择小字体来显示文本 -->
<string name="menu_font_normal">Medium</string>
<!-- 定义名为“menu_font_normal”的字符串资源推测是在字体大小设置相关的菜单中作为表示中等字号这一选项显示的文字内容方便用户将文本字体设置为中等大小 -->
<string name="menu_font_large">Large</string>
<!-- 定义名为“menu_font_large”的字符串资源应该是在字体大小设置相关的菜单里作为表示大字号这一选项所展示的文字内容让用户可以选择大字体来显示文本 -->
<string name="menu_font_super">Super</string>
<!-- 定义名为“menu_font_super”的字符串资源可能是在字体大小设置相关的菜单中作为表示超大字号这一选项显示的文字内容供用户选择超大字体来展示文本 -->
<string name="menu_list_mode">Enter check list</string>
<!-- 定义名为“menu_list_mode”的字符串资源用于在菜单中作为进入清单模式比如待办事项清单等类似的模式相关操作的菜单项显示的文本内容提示用户点击该菜单项可进入对应的清单模式方便对相关内容进行清单式管理 -->
<string name="menu_normal_mode">Leave check list</string>
<!-- 定义名为“menu_normal_mode”的字符串资源用于在菜单中作为离开清单模式例如待办事项清单等相关操作的菜单项显示的文本内容提示用户点击该菜单项可退出当前所处的清单模式恢复到常规显示状态 -->
<string name="menu_folder_view">View folder</string>
<!-- 定义名为“menu_folder_view”的字符串资源用于在菜单里作为查看文件夹相关操作的菜单项所显示的文本内容告知用户点击该菜单项后可以查看指定文件夹内的内容比如查看文件夹里包含的笔记等信息 -->
<string name="menu_folder_delete">Delete folder</string>
<!-- 定义名为“menu_folder_delete”的字符串资源用于在菜单中作为删除文件夹相关操作的菜单项显示的文本内容提示用户点击该菜单项会执行删除文件夹的操作通常可能还会有相应确认提示以防误删 -->
<string name="menu_folder_change_name">Change folder name</string>
<!-- 定义名为“menu_folder_change_name”的字符串资源用于在菜单中作为更改文件夹名称相关操作的菜单项显示的文本内容引导用户点击该菜单项来对已有的文件夹重新命名使其名称更符合需求或便于识别 -->
<string name="folder_exist">The folder %1$s exist, please rename</string>
<!-- 定义名为“folder_exist”的字符串资源从格式“The folder %1$s exist, please rename”来看它是一种带有占位符%1$s的提示文本通常在创建文件夹时如果要创建的文件夹名称已存在就会用实际的文件夹名称替换占位符显示该提示信息告知用户需要重命名后再进行操作 -->
<string name="menu_share">Share</string>
<!-- 定义名为“menu_share”的字符串资源用于在菜单中作为分享相关操作的菜单项显示的文本内容提示用户点击该菜单项可进行分享操作比如分享笔记内容到其他应用或者平台等 -->
<string name="menu_send_to_desktop">Send to home</string>
<!-- 定义名为“menu_send_to_desktop”的字符串资源用于在菜单中作为将相关内容发送到桌面例如创建桌面快捷方式等相关操作的菜单项显示的文本内容告知用户点击该菜单项可把对应的内容发送到桌面方便快速访问 -->
<string name="menu_alert">Remind me</string>
<!-- 定义名为“menu_alert”的字符串资源用于在菜单中作为设置提醒相关操作的菜单项显示的文本内容提示用户点击该菜单项可进行提醒相关设置比如设置某个笔记的提醒时间等 -->
<string name="menu_remove_remind">Delete reminder</string>
<!-- 定义名为“menu_remove_remind”的字符串资源用于在菜单中作为删除提醒相关操作的菜单项显示的文本内容提示用户点击该菜单项可删除已设置好的提醒取消之前设定的提醒功能 -->
<string name="menu_title_select_folder">Select folder</string>
<!-- 定义名为“menu_title_select_folder”的字符串资源用于在选择文件夹相关操作时作为标题类提示文本显示告知用户当前正在进行选择文件夹的操作让用户明确操作意图 -->
<string name="menu_move_parent_folder">Parent folder</string>
<!-- 定义名为“menu_move_parent_folder”的字符串资源用于在涉及将内容移动到上级文件夹等相关操作时作为提示文本显示告知用户可以选择将相应内容移动到当前所在文件夹的上级文件夹中 -->
<string name="info_note_enter_desktop">Note added to home</string>
<!-- 定义名为“info_note_enter_desktop”的字符串资源可能是在笔记成功添加到桌面比如创建桌面快捷方式成功等情况用于显示的提示信息告知用户对应的笔记已经被添加到桌面了方便用户知晓操作结果 -->
<string name="alert_message_delete_folder">Confirm to delete folder and its notes?</string>
<!-- 定义名为“alert_message_delete_folder”的字符串资源用于在要删除文件夹及其内部笔记时弹出提示框显示的询问信息询问用户是否确认执行删除该文件夹以及里面所有笔记的操作需要用户明确回应以避免误操作 -->
<string name="alert_title_delete">Delete selected notes</string>
<!-- 定义名为“alert_title_delete”的字符串资源用于在删除选中笔记相关操作时弹出提示框的标题文本清晰地告知用户当前操作是针对已选中的笔记进行删除让用户快速了解提示框所关联的操作内容 -->
<string name="alert_message_delete_notes">Confirm to delete the selected %d notes?</string>
<!-- 定义名为“alert_message_delete_notes”的字符串资源从格式“Confirm to delete the selected %d notes?”来看,它是在要删除多个选中笔记时,弹出提示框显示的询问信息,会根据实际选中笔记的数量替换占位符“%d”来准确询问用户是否确认删除相应数量的笔记 -->
<string name="alert_message_delete_note">Confirm to delete this note?</string>
<!-- 定义名为“alert_message_delete_note”的字符串资源用于在要删除单个笔记时弹出提示框显示的询问信息询问用户是否确认删除当前这一个笔记获取用户的明确操作意向 -->
<string name="format_move_notes_to_folder">Have moved selected %1$d notes to %2$s folder</string>
<!-- 定义名为“format_move_notes_to_folder”的字符串资源从格式来看它是用于在成功将选中笔记移动到指定文件夹后显示的提示信息会用实际移动笔记的数量替换占位符“%1$d”用具体的文件夹名称替换“%2$s”告知用户移动操作的具体情况 -->
<!-- Error information -->
<!-- 这是一个注释说明,告知下面的字符串资源是与错误相关的提示信息 -->
<string name="error_sdcard_unmounted">SD card busy, not available now</string>
<!-- 定义名为“error_sdcard_unmounted”的字符串资源用于在 SD 卡处于忙碌状态(比如正在被其他程序使用、出现故障等情况),导致无法正常使用时,显示的提示信息,告知用户当前 SD 卡不可用,无法进行相关操作 -->
<string name="error_sdcard_export">Export failed, please check SD card</string>
<!-- 定义名为“error_sdcard_export”的字符串资源在文本导出操作失败且原因可能与 SD 卡相关(比如 SD 卡空间不足、连接异常等)时,显示该提示信息,提示用户去检查 SD 卡的情况,以便排查问题所在 -->
<string name="error_note_not_exist">The note is not exist</string>
<!-- 定义名为“error_note_not_exist”的字符串资源当要操作的笔记不存在比如查找、编辑、删除某个不存在的笔记时会显示该提示信息告知用户指定的笔记不存在操作无法进行 -->
<string name="error_note_empty_for_clock">Sorry, can not set clock on empty note</string>
<!-- 定义名为“error_note_empty_for_clock”的字符串资源用于在尝试对空白笔记没有内容的笔记设置闹钟提醒等与时间相关操作时显示的提示信息告知用户无法在空白笔记上进行此类操作需要先添加内容等 -->
<string name="error_note_empty_for_send_to_desktop">Sorry, can not send and empty note to home</string>
<!-- 定义名为“error_note_empty_for_send_to_desktop”的字符串资源在试图将空白笔记发送到桌面比如创建桌面快捷方式显示该提示信息告知用户不能将空白笔记发送到桌面可能需要先完善笔记内容等 -->
<string name="success_sdcard_export">Export successful</string>
<!-- 定义名为“success_sdcard_export”的字符串资源在文本导出到 SD 卡操作成功后,用于显示的提示信息,告知用户导出操作已经顺利完成,让用户知晓操作结果 -->
<string name="failed_sdcard_export">Export fail</string>
<!-- 定义名为“failed_sdcard_export”的字符串资源当文本导出到 SD 卡操作失败时,显示该提示信息,简单告知用户导出失败了,不过可能还需要结合其他错误提示进一步排查原因 -->
<string name="format_exported_file_location">Export text file (%1$s) to SD (%2$s) directory</string>
<!-- 定义名为“format_exported_file_location”的字符串资源从格式来看它带有两个占位符%1$s 和 %2$s通常用于在告知用户文本导出文件的存放位置信息时会用实际的文件名替换“%1$s”用 SD 卡的具体目录路径替换“%2$s”清晰展示导出文件的具体位置情况 -->
<!-- Sync -->
<!-- 这是一个注释说明告知下面的字符串资源是与同步Sync相关的提示信息 -->
<string name="ticker_syncing">Syncing notes...</string>
<!-- 定义名为“ticker_syncing”的字符串资源在开始进行笔记同步操作时用于显示的提示信息告知用户正在同步笔记让用户知晓当前操作状态一般可能会以滚动条文字等形式展示 -->
<string name="ticker_success">Sync is successful</string>
<!-- 定义名为“ticker_success”的字符串资源在笔记同步操作成功完成后用于显示的提示信息告知用户同步已经顺利完成让用户了解操作结果 -->
<string name="ticker_fail">Sync is failed</string>
<!-- 定义名为“ticker_fail”的字符串资源当笔记同步操作失败时用于显示的提示信息告知用户同步失败了通常可能还需要结合其他相关错误提示进一步排查原因 -->
<string name="ticker_cancel">Sync is canceled</string>
<!-- 定义名为“ticker_cancel”的字符串资源在同步操作被取消比如用户手动点击取消按钮等情况用于显示的提示信息告知用户同步操作已被取消让用户知晓当前状态变化 -->
<string name="success_sync_account">Sync is successful with account %1$s</string>
<!-- 定义名为“success_sync_account”的字符串资源从格式“Sync is successful with account %1$s”来看它带有一个占位符%1$s在使用特定账户成功完成同步操作后会用实际的账户名称替换占位符告知用户使用哪个账户同步成功了明确同步相关情况 -->
<string name="error_sync_network">Sync failed, please check network and account settings</string>
<!-- 定义名为“error_sync_network”的字符串资源当笔记同步操作失败且原因可能与网络或者账户设置相关比如网络连接不稳定、账户密码错误等显示该提示信息提示用户去检查网络以及账户设置情况以排查同步失败的问题所在 -->
<string name="error_sync_internal">Sync failed, internal error occurs</string>
<!-- 定义名为“error_sync_internal”的字符串资源在笔记同步操作因内部错误比如应用自身的代码逻辑问题、服务器端问题等导致失败时显示该提示信息告知用户同步失败是由于内部出现错误不过具体原因可能还需要进一步排查分析 -->
<string name="error_sync_cancelled">Sync is canceled</string>
<!-- 定义名为“error_sync_cancelled”的字符串资源在同步操作被取消比如因程序异常等情况自动取消用于显示的提示信息告知用户同步操作已被取消与前面手动取消同步操作后显示的提示信息类似只是触发取消的原因不同 -->
<string name="sync_progress_login">Logging into %1$s...</string>
<!-- 定义名为“sync_progress_login”的字符串资源从格式“Logging into %1$s...”来看,它带有一个占位符(%1$s在同步操作过程中当正在登录相关账户比如登录到云端服务账户用于同步笔记会用实际的账户名称替换占位符告知用户正在登录哪个账户让用户了解同步操作的进展情况 -->
<string name="sync_progress_init_list">Getting remote note list...</string>
<!-- 定义名为“sync_progress_init_list”的字符串资源在同步操作中当正在获取远程笔记列表比如从云端服务器获取存储的笔记列表以便和本地笔记进行对比同步用于显示的提示信息告知用户当前正在获取远程笔记列表体现同步操作的进度状态 -->
<string name="sync_progress_syncing">Synchronize local notes with Google Task...</string>
<!-- 定义名为“sync_progress_syncing”的 string 资源,在同步操作进行到将本地笔记与 Google Task可能是谷歌的某种任务管理或笔记服务用于存储和同步笔记等进行同步的阶段时用于显示的提示信息告知用户当前正在进行具体的笔记同步操作步骤让用户知晓同步进展 -->
<!-- Preferences -->
<!-- 这是一个注释说明告知下面的字符串资源是与偏好设置Preferences相关的提示信息 -->
<string name="preferences_title">Settings</string>
<!-- 定义名为“preferences_title”的字符串资源用于在应用的偏好设置界面或者相关菜单中作为显示标题的文本内容告知用户当前进入的是设置相关的功能区域方便用户识别和操作 -->
<string name="preferences_account_title">Sync account</string>
<!-- 定义名为“preferences_account_title”的字符串资源用于在偏好设置中与同步账户相关的设置区域作为显示标题的文本内容提示用户该部分设置是针对同步账户进行操作的比如选择、更改同步账户等 -->
<string name="preferences_account_summary">Sync notes with google task</string>
<!-- 定义名为“preferences_account_summary”的字符串资源用于在同步账户相关设置区域作为对该设置功能的简要说明文本内容告知用户在这里可以将笔记与谷歌任务进行同步让用户了解该设置选项的作用 -->
<string name="preferences_last_sync_time">Last sync time %1$s</string>
<!-- 定义名为“preferences_last_sync_time”的字符串资源从格式“Last sync time %1$s”来看它带有一个占位符%1$s通常用于在显示上次同步时间信息时会用实际的时间数据按照一定格式存储的时间字符串替换占位符准确告知用户上次同步操作是什么时候进行的 -->
<string name="preferences_last_sync_time_format">yyyy-MM-dd hh:mm:ss</string>
<!-- 定义名为“preferences_last_sync_time_format”的字符串资源它规定了上次同步时间的显示格式采用“yyyy-MM-dd hh:mm:ss”这种常见的年--日 时:分:秒的格式,便于统一展示和识别同步时间信息 -->
<string name="preferences_add_account">Add account</string>
<!-- 定义名为“preferences_add_account”的字符串资源用于在偏好设置中作为添加账户比如添加用于同步笔记的新账户相关操作的菜单项或按钮显示的文本内容提示用户点击可进行添加账户的操作 -->
<string name="preferences_menu_change_account">Change sync account</string>
<!-- 定义名为“preferences_menu_change_account”的字符串资源用于在偏好设置的菜单中作为更改同步账户相关操作的菜单项显示的文本内容提示用户点击该菜单项可进行更改当前同步账户的操作 -->
<string name="preferences_menu_remove_account">Remove sync account</string>
<!-- 定义名为“preferences_menu_remove_account”的字符串资源用于在偏好设置的菜单中作为移除同步账户相关操作的菜单项显示的文本内容提示用户点击该菜单项可执行移除已有的同步账户的操作 -->
<string name="preferences_menu_cancel">Cancel</string>
<!-- 定义名为“preferences_menu_cancel”的字符串资源用于在偏好设置相关操作过程中比如正在添加、更改、移除账户等操作时作为取消当前操作的菜单项或按钮显示的文本内容提示用户点击可取消正在进行的操作回到之前的状态 -->
<string name="preferences_button_sync_immediately">Sync immediately</string>
<!-- 定义名为“preferences_button_sync_immediately”的字符串资源用于在偏好设置界面或者相关区域作为立即进行同步操作的按钮显示的文本内容提示用户点击该按钮可马上发起笔记同步操作无需等待其他定时同步等情况 -->
<string name="preferences_button_sync_cancel">Cancel syncing</string>
<!-- 定义名为“preferences_button_sync_cancel”的字符串资源用于在正在进行同步操作时在偏好设置界面或者相关区域作为取消同步操作的按钮显示的文本内容提示用户点击该按钮可终止当前正在进行的同步操作 -->
<string name="preferences_dialog_change_account_title">Current account %1$s</string>
<!-- 定义名为“preferences_dialog_change_account_title”的字符串资源从格式“Current account %1$s”来看它带有一个占位符%1$s在弹出更改账户相关的对话框时会用实际的当前账户名称替换占位符作为对话框的标题显示明确告知用户当前正在操作的是哪个账户 -->
<string name="preferences_dialog_change_account_warn_msg">All sync related information will be deleted, which may result in duplicated items sometime</string>
<!-- 定义名为“preferences_dialog_change_account_warn_msg”的字符串资源用于在弹出更改账户相关对话框时作为显示警告信息的文本内容告知用户更改账户操作会导致所有与同步相关的信息被删除并且有可能会出现重复的项目提醒用户谨慎操作 -->
<string name="preferences_dialog_select_account_title">Sync notes</string>
<string name="preferences_dialog_select_account_tips">Please select a google account. Local notes will be synced with google task.</string>
<string name="preferences_toast_cannot_change_account">Cannot change the account because sync is in progress</string>
<string name="preferences_toast_success_set_accout">%1$s has been set as the sync account</string>
<string name="preferences_bg_random_appear_title">New note background color random</string>
<!-- 定义名为“preferences_dialog_select_account_title”的字符串资源用于在弹出选择账户用于同步笔记相关的对话框时作为对话框的标题显示的文本内容告知用户当前对话框是用于选择同步笔记的账户让用户明确操作意图 -->
<string name="preferences_dialog_select_account_tips">Please select a google account. Local notes will be synced with google task.</string>
<!-- 定义名为“preferences_dialog_select_account_tips”的字符串资源用于在弹出选择账户相关对话框时作为提示信息的文本内容告知用户需要选择一个谷歌账户并且提示用户选择-->
<string name="button_delete">Delete</string>
<!-- 定义名为“button_delete”的字符串资源通常用于在界面上作为按钮显示的文本内容提示用户点击该按钮可执行删除相关的操作比如删除某个笔记、文件或者其他数据项等 -->
<string name="call_record_folder_name">Call notes</string>
<!-- 定义名为“call_record_folder_name”的字符串资源推测是用于表示存放通话记录相关笔记或者类似与通话相关内容记录的文件等的文件夹名称方便在应用中对这类特定内容进行归类和识别 -->
<string name="hint_foler_name">Input name</string>
<!-- 定义名为“hint_foler_name”的字符串资源大概率是用于在需要用户输入文件夹名称的场景下作为提示信息显示给用户告知用户在此处输入相应的名称引导用户完成命名操作 -->
<string name="search_label">Searching Notes</string>
<!-- 定义名为“search_label”的字符串资源可能是在搜索功能启动后用于显示正在搜索笔记这一状态的提示文本比如在搜索框附近或者搜索界面上展示让用户知晓当前正在进行搜索操作 -->
<string name="search_hint">Search notes</string>
<!-- 定义名为“search_hint”的字符串资源一般用于作为搜索框内的占位提示文字提示用户在此处输入内容来进行笔记搜索引导用户使用搜索功能查找想要的笔记内容 -->
<string name="search_setting_description">Text in your notes</string>
<!-- 定义名为“search_setting_description”的字符串资源或许是用于在搜索相关设置界面中作为对搜索功能的描述文本告知用户搜索的范围是笔记内包含的文本内容让用户了解搜索的具体情况 -->
<string name="search">Notes</string>
<!-- 定义名为“search”的字符串资源可能是用于标识搜索功能或者搜索相关区域的名称等例如在菜单中作为搜索选项对应的显示文字提示用户点击可进入搜索相关操作界面 -->
<string name="datetime_dialog_ok">set</string>
<!-- 定义名为“datetime_dialog_ok”的字符串资源通常是在日期时间相关的对话框比如设置提醒时间、编辑笔记时间等涉及时间选择的对话框作为确认按钮显示的文本内容提示用户点击该按钮可完成时间设置等相关操作即表示“确定、设置”的意思 -->
<string name="datetime_dialog_cancel">cancel</string>
<!-- 定义名为“datetime_dialog_cancel”的字符串资源同样是在日期时间相关的对话框中作为取消按钮显示的文本内容提示用户点击该按钮可取消当前正在进行的时间相关操作关闭对话框且不保存更改等 -->
<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>
<!-- 定义名为“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>
<!-- 定义复数资源中的一个子项当搜索结果数量为“一”quantity="one")时显示此文本内容。其中包含了两个占位符(<xliff:g>标签定义),%1$s 通常会被替换为实际的结果数量数字,%2$s 会被替换为搜索的关键词内容,用于准确告知用户搜索到了一个结果以及对应的搜索关键词是什么 -->
<!-- Case of 0 or 2 or more results. -->
<item quantity="other"><xliff:g id="number" example="15">%1$s</xliff:g> results for \"<xliff:g id="search" example="???">%2$s</xliff:g>\"</item>
<!-- 定义复数资源中的另一个子项当搜索结果数量为“零”或者“两个及以上”quantity="other")时显示此文本内容。同样包含两个占位符,作用与上面类似,会根据实际结果数量和搜索关键词来替换占位符,告知用户搜索到的结果数量以及对应的搜索关键词情况 -->
</plurals>
</resources>
</resources>

@ -1,69 +1,111 @@
<?xml version="1.0" encoding="utf-8"?>
// XML 声明,指定该 XML 文件的版本为 1.0,字符编码采用 UTF-8 格式,这是 XML 文件开头的标准声明部分,用于告知解析器如何处理该文件内容。
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
// 版权声明及开源许可相关注释说明该代码文件的版权归属MiCode 开源社区,时间范围是 2010 - 2011 年),以及所遵循的 Apache License 2.0 开源许可协议内容,告知使用者在符合该许可协议要求的情况下才能使用此文件。
<resources>
// XML 资源标签的开始,用于定义一系列的 Android 应用资源比如样式styles、尺寸dimens、颜色colors等各种资源都会在这个标签内部进行定义。
<style name="TextAppearanceSuper">
<item name="android:textSize">@dimen/text_font_size_super</item>
<item name="android:textColorLink">#0000ff</item>
</style>
// 定义了一个名为"TextAppearanceSuper"的样式资源。
// 其中包含两个子项(<item>
// 第一个子项通过"@dimen/text_font_size_super"设置了文本的字号大小,这里引用了名为"text_font_size_super"的尺寸资源(通常在 dimens.xml 文件中定义具体数值)来确定实际字号大小;
// 第二个子项将链接文本的颜色设置为蓝色(十六进制颜色码 #0000ff用于指定当文本作为链接显示时的颜色样式。
<style name="TextAppearanceLarge">
<item name="android:textSize">@dimen/text_font_size_large</item>
<item name="android:textColorLink">#0000ff</item>
</style>
// 定义名为"TextAppearanceLarge"的样式资源。
// 同样包含两个子项:
// 第一个子项利用"@dimen/text_font_size_large"来设置文本字号,引用对应的尺寸资源确定大小;
// 第二个子项也是把链接文本颜色设为蓝色(#0000ff与上面类似规定了链接文本颜色的显示样式。
<style name="TextAppearanceMedium">
<item name="android:textSize">@dimen/text_font_size_medium</item>
<item name="android:textColorLink">#0000ff</item>
</style>
// 定义"TextAppearanceMedium"样式资源。
// 包含两个子项,分别通过引用"text_font_size_medium"尺寸资源设置文本字号以及将链接文本颜色设为蓝色(#0000ff用于特定的文本显示样式设定。
<style name="TextAppearanceNormal">
<item name="android:textSize">@dimen/text_font_size_normal</item>
<item name="android:textColorLink">#0000ff</item>
</style>
// 定义"TextAppearanceNormal"样式资源。
// 其两个子项中,一个借助"text_font_size_normal"尺寸资源来规定文本字号大小,另一个将链接文本颜色设定为蓝色(#0000ff以定义相应的文本外观样式。
<style name="TextAppearancePrimaryItem">
<item name="android:textSize">@dimen/text_font_size_normal</item>
<item name="android:textColor">@color/primary_text_dark</item>
</style>
// 定义名为"TextAppearancePrimaryItem"的样式资源。
// 有两个子项:
// 第一个子项根据"text_font_size_normal"尺寸资源确定文本字号大小;
// 第二个子项通过引用"@color/primary_text_dark"来设置文本的颜色,这里会使用名为"primary_text_dark"的颜色资源(通常在 colors.xml 文件中定义具体颜色值)来确定文本实际显示的颜色,用于特定主要项目文本的外观样式定义。
<style name="TextAppearanceSecondaryItem">
<item name="android:textSize">@dimen/text_font_size_small</item>
<item name="android:textColor">@color/secondary_text_dark</item>
</style>
// 定义"TextAppearanceSecondaryItem"样式资源。
// 包含两个子项:
// 第一个子项按照"text_font_size_small"尺寸资源设置文本字号,使其呈现较小的字号样式;
// 第二个子项引用"@color/secondary_text_dark"颜色资源来设定文本颜色,用于特定次要项目文本的外观样式定义,比如一些辅助说明性文字等。
<style name="TextAppearanceUnderMenuIcon">
<item name="android:textSize">@dimen/text_font_size_normal</item>
<item name="android:textColor">@android:color/black</item>
</style>
// 定义"TextAppearanceUnderMenuIcon"样式资源。
// 它的两个子项中,一个依据"text_font_size_normal"尺寸资源确定文本字号大小保持正常字号;
// 另一个直接使用 Android 系统内置的黑色(@android:color/black来设置文本颜色用于在菜单图标下方等相关位置文本的外观样式设定。
<style name="HighlightTextAppearancePrimary">
<item name="android:textSize">@dimen/text_font_size_normal</item>
<item name="android:textSize">@dimen/text_font_size_normal</item>
<item name="android:textColor">@color/primary_text_dark</item>
</style>
// 定义"HighlightTextAppearancePrimary"样式资源。
// 同样有两个子项,分别通过"text_font_size_normal"尺寸资源确定文本字号以及引用"@color/primary_text_dark"颜色资源设置文本颜色,用于特定主要内容的突出显示文本(比如高亮显示的主要文本)的外观样式定义。
<style name="HighlightTextAppearanceSecondary">
<item name="android:textSize">@dimen/text_font_size_small</item>
<item name="android:textColor">@color/secondary_text_dark</item>
</style>
// 定义"HighlightTextAppearanceSecondary"样式资源。
// 其两个子项分别按照"text_font_size_small"尺寸资源设置较小的文本字号,并通过引用"@color/secondary_text_dark"颜色资源确定文本颜色,用于次要内容的突出显示文本(比如高亮显示的次要文本)的外观样式定义。
<style name="NoteTheme" parent="@android:style/Theme.Holo.Light">
<item name="android:actionBarStyle">@style/NoteActionBarStyle</item>
</style>
// 定义名为"NoteTheme"的主题样式资源,它继承自 Android 系统内置的"@android:style/Theme.Holo.Light"主题样式(即基于该主题样式进行扩展和定制)。
// 其中包含一个子项,通过"@style/NoteActionBarStyle"来设置该主题下的ActionBar操作栏的样式将其关联到名为"NoteActionBarStyle"的自定义样式资源上,用于整体主题中操作栏样式的定制设定。
<style name="NoteActionBarStyle" parent="@android:style/Widget.Holo.Light.ActionBar.Solid">
<item name="android:displayOptions" />
<item name="android:visibility">gone</item>
</style>
</resources>
// 定义名为"NoteActionBarStyle"的样式资源,它继承自 Android 系统内置的"@android:style/Widget.Holo.Light.ActionBar.Solid"样式(在继承基础上做进一步修改)。
// 包含两个子项:
// 第一个子项对"android:displayOptions"属性进行了设置这里虽然没有具体值但可能会在后续代码中根据需求动态赋值或者通过代码逻辑来处理该属性相关情况用于控制ActionBar的显示相关选项
// 第二个子项将"android:visibility"属性设置为"gone",意味着该操作栏在使用这个样式时将被隐藏,不显示在界面上,用于定制操作栏的显示状态。
</resources>
// XML 资源标签的结束,表示资源定义部分的结束。

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML 声明,表明该 XML 文件遵循的版本是 1.0,并且使用的字符编码格式为 UTF-8这是 XML 文件开头常见的标准声明,用于告知解析器如何正确解析后续内容。-->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
@ -15,16 +16,28 @@
limitations under the License.
-->
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:android="http://schemas.android.com/apk/res/android">
// 定义一个“PreferenceScreen”偏好设置屏幕元素它是 Android 中用于构建应用设置界面的根容器通过“xmlns:android”属性声明了 Android 命名空间,以便后续使用 Android 相关的属性和标签,整个设置界面相关的内容都会包含在这个标签内部。
<PreferenceCategory
android:key="pref_sync_account_key">
android:key="pref_sync_account_key">
</PreferenceCategory>
// 定义一个“PreferenceCategory”偏好设置类别元素用于对设置项进行分组归类这里通过“android:key”属性赋予了它一个唯一的标识“pref_sync_account_key”可以在代码中通过这个标识来操作该分组相关内容但目前该分组内部暂时没有具体的设置项为空可能后续会添加与同步账号相关的设置项进去。
<PreferenceCategory>
<CheckBoxPreference
android:key="pref_key_bg_random_appear"
android:title="@string/preferences_bg_random_appear_title"
android:defaultValue="false" />
android:key="pref_key_bg_random_appear"
android:title="@string/preferences_bg_random_appear_title"
android:defaultValue="false" />
</PreferenceCategory>
// 又定义了一个“PreferenceCategory”偏好设置类别元素用于对另外一组设置项进行分组不过这里没有给它设置“android:key”属性相对来说是一个比较通用的分组。
// 在这个分组内部有一个“CheckBoxPreference”复选框偏好设置元素
// 它通过“android:key”属性被赋予了“pref_key_bg_random_appear”这个唯一标识方便在代码中对这个复选框进行操作和状态获取等
// 通过“android:title”属性引用了名为“preferences_bg_random_appear_title”的字符串资源通常在 strings.xml 文件中定义具体文本内容)来作为该复选框在界面上显示的标题,用于提示用户该复选框对应的功能含义;
// “android:defaultValue”属性被设置为“false”表示该复选框的默认初始状态是未选中状态用于确定该偏好设置在初次显示时的默认值情况。
//PreferenceScreen偏好设置屏幕元素的结束标签标志着整个偏好设置界面相关内容定义的结束
// 版权声明及开源许可相关注释,指出该代码文件的版权归属于 MiCode 开源社区(时间范围为 2010 - 2011 年),同时说明了此文件遵循 Apache License 2.0 开源许可协议,告知使用者需遵循该协议规定来使用此文件。
</PreferenceScreen>

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML声明指定该XML文件的版本为1.0字符编码采用UTF-8格式这是XML文件开头的标准写法用于告知解析器如何处理该文件内容 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
@ -14,14 +15,24 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- 版权声明及开源许可相关注释说明该代码文件的版权归属MiCode开源社区时间范围是2010 - 2011年以及所遵循的Apache License 2.0开源许可协议内容,告知使用者在符合该许可协议要求的情况下才能使用此文件 -->
<searchable
xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/search_label"
android:hint="@string/search_hint"
android:searchMode="queryRewriteFromText"
xmlns:android="http://schemas.android.com/apk/res/android"
android:label="@string/search_label"
android:hint="@string/search_hint"
android:searchMode="queryRewriteFromText"
android:searchSuggestAuthority="notes"
android:searchSuggestIntentAction="android.intent.action.VIEW"
android:searchSettingsDescription="@string/search_setting_description"
android:includeInGlobalSearch="true" />
android:searchSuggestAuthority="notes"
android:searchSuggestIntentAction="android.intent.action.VIEW"
android:searchSettingsDescription="@string/search_setting_description"
android:includeInGlobalSearch="true" />
<!-- 声明Android命名空间通过这个命名空间可以使用Android系统中定义的相关属性是在Android XML布局等文件中常用的声明方式 -->
<!-- 设置搜索功能在界面上显示的标签文本其具体内容通常引用自strings.xml文件中名为"search_label"的字符串资源,用于向用户标识搜索相关的功能 -->
<!-- 设置搜索输入框的提示文字会引用strings.xml文件里名为"search_hint"的字符串资源,用于提示用户输入搜索内容等操作 -->
<!-- 指定搜索模式为从输入的文本进行查询重写,意味着系统会根据用户输入的文本按照一定规则进行处理来执行搜索任务 -->
<!-- 设置搜索建议的授权来源为"notes",可能用于确定搜索建议数据的获取来源或者相关权限范围等,具体取决于应用的搜索功能实现逻辑 -->
<!-- 定义当用户点击搜索建议时执行的意图动作是"android.intent.action.VIEW",通常用于触发查看相关的操作,比如查看对应的搜索结果详情等 -->
<!-- 设置搜索设置的描述信息会引用strings.xml文件中名为"search_setting_description"的字符串资源,用于向用户简要说明搜索相关设置的情况 -->
<!-- 设置该搜索功能是否包含在全局搜索中,这里设置为"true",表示此搜索功能会参与到整个应用的全局搜索功能里 -->

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML 声明,表明此 XML 文件遵循的版本是 1.0,使用的字符编码格式为 UTF-8这是 XML 文件开头的标准声明形式,用于告知 XML 解析器按照相应规则来解析后续的文件内容 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
@ -14,10 +15,17 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- 版权声明及开源许可相关注释,说明该代码文件的版权归属于 MiCode 开源社区(时间范围是 2010 - 2011 年),并且告知使用者该文件遵循 Apache License 2.0 开源许可协议,意味着只有在符合该协议要求的情况下,才能合法使用此文件 -->
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget_2x"
android:minWidth="146dip"
android:minHeight="146dip">
xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget_2x"
android:minWidth="146dip"
android:minHeight="146dip">
</appwidget-provider>
<!-- 声明 Android 命名空间,通过这个命名空间的声明,后续可以使用 Android 系统中定义好的各种属性来对 App Widget桌面小部件进行配置这是 Android 中 XML 配置文件里常用的声明方式 -->
<!-- 指定 App Widget桌面小部件初始显示时所采用的布局资源这里引用了名为“widget_2x”的布局文件通常在 layout 文件夹下定义),用于确定小部件首次显示在桌面等位置时的界面样式 -->
<!-- 设置 App Widget桌面小部件的最小宽度为 146 设备独立像素dip用于限定小部件在宽度方向上可被缩放的最小尺寸确保其在不同屏幕密度的设备上都能有合适的显示效果 -->
<!-- 设置 App Widget桌面小部件的最小高度为 146 设备独立像素dip与设置最小宽度类似用于限定小部件在高度方向上可被缩放的最小尺寸保证其在各类屏幕密度的设备上呈现出合理的外观形态 -->
<!-- 结束“appwidget-provider”标签标志着对 App Widget桌面小部件相关配置的定义结束 -->

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- XML 声明,用于表明该 XML 文件遵循的版本是 1.0,并且采用的字符编码格式为 UTF-8这是 XML 文件开头必备的标准声明部分,作用是告知 XML 解析器按照相应的版本和编码规则来处理后续的文件内容 -->
<!-- Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
@ -14,10 +15,17 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- 版权声明及开源许可相关注释,清晰地指出该 XML 文件的版权归属于 MiCode 开源社区,其版权生效时间范围是 2010 至 2011 年。同时还说明了该文件遵循 Apache License 2.0 开源许可协议,这意味着使用者若要使用此文件,必须严格按照该协议规定的要求来操作 -->
<appwidget-provider
xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget_4x"
android:minWidth="294dip"
android:minHeight="294dip">
xmlns:android="http://schemas.android.com/apk/res/android"
android:initialLayout="@layout/widget_4x"
android:minWidth="294dip"
android:minHeight="294dip">
</appwidget-provider>
<!-- 这是声明 Android 命名空间的语句,通过指定该命名空间,后续在这个 XML 标签内就能使用 Android 系统预定义的各类属性来对 App Widget桌面小部件进行相应的配置它是 Android 应用开发中 XML 配置文件里常用的一种声明方式 -->
<!-- 用于指定 App Widget桌面小部件在初始状态下显示所采用的布局资源这里引用的是名为“widget_4x”的布局文件一般该布局文件会存放在项目的 layout 文件夹内),以此来确定小部件首次出现在桌面等展示位置时的具体界面样式 -->
<!-- 此属性设置了 App Widget桌面小部件的最小宽度为 294 设备独立像素dip其目的在于限定小部件在不同屏幕密度的设备上进行宽度缩放时的最小尺寸范围确保小部件在各种屏幕条件下都能有一个合适的、符合视觉效果的宽度展示 -->
<!-- 该属性用于设定 App Widget桌面小部件的最小高度为 294 设备独立像素dip和设置最小宽度的作用类似主要是为了对小部件在高度方向上的缩放进行限制保障其在不同屏幕的设备上能呈现出合理且美观的高度外观形态 -->
<!-- 此为“appwidget-provider”标签的结束标记表示针对 App Widget桌面小部件相关配置信息的定义到此结束 -->

@ -2,14 +2,19 @@
buildscript {
repositories {
jcenter()
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.2'
//classpath 'com.android.tools.build:gradle:2.3.2'
classpath 'com.android.tools.build:gradle:7.4.2'
}
}
allprojects {
repositories {
jcenter()
google()
mavenCentral()
}
}

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip

Loading…
Cancel
Save