dhc 8 months ago
parent 2d5aa76fff
commit f774eb5fda

@ -0,0 +1,202 @@
/*
* 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.
*/
// 包声明表明该类所在的包名为net.micode.notes.ui
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;
// AlarmAlertActivity类继承自Activity实现了OnClickListener和OnDismissListener接口用于处理闹钟提醒相关的界面展示、声音播放以及用户交互等功能
public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener {
// 用于存储关联笔记的ID可能是触发闹钟提醒的那个笔记对应的唯一标识符
private long mNoteId;
// 用于存储笔记的内容摘要,会进行一定长度限制处理后展示给用户
private String mSnippet;
// 定义内容摘要的最大预览长度为60个字符
private static final int SNIPPET_PREW_MAX_LEN = 60;
// MediaPlayer对象用于播放闹钟提醒的声音
MediaPlayer mPlayer;
// Activity创建时调用的方法进行一系列初始化操作
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设置Activity无标题栏去除默认的标题显示
requestWindowFeature(Window.FEATURE_NO_TITLE);
final Window win = getWindow();
// 设置窗口属性使得在屏幕锁定时也能显示该Activity方便用户看到提醒信息
win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
// 如果屏幕当前处于关闭状态,添加一系列窗口标志,确保屏幕能亮起并保持亮起等相关显示设置
if (!isScreenOn()) {
win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
}
// 获取启动该Activity的意图Intent从中获取相关数据
Intent intent = getIntent();
try {
// 从意图的数据部分解析出笔记的ID假设数据路径中第二个元素是笔记ID具体格式由传入意图决定
mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1));
// 通过DataUtils工具类根据笔记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;
} catch (IllegalArgumentException e) {
// 如果解析数据出现异常,打印异常堆栈信息,并直接返回,不进行后续操作
e.printStackTrace();
return;
}
// 创建MediaPlayer对象用于后续播放声音
mPlayer = new MediaPlayer();
// 检查该笔记是否在笔记数据库中可见通过DataUtils工具类的方法判断如果可见则展示操作对话框并播放闹钟声音否则直接结束该Activity
if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) {
showActionDialog();
playAlarmSound();
} else {
finish();
}
}
// 判断屏幕是否处于开启状态的方法通过获取PowerManager服务并调用其isScreenOn方法来判断
private boolean isScreenOn() {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
return pm.isScreenOn();
}
// 用于播放闹钟提醒声音的方法
private void playAlarmSound() {
// 获取系统默认的闹钟铃声的Uri
Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM);
// 获取系统中静音模式影响的音频流设置值
int silentModeStreams = Settings.System.getInt(getContentResolver(),
Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0);
// 根据静音模式设置情况来设置MediaPlayer的音频流类型如果静音模式影响到闹钟音频流则使用相应设置否则使用默认的闹钟音频流类型
if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM))!= 0) {
mPlayer.setAudioStreamType(silentModeStreams);
} else {
mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
}
try {
// 设置MediaPlayer的数据源为获取到的铃声Uri
mPlayer.setDataSource(this, url);
// 准备MediaPlayer使其处于可播放状态可能涉及加载音频资源等操作
mPlayer.prepare();
// 设置循环播放,让闹钟声音一直响,直到被停止
mPlayer.setLooping(true);
// 启动MediaPlayer开始播放闹钟声音
mPlayer.start();
} catch (IllegalArgumentException e) {
// 捕获并打印参数异常相关的堆栈信息,如果出现此类异常
e.printStackTrace();
} catch (SecurityException e) {
// 捕获并打印安全相关异常的堆栈信息,如果出现此类异常
e.printStackTrace();
} catch (IllegalStateException e) {
// 捕获并打印非法状态相关异常的堆栈信息,如果出现此类异常
e.printStackTrace();
} catch (IOException e) {
// 捕获并打印I/O相关异常的堆栈信息如果出现此类异常
e.printStackTrace();
}
}
// 用于显示操作对话框的方法,对话框中包含提醒相关的信息以及操作按钮等
private void showActionDialog() {
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
// 设置对话框的标题,使用应用名称作为标题(从资源文件中获取对应字符串)
dialog.setTitle(R.string.app_name);
// 设置对话框显示的消息内容,为前面获取并处理后的笔记内容摘要
dialog.setMessage(mSnippet);
// 设置对话框的“确定”按钮文本及点击监听器实现了OnClickListener接口
dialog.setPositiveButton(R.string.notealert_ok, this);
// 如果屏幕处于开启状态,设置“进入”按钮文本及点击监听器,方便用户进一步操作(比如进入笔记编辑界面等)
if (isScreenOn()) {
dialog.setNegativeButton(R.string.notealert_enter, this);
}
// 显示对话框并设置对话框关闭时的监听器实现了OnDismissListener接口
dialog.show().setOnDismissListener(this);
}
// 实现OnClickListener接口的方法处理对话框按钮点击事件
public void onClick(DialogInterface dialog, int which) {
switch (which) {
// 如果点击的是“进入”按钮(一般对应负向按钮,具体由设置顺序决定)
case DialogInterface.BUTTON_NEGATIVE:
// 创建一个启动NoteEditActivity的意图设置动作为查看ACTION_VIEW并传入笔记的ID作为额外数据
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra(Intent.EXTRA_UID, mNoteId);
// 启动NoteEditActivity让用户可以查看或编辑对应的笔记内容
startActivity(intent);
break;
default:
break;
}
}
// 实现OnDismissListener接口的方法当对话框关闭时调用
public void onDismiss(DialogInterface dialog) {
// 停止播放闹钟声音并释放MediaPlayer相关资源
stopAlarmSound();
// 结束当前的AlarmAlertActivity完成整个闹钟提醒相关的操作流程
finish();
}
// 用于停止播放闹钟声音并释放MediaPlayer资源的方法
private void stopAlarmSound() {
if (mPlayer!= null) {
// 停止MediaPlayer播放
mPlayer.stop();
// 释放MediaPlayer占用的资源
mPlayer.release();
// 将MediaPlayer对象置为null避免后续误操作
mPlayer = null;
}
}
}

@ -0,0 +1,227 @@
/*
* 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.
*/
// 包声明表明该类所在的包名为net.micode.notes.ui
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.text.TextUtils;
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;
// NoteItemData类用于封装笔记相关的数据信息它从数据库游标Cursor中获取各项笔记数据并提供了一系列方法来对外提供这些数据以及判断笔记在列表中的相关位置等情况
// 方便在展示笔记列表等场景下对笔记的各项属性进行操作和判断。
public class NoteItemData {
// 定义查询数据库时使用的投影(即要查询的列),用于获取笔记相关的多个属性信息,这些列对应了笔记表中的各个字段,
// 通过指定这些列可以从数据库中获取需要的数据来构建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,
};
// 定义列索引常量方便后续从游标Cursor中准确获取对应列的数据每个常量对应着PROJECTION数组中列的顺序位置
// 通过这些索引可以清晰地从游标中提取出笔记的各个属性值如ID、提醒日期、背景颜色ID等信息。
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;
// 笔记的唯一标识符从数据库游标中获取用于区分不同的笔记记录在后续对笔记进行各种操作如查找、更新、删除等可通过该ID来定位具体的笔记。
private long mId;
// 笔记的提醒日期(以时间戳形式表示),从游标中获取,用于判断笔记是否设置了提醒以及何时提醒,方便在相关业务逻辑中根据提醒日期进行相应的提醒功能实现。
private long mAlertDate;
// 笔记的背景颜色ID从游标中获取对应着笔记在展示时所使用的背景颜色资源标识通过该ID可以设置笔记在界面上的背景颜色样式。
private int mBgColorId;
// 笔记的创建日期(以时间戳形式表示),从游标中获取,可用于展示笔记的创建时间信息,例如在列表中显示笔记创建的先后顺序等情况。
private long mCreatedDate;
// 用于标记笔记是否有附件,从游标中获取对应的数据并转换为布尔值,方便判断笔记是否关联了其他附件资源,如图片、文件等,以决定是否展示附件相关的提示或操作入口。
private boolean mHasAttachment;
// 笔记的最后修改日期(以时间戳形式表示),从游标中获取,常用于展示笔记的更新情况,例如提示用户笔记最近一次修改的时间等,便于用户了解笔记的变动历史。
private long mModifiedDate;
// 与笔记相关的数量信息(具体含义可能根据业务场景而定,也许是关联的子笔记数量等情况),从游标中获取,可用于一些统计或展示相关的逻辑处理,例如显示某个文件夹下笔记的总数等。
private int mNotesCount;
// 笔记所属的父级ID如所属文件夹的ID等从游标中获取用于构建笔记的层级关系方便进行分类管理和展示例如在文件夹视图中确定笔记归属的文件夹。
private long mParentId;
// 笔记的摘要信息(通常是笔记内容的简短描述,可能是开头部分内容等),从游标中获取,并进行一些预处理(去除特定标记字符串)后存储,
// 可用于在列表中简要展示笔记内容,让用户快速了解笔记大致情况。
private String mSnippet;
// 笔记的类型标识,从游标中获取,用于区分不同类型的笔记(如普通笔记、系统笔记、文件夹等不同类型),根据类型可以进行不同的业务逻辑处理和展示方式。
private int mType;
// 笔记关联的小部件ID如果有的话从游标中获取用于处理笔记与桌面小部件之间的关联关系例如更新小部件显示内容时根据该ID找到对应的笔记数据。
private int mWidgetId;
// 笔记关联的小部件类型标识,从游标中获取,用于区分不同类型的小部件(可能对应不同的布局、功能等),以便在与小部件交互时进行相应的适配操作。
private int mWidgetType;
// 通话记录相关笔记对应的联系人姓名若笔记属于通话记录类型根据父级ID判断则通过相关工具方法尝试获取对应的联系人姓名若获取不到则使用电话号码代替
// 用于在展示通话记录笔记时显示更友好的联系人信息,方便用户识别。
private String mName;
// 通话记录相关笔记对应的电话号码若笔记属于通话记录类型根据父级ID判断则通过相关工具方法尝试获取对应的电话号码用于展示通话记录相关的关键信息以及后续可能的拨打电话等操作。
private String mPhoneNumber;
// 用于标记该笔记数据对应的笔记是否是列表中的最后一项,通过游标判断是否处于最后位置来设置该标记,方便在列表展示等场景下进行边界判断和相关样式处理。
private boolean mIsLastItem;
// 用于标记该笔记数据对应的笔记是否是列表中的第一项,通过游标判断是否处于第一位置来设置该标记,同样便于在列表展示等场景下进行边界判断和相关样式处理。
private boolean mIsFirstItem;
// 用于标记该笔记数据对应的笔记是否是列表中唯一的一项通过判断游标中的记录总数是否为1来设置该标记可用于一些特殊的展示逻辑例如只有一个笔记时的全屏展示等情况。
private boolean mIsOnlyOneItem;
// 用于标记该笔记是否是某个文件夹下仅跟随的一个笔记,通过检查笔记类型以及前后位置关系等来判断,在列表展示和操作逻辑中可用于区分不同的排列情况。
private boolean mIsOneNoteFollowingFolder;
// 用于标记该笔记是否是某个文件夹下跟随的多个笔记之一,通过检查笔记类型以及前后位置关系等来判断,同样有助于在列表展示和操作逻辑中对笔记的排列情况进行准确判断和处理。
private boolean mIsMultiNotesFollowingFolder;
// 构造函数用于创建NoteItemData实例接收上下文Context和数据库游标Cursor作为参数
// 从游标中按照定义的列索引提取各项笔记数据,并进行一些必要的预处理和位置相关情况的判断,以初始化该实例的各个属性。
public NoteItemData(Context context, Cursor cursor) {
// 从游标中获取笔记的ID并赋值给对应的成员变量作为该笔记的唯一标识。
mId = cursor.getLong(ID_COLUMN);
// 从游标中获取笔记的提醒日期,并赋值给对应的成员变量,用于后续提醒相关的逻辑判断。
mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN);
// 从游标中获取笔记的背景颜色ID并赋值给对应的成员变量以便后续设置笔记的背景颜色样式。
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);
// 从游标中获取笔记所属的父级ID并赋值给对应的成员变量用于构建笔记的层级关系。
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);
// 从游标中获取笔记关联的小部件ID并赋值给对应的成员变量用于处理笔记与小部件之间的关联关系。
mWidgetId = cursor.getInt(WIDGET_ID_COLUMN);
// 从游标中获取笔记关联的小部件类型标识,并赋值给对应的成员变量,用于区分不同类型的小部件进行相应的适配操作。
mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN);
// 初始化电话号码为空字符串,后续根据笔记是否属于通话记录类型来尝试获取对应的电话号码信息。
mPhoneNumber = "";
// 判断如果笔记所属的父级ID是通话记录文件夹的IDNotes.ID_CALL_RECORD_FOLDER则尝试通过DataUtils工具类方法
// 根据笔记ID从内容解析器getContentResolver获取对应的电话号码信息并赋值给mPhoneNumber成员变量。
if (mParentId == Notes.ID_CALL_RECORD_FOLDER) {
mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId);
// 如果获取到的电话号码不为空即成功获取到了电话号码则通过Contact类的方法尝试获取对应的联系人姓名
// 如果获取到的联系人姓名为null可能不存在对应的联系人记录等情况则使用电话号码作为显示名称赋值给mName成员变量。
if (!TextUtils.isEmpty(mPhoneNumber)) {
mName = Contact.getContact(context, mPhoneNumber);
if (mName == null) {
mName = mPhoneNumber;
}
}
}
// 如果最终获取到的联系人姓名仍为null例如前面获取过程出现问题或者本身就没有对应联系人则将姓名设置为空字符串确保成员变量有合理的默认值。
if (mName == null) {
mName = "";
}
// 调用方法检查该笔记在列表中的位置相关情况,设置对应的位置标记成员变量,以便后续在列表展示等场景下进行相关判断和处理。
checkPostion(cursor);
}
// 私有方法,用于检查当前笔记在游标所代表的列表中的位置相关情况,通过游标提供的方法判断是否是第一、最后一项,以及是否是某个文件夹下的唯一笔记、
// 仅跟随一个笔记或者跟随多个笔记等情况,并设置相应的成员变量标记,方便在其他方法中对外提供这些位置判断信息。
private void checkPostion(Cursor cursor) {
// 通过游标判断是否处于最后位置将结果赋值给mIsLastItem成员变量标记该笔记是否是列表中的最后一项。
mIsLastItem = cursor.isLast()? true : false;
// 通过游标判断是否处于第一位置将结果赋值给mIsFirstItem成员变量标记该笔记是否是列表中的第一项。
mIsFirstItem = cursor.isFirst()? true : false;
// 通过判断游标中的记录总数是否为1将结果赋值给mIsOnlyOneItem成员变量标记该笔记是否是列表中唯一的一项。
mIsOnlyOneItem = (cursor.getCount() == 1);
// 初始化为false表示默认该笔记不是某个文件夹下跟随多个笔记的情况后续根据具体判断逻辑进行更新。
mIsMultiNotesFollowingFolder = false;
// 初始化为false表示默认该笔记不是某个文件夹下仅跟随一个笔记的情况后续根据具体判断逻辑进行更新。
mIsOneNoteFollowingFolder = false;
// 如果当前笔记的类型是普通笔记类型Notes.TYPE_NOTE且不是列表中的第一项因为要判断前面的记录情况则进行以下位置相关的判断逻辑。
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) {
// 判断游标中的记录总数是否大于当前位置索引加1即当前位置后面还有记录如果是则表示当前笔记是某个文件夹下跟随的多个笔记之一
// 设置mIsMultiNotesFollowingFolder成员变量为true标记该情况。
if (cursor.getCount() > (position + 1)) {
mIsMultiNotesFollowingFolder = true;
} else {
// 如果后面没有其他记录了则表示当前笔记是某个文件夹下仅跟随的一个笔记设置mIsOneNoteFollowingFolder成员变量为true标记该情况。
mIsOneNoteFollowingFolder = true;
}
}
// 将游标再移回原来的位置(即当前笔记所在的位置),确保游标位置的正确性,不影响后续其他操作对游标的使用。
if (!cursor.moveToNext()) {
throw new IllegalStateException("cursor move to previous but can't move back");
}
}
}
}
// 用于判断该笔记是否是某个文件夹下仅跟随的一个笔记,返回对应的成员变量标记值,外部可通过调用该方法来获取此位置判断信息,
// 用于在列表展示等场景下进行不同的样式或操作逻辑处理,例如针对仅跟随一个笔记的情况显示特殊的提示等。
public boolean isOneFollowingFolder() {
return mIsOneNoteFollowingFolder;
}
// 用于判断该笔记是否是某个文件夹下跟随的多个笔记之一,返回对应的成员变量标记值,方便外部获取此位置判断信息,
// 在列表展示和操作逻辑中可根据该情况进行相应的处理,比如分组展示等操作。
public boolean isMultiFollowingFolder() {
return mIsMultiNotesFollowingFolder;
}
// 用于判断该笔记是否是列表中的最后一项,返回对应的成员变量标记值,外部可通过调用该方法来获取此位置判断信息,
// 常用于列表滚动、分页等相关逻辑中,例如判断是否加载下一页数据等情况。
public boolean isLast() {
return mIsLastItem;
}
// 用于获取通话记录笔记对应的联系人姓名(如果是通话记录类型笔记),返回存储的联系人姓名成员变量值,方便在展示通话记录笔记时显示相应的联系人信息,
// 提高信息展示的友好性,便于用户识别通话记录相关的笔记内容。
public String getCallName() {
return mName;
}
// 用于判断该笔记是否是列表中的第一项,返回对应的成员变量标记值,外部可通过调用该方法来获取此位置判断信息,
// 在列表展示等

@ -0,0 +1,305 @@
// 内部类实现了ListView.MultiChoiceModeListener和OnMenuItemClickListener接口用于处理ListView的多选模式相关的各种操作逻辑
// 比如创建多选模式下的菜单、处理菜单项点击、选项状态改变以及退出多选模式等操作通过与NotesListAdapter等配合实现对笔记的批量操作功能。
private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener {
private DropdownMenu mDropDownMenu;
private ActionMode mActionMode;
private MenuItem mMoveMenu;
// 当多选模式创建时调用该方法,用于初始化多选模式下的菜单、设置菜单项的可见性和点击监听器等操作,
// 例如根据笔记所在文件夹等情况决定“移动”菜单项是否可见,同时设置自定义的视图用于展示下拉菜单等内容。
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
// 填充多选模式下的菜单布局R.menu.note_list_options到传入的菜单对象中使得菜单显示相应的选项。
getMenuInflater().inflate(R.menu.note_list_options, menu);
// 为“删除”菜单项设置点击监听器当点击该菜单项时会触发此内部类中对应的点击处理逻辑通过实现OnMenuItemClickListener接口
menu.findItem(R.id.delete).setOnMenuItemClickListener(this);
mMoveMenu = menu.findItem(R.id.move);
// 如果当前长按选中的笔记数据项所在的父文件夹是通话记录文件夹或者用户创建的文件夹数量为0那么隐藏“移动”菜单项
// 因为在这些情况下可能不适合或无法进行移动操作。
if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER
|| DataUtils.getUserFolderCount(mContentResolver) == 0) {
mMoveMenu.setVisible(false);
} else {
mMoveMenu.setVisible(true);
// 为“移动”菜单项设置点击监听器,以便后续处理点击该菜单项时的相应逻辑。
mMoveMenu.setOnMenuItemClickListener(this);
}
mActionMode = mode;
// 通知笔记列表适配器进入多选模式,以便适配器进行相应的数据和视图状态更新,例如标记选中的项等。
mNotesListAdapter.setChoiceMode(true);
// 设置ListView不再响应长按事件因为此时已经进入了多选模式长按相关逻辑由多选模式的操作来处理。
mNotesListView.setLongClickable(false);
// 隐藏“新建笔记”按钮,在多选模式下通常不需要该按钮显示,避免操作冲突等情况。
mAddNewNote.setVisibility(View.GONE);
// 通过LayoutInflater加载一个自定义的视图R.layout.note_list_dropdown_menu用于作为多选模式下的自定义视图
// 这个视图可能包含一些额外的交互元素,比如下拉菜单等。
View customView = LayoutInflater.from(NotesListActivity.this).inflate(
R.layout.note_list_dropdown_menu, null);
// 将自定义视图设置到ActionMode中使其在界面上显示出来替代默认的ActionMode样式。
mode.setCustomView(customView);
mDropDownMenu = new DropdownMenu(NotesListActivity.this,
(Button) customView.findViewById(R.id.selection_menu),
R.menu.note_list_dropdown);
// 为下拉菜单设置点击监听器,当点击下拉菜单中的菜单项时,会执行相应的逻辑,这里用于处理全选/反选操作以及更新菜单显示状态。
mDropDownMenu.setOnDropdownMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
// 调用笔记列表适配器的方法,实现全选或反选已选笔记的功能,根据当前的选中状态进行切换。
mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected());
// 更新菜单相关的显示状态,比如更新全选按钮的文本和选中状态等。
updateMenu();
return true;
}
});
return true;
}
// 更新菜单相关显示状态的私有方法,比如根据已选笔记的数量更新下拉菜单的标题,以及更新全选菜单项的选中状态和文本等,
// 使得菜单显示能准确反映当前的多选情况。
private void updateMenu() {
int selectedCount = mNotesListAdapter.getSelectedCount();
// 获取格式化后的字符串资源,用于设置下拉菜单的标题,显示当前选中的笔记数量等信息。
String format = getResources().getString(R.string.menu_select_title, selectedCount);
mDropDownMenu.setTitle(format);
MenuItem item = mDropDownMenu.findItem(R.id.action_select_all);
if (item!= null) {
if (mNotesListAdapter.isAllSelected()) {
item.setChecked(true);
item.setTitle(R.string.menu_deselect_all);
} else {
item.setChecked(false);
item.setTitle(R.string.menu_select_all);
}
}
}
// 在准备多选模式下的菜单时调用每次菜单显示前可能会调用此方法来更新菜单项状态等目前此方法未实现具体逻辑直接返回false
// 可根据实际需求后续添加相应代码来动态调整菜单的准备工作,比如根据不同条件禁用或启用某些菜单项等。
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// TODO Auto-generated method stub
return false;
}
// 处理多选模式下菜单项点击事件的方法,根据点击的菜单项执行相应的操作逻辑,目前此方法还未完整实现具体的业务逻辑,
// 后续需根据不同菜单项的功能需求添加代码来实现诸如删除、移动所选笔记等操作。
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
// TODO Auto-generated method stub
return false;
}
// 当多选模式销毁时调用的方法用于清理相关的状态和设置比如通知笔记列表适配器退出多选模式恢复ListView的长按响应功能
// 以及重新显示“新建笔记”按钮等,确保界面回到正常的交互状态。
public void onDestroyActionMode(ActionMode mode) {
mNotesListAdapter.setChoiceMode(false);
mNotesListView.setLongClickable(true);
mAddNewNote.setVisibility(View.VISIBLE);
}
// 结束当前的多选模式调用ActionMode的finish方法来关闭多选模式相关的界面和操作状态
// 通常在完成批量操作或者取消操作时调用此方法来退出多选模式。
public void finishActionMode() {
mActionMode.finish();
}
// 当多选模式下笔记的选中状态发生改变时调用的方法,用于通知笔记列表适配器更新对应笔记项的选中状态,
// 并调用updateMenu方法来更新菜单的显示状态以保持界面与数据状态的一致性。
public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
boolean checked) {
mNotesListAdapter.setCheckedItem(position, checked);
updateMenu();
}
// 处理菜单项点击事件的方法,根据点击的具体菜单项执行相应的业务逻辑,比如点击“删除”菜单项时弹出确认对话框进行笔记删除操作,
// 点击“移动”菜单项时启动查询目标文件夹的操作等,实现对所选笔记的各种批量操作功能。
public boolean onMenuItemClick(MenuItem item) {
// 如果没有选中任何笔记项即选中数量为0则弹出提示Toast告知用户需要先选择笔记然后直接返回不执行后续逻辑。
if (mNotesListAdapter.getSelectedCount() == 0) {
Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none),
Toast.LENGTH_SHORT).show();
return true;
}
switch (item.getItemId()) {
case R.id.delete:
// 创建一个AlertDialog.Builder用于构建确认删除的对话框设置对话框的标题、图标、提示信息等内容。
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.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_notes,
mNotesListAdapter.getSelectedCount()));
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
// 点击确认按钮后调用batchDelete方法执行批量删除所选笔记的操作。
batchDelete();
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.show();
break;
case R.id.move:
// 点击“移动”菜单项时调用startQueryDestinationFolders方法启动查询目标文件夹的操作
// 以便后续将所选笔记移动到指定的文件夹中。
startQueryDestinationFolders();
break;
default:
return false;
}
return true;
}
}
// 内部类实现了OnTouchListener接口用于处理“新建笔记”按钮的触摸事件比如判断触摸位置是否在按钮的特定透明区域
// 以及根据触摸动作按下、移动、抬起等进行相应的事件分发和处理逻辑例如将触摸事件分发给ListView等操作。
private class NewNoteOnTouchListener implements OnTouchListener {
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
// 获取设备屏幕的默认显示对象,用于获取屏幕相关的尺寸信息,比如屏幕高度等。
Display display = getWindowManager().getDefaultDisplay();
int screenHeight = display.getHeight();
int newNoteViewHeight = mAddNewNote.getHeight();
int start = screenHeight - newNoteViewHeight;
int eventY = start + (int) event.getY();
/**
* Minus TitleBar's height
* 如果当前处于子文件夹状态,需要减去标题栏的高度,因为坐标计算等可能需要基于去除标题栏后的区域来进行,
* 以更准确地判断触摸位置等情况。
*/
if (mState == ListEditState.SUB_FOLDER) {
eventY -= mTitleBar.getHeight();
start -= mTitleBar.getHeight();
}
/**
* HACKME:When click the transparent part of "New Note" button, dispatch
* the event to the list view behind this button. The transparent part of
* "New Note" button could be expressed by formula y=-0.12x+94Unit:pixel
* and the line top of the button. The coordinate based on left of the "New
* Note" button. The 94 represents maximum height of the transparent part.
* Notice that, if the background of the button changes, the formula should
* also change. This is very bad, just for the UI designer's strong requirement.
* 以下是一段临时解决办法HACKME的代码逻辑当点击“新建笔记”按钮的透明部分时将触摸事件分发给按钮后面的ListView进行处理。
* 透明部分的范围通过一个公式y = -0.12x + 94单位为像素坐标基于按钮左侧以及按钮顶部边界来界定
* 并且提示如果按钮背景改变该公式也需要相应更改这种处理方式不太理想只是为了满足UI设计师的特定要求。
*/
if (event.getY() < (event.getX() * (-0.12) + 94)) {
View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1
- mNotesListView.getFooterViewsCount());
if (view!= null && view.getBottom() > start
&& (view.getTop() < (start + 94))) {
mOriginY = (int) event.getY();
mDispatchY = eventY;
event.setLocation(event.getX(), mDispatchY);
mDispatch = true;
return mNotesListView.dispatchTouchEvent(event);
}
}
break;
}
case MotionEvent.ACTION_MOVE: {
if (mDispatch) {
mDispatchY += (int) event.getY() - mOriginY;
event.setLocation(event.getX(), mDispatchY);
return mNotesListView.dispatchTouchEvent(event);
}
break;
}
default: {
if (mDispatch) {
event.setLocation(event.getX(), mDispatchY);
mDispatch = false;
return mNotesListView.dispatchTouchEvent(event);
}
break;
}
}
return false;
}
};
// 启动异步查询笔记列表数据的方法根据当前所在文件夹的ID来构建合适的查询条件然后通过异步查询处理实例mBackgroundQueryHandler
// 发起对笔记列表数据的查询操作,确保在后台线程进行数据库查询,不阻塞主线程,提高应用的响应性能。
private void startAsyncNotesListQuery() {
String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER)? ROOT_FOLDER_SELECTION
: NORMAL_SELECTION;
mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null,
Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[]{
String.valueOf(mCurrentFolderId)
}, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC");
}
// 内部类继承自AsyncQueryHandler用于在后台线程处理数据库查询操作并在查询完成后通过回调方法处理相应的结果
// 根据不同的查询令牌token区分是查询笔记列表还是文件夹列表等情况然后执行对应的后续操作比如更新适配器数据等。
private final class BackgroundQueryHandler extends AsyncQueryHandler {
public BackgroundQueryHandler(ContentResolver contentResolver) {
super(contentResolver);
}
@Override
protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
switch (token) {
case FOLDER_NOTE_LIST_QUERY_TOKEN:
// 如果是查询笔记列表的操作完成调用笔记列表适配器的方法传入查询得到的游标Cursor数据
// 以便适配器更新界面上显示的笔记列表内容,反映最新的数据情况。
mNotesListAdapter.changeCursor(cursor);
break;
case FOLDER_LIST_QUERY_TOKEN:
if (cursor!= null && cursor.getCount() > 0) {
// 如果是查询文件夹列表操作完成且查询结果不为空即有文件夹数据则调用showFolderListMenu方法展示文件夹列表菜单
// 方便用户选择操作的目标文件夹等。
showFolderListMenu(cursor);
} else {
Log.e(TAG, "Query folder failed");
}
break;
default:
return;
}
}
}
// 展示文件夹列表菜单的方法通过AlertDialog.Builder构建一个包含文件夹列表的对话框使用FoldersListAdapter作为适配器来展示文件夹数据
// 并为对话框的列表项点击事件设置监听器当用户点击某个文件夹时执行将所选笔记移动到该文件夹的操作并弹出相应的提示Toast告知用户操作结果
// 最后关闭多选模式相关的操作界面。
private void showFolderListMenu(Cursor cursor) {
AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this);
builder.setTitle(R.string.menu_title_select_folder);
final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor);
builder.setAdapter(adapter, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
DataUtils.batchMoveToFolder(mContentResolver,
mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which));
Toast.makeText(
NotesListActivity.this,
getString(R.string.format_move_notes_to_folder,
mNotesListAdapter.getSelectedCount(),
adapter.getFolderName(NotesListActivity.this, which)),
Toast.LENGTH_SHORT).show();
mModeCallBack.finishActionMode();
}
});
builder.show();
}
// 创建新笔记的方法构建一个启动NoteEditActivity的Intent设置相应的动作Intent.ACTION_INSERT_OR_EDIT以及传递当前所在文件夹的ID作为额外参数
// 然后通过startActivityForResult方法启动该Activity以便在新建笔记操作完成后能获取返回结果并进行相应的数据更新等处理。
private void createNewNote() {
Intent intent = new Intent(this, NoteEditActivity.class);
intent.setAction(Intent.ACTION_INSERT_OR_EDIT);
intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId);
this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE);
}
// 批量删除所选笔记的方法通过AsyncTask在后台线程执行删除操作根据是否处于同步模式通过isSyncMode方法判断来决定是直接删除笔记还是将笔记移动到回收站文件夹
// 在操作完成后onPostExecute方法中如果涉及到与小部件相关的数据更新比如所选笔记关联了小部件则调用updateWidget方法更新相应的小部件显示内容
// 最后关闭多选模式相关的操作界面。
private void batchDelete() {
new AsyncTask<Void, Void, HashSet<AppWidgetAttribute>>() {
protected HashSet<AppWidgetAttribute> doInBackground(Void... unused) {
HashSet<AppWidgetAttribute> widgets = mNotesListAdapter.getSelectedWidget();
if (!isSyncMode()) {
// 如果未处于同步模式直接调用DataUtils的方法批量删除所选笔记这里

@ -0,0 +1,240 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 按照许可要求使用此文件,否则不允许使用。
* 可以通过以下网址获取许可证副本:
* http://www.apache.org/licenses/LICENSE-2.0
*
* 除非适用法律要求或书面同意,软件依据许可证分发是“按现状”分发,
* 不附带任何明示或暗示的保证或条件。请查看许可证了解具体权限和限制。
*/
// 所在包声明表明该类属于笔记应用net.micode.notes的用户界面ui相关模块。
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import net.micode.notes.data.Notes;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
// NotesListAdapter类继承自CursorAdapter是专门用于适配笔记列表数据与视图如ListView的适配器类
// 负责处理笔记数据展示、管理笔记项的选中状态等相关操作逻辑,是笔记列表界面展示与数据交互的重要组成部分。
public class NotesListAdapter extends CursorAdapter {
// 定义一个静态的字符串常量,作为日志输出时的标签,方便在查看日志时快速识别与该适配器相关的日志信息,利于调试与问题排查。
private static final String TAG = "NotesListAdapter";
// 用于保存上下文对象,通过构造函数传入,在整个适配器的生命周期内提供与应用环境相关的资源访问、视图创建等操作所需的上下文环境。
private Context mContext;
// 使用HashMap来存储笔记项在列表中的选中状态Integer类型为笔记项在列表中的位置索引Boolean类型表示对应位置的笔记项是否被选中
// 以此来跟踪用户在笔记列表中的多选操作情况,方便后续根据选中状态进行相应的数据处理和界面更新。
private HashMap<Integer, Boolean> mSelectedIndex;
// 记录笔记的数量(仅统计实际的笔记类型数据项数量,不包含文件夹等其他非笔记类型的数据),
// 该数量会在数据内容变化(如游标更新、数据重新加载)时通过重新计算得到,用于辅助判断如全选等相关业务逻辑。
private int mNotesCount;
// 用于标识当前适配器是否处于多选模式的布尔变量,通过设置该变量来控制适配器在不同模式下的数据处理、视图显示等行为逻辑,
// 例如在多选模式下需正确显示已选笔记项的选中状态、提供相应的批量操作菜单等。
private boolean mChoiceMode;
// 内部静态类用于封装与应用小部件Widget相关的属性信息主要包含小部件的唯一标识ID和小部件的类型信息
// 在涉及笔记与小部件关联的相关操作中,用于传递和处理小部件的相关属性数据。
public static class AppWidgetAttribute {
public int widgetId;
public int widgetType;
};
// 构造方法接收一个Context上下文对象调用父类CursorAdapter的构造方法并传入上下文以及初始化为null的游标对象
// 同时初始化用于记录选中状态的HashMap保存传入的上下文对象并将笔记数量初始化为0完成适配器的初始化设置。
public NotesListAdapter(Context context) {
super(context, null);
mSelectedIndex = new HashMap<Integer, Boolean>();
mContext = context;
mNotesCount = 0;
}
// 重写CursorAdapter的抽象方法用于创建新的视图对象该方法在需要为新的笔记项创建视图进行展示时被调用
// 此处简单地返回一个NotesListItem类型的视图实例通常NotesListItem是自定义的视图类用于具体实现笔记内容在界面上的展示布局等细节逻辑。
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new NotesListItem(context);
}
// 重写CursorAdapter的抽象方法负责将数据绑定到已创建的视图上使其能够正确展示笔记信息
// 首先判断传入的视图是否是NotesListItem类型如果是则从游标中解析出笔记数据并封装成NoteItemData对象
// 然后调用NotesListItem的bind方法将上下文、笔记数据对象、当前的多选模式状态以及该笔记项是否被选中的状态传递进去
// 从而实现将数据与视图进行绑定,确保笔记信息准确显示在对应的视图上。
@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()));
}
}
// 用于设置指定位置笔记项的选中状态的公有方法接收笔记项在列表中的位置索引int类型和要设置的选中状态布尔值作为参数
// 将对应的位置和选中状态存入mSelectedIndex这个HashMap中之后调用notifyDataSetChanged方法通知适配器数据集已发生变化
// 触发视图的刷新操作,使得界面上该笔记项的选中状态显示能够根据新设置的状态进行更新,例如改变选中项的背景颜色等视觉效果。
public void setCheckedItem(final int position, final boolean checked) {
mSelectedIndex.put(position, checked);
notifyDataSetChanged();
}
// 用于判断当前适配器是否处于多选模式的公有方法直接返回表示多选模式状态的成员变量mChoiceMode的值
// 外部代码可以通过调用该方法来了解适配器当前所处的交互模式状态,进而执行与之对应的不同操作逻辑,例如在多选模式下显示特定的操作菜单等。
public boolean isInChoiceMode() {
return mChoiceMode;
}
// 用于设置适配器的多选模式状态的公有方法接收一个布尔值参数当进入多选模式时传入true
// 先清空之前记录的所有笔记项选中状态通过调用mSelectedIndex的clear方法然后将mChoiceMode变量设置为传入的模式值
// 此后适配器会根据新的模式状态调整相关的数据处理和视图交互逻辑,例如更新界面上笔记项的选中显示效果等。
public void setChoiceMode(boolean mode) {
mSelectedIndex.clear();
mChoiceMode = mode;
}
// 用于实现全选或全不选所有笔记项的公有方法,接收一个布尔值参数,根据该参数决定是将所有笔记项设置为选中还是取消选中状态,
// 首先获取当前适配器关联的游标对象通过调用getCursor方法然后遍历游标中的所有笔记项通过getCount方法获取笔记项总数并移动游标到每个位置
// 对于每个位置的笔记项通过NoteItemData.getNoteType方法判断其类型是否为笔记与Notes.TYPE_NOTE进行比较如果是笔记类型则调用setCheckedItem方法设置其选中状态。
public void selectAll(boolean checked) {
Cursor cursor = getCursor();
for (int i = 0; i < getCount(); i++) {
if (cursor.moveToPosition(i)) {
if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) {
setCheckedItem(i, checked);
}
}
}
}
// 用于获取所有被选中笔记项的ID集合的公有方法创建一个HashSet<Long>类型的集合用于存储笔记项的ID
// 遍历mSelectedIndex这个记录选中状态的HashMap对于值为true即被选中的项通过调用getItemId方法获取其对应的笔记项ID
// 接着进行合法性判断如果获取到的ID等于Notes.ID_ROOT_FOLDER通常根文件夹ID不应作为笔记项被选中此处作为一种异常情况的判断
// 则在日志中输出相应提示信息使用Log.d方法输出调试级别的日志否则将合法的ID添加到HashSet集合中最后返回该包含所有选中笔记项ID的集合
// 方便外部代码基于这些ID进行批量操作如批量删除、批量移动等操作时确定具体操作的笔记对象。
public HashSet<Long> getSelectedItemIds() {
HashSet<Long> itemSet = new HashSet<Long>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
Long id = getItemId(position);
if (id == Notes.ID_ROOT_FOLDER) {
Log.d(TAG, "Wrong item id, should not happen");
} else {
itemSet.add(id);
}
}
}
return itemSet;
}
// 用于获取所有被选中笔记项关联的小部件属性集合的公有方法创建一个HashSet<AppWidgetAttribute>类型的集合用于存储小部件相关属性对象,
// 遍历mSelectedIndex这个记录选中状态的HashMap对于值为true即被选中的项通过调用getItem方法获取对应的游标对象代表选中的笔记项数据
// 如果游标不为空则创建一个AppWidgetAttribute对象从游标数据中解析出小部件的ID和类型信息通过NoteItemData类的相关方法获取并赋值给该对象
// 然后将其添加到HashSet集合中最后返回包含所有选中笔记项关联小部件属性的集合该集合可用于后续与小部件相关的批量更新等操作
// 注意按照代码中的注释说明,此处不会关闭游标,游标关闭操作由适配器统一管理,以确保数据访问的正确性和一致性。
public HashSet<AppWidgetAttribute> getSelectedWidget() {
HashSet<AppWidgetAttribute> itemSet = new HashSet<AppWidgetAttribute>();
for (Integer position : mSelectedIndex.keySet()) {
if (mSelectedIndex.get(position) == true) {
Cursor c = (Cursor) getItem(position);
if (c!= null) {
AppWidgetAttribute widget = new AppWidgetAttribute();
NoteItemData item = new NoteItemData(mContext, c);
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;
}
}
}
return itemSet;
}
// 用于获取当前被选中笔记项数量的公有方法首先获取mSelectedIndex中所有值的集合即所有笔记项的选中状态值集合Collection<Boolean>类型),
// 如果该集合为空表示没有任何笔记项设置了选中状态则直接返回0否则通过迭代器Iterator<Boolean>)遍历该集合,
// 统计值为true即被选中的元素个数最后返回统计得到的选中笔记项数量方便外部代码根据选中数量进行相应的逻辑判断或操作提示等。
public int getSelectedCount() {
Collection<Boolean> values = mSelectedIndex.values();
if (null == values) {
return 0;
}
Iterator<Boolean> iter = values.iterator();
int count = 0;
while (iter.hasNext()) {
if (true == iter.next()) {
count++;
}
}
return count;
}
// 用于判断是否所有笔记项都被选中的公有方法首先通过调用getSelectedCount方法获取当前被选中笔记项的数量
// 然后判断如果选中数量不为0即至少有一个笔记项被选中且选中数量等于笔记的总数量通过比较与mNotesCount的值则返回true表示处于全选状态否则返回false
// 可用于在界面上显示全选相关的操作提示或进行与全选状态相关的业务逻辑处理等情况。
public boolean isAllSelected() {
int checkedCount = getSelectedCount();
return (checkedCount!= 0 && checkedCount == mNotesCount);
}
// 用于判断指定位置的笔记项是否被选中的公有方法通过获取mSelectedIndex中对应位置的选中状态值来判断
// 如果该位置的值为null可能是该位置的笔记项还未设置过选中状态则返回false表示未被选中否则返回对应位置记录的布尔值即是否被选中的状态
// 方便外部代码在处理具体的笔记项交互逻辑(如点击某个笔记项时)判断其当前的选中情况,进而执行相应的操作逻辑。
public boolean isSelectedItem(final int position) {
if (null == mSelectedIndex.get(position)) {
return false;
}
return mSelectedIndex.get(position);
}
// 重写父类CursorAdapter的方法当数据集的内容发生变化时例如数据库中的笔记数据有更新、插入或删除等操作导致游标所关联的数据改变会被调用
// 在此方法中先调用父类的onContentChanged方法执行默认的内容改变处理逻辑然后调用calcNotesCount私有方法重新计算笔记的总数量
// 以确保后续基于笔记数量的相关业务逻辑(如全选判断等)能够基于最新的数据情况进行正确操作。
@Override
protected void onContentChanged() {
super.onContentChanged();
calcNotesCount();
}
// 重写父类CursorAdapter的方法用于更改适配器所使用的游标对象通常意味着数据来源发生了变化比如重新从数据库查询获取了新的笔记数据
// 先调用父类的changeCursor方法完成游标切换的默认操作然后调用calcNotesCount私有方法重新计算笔记的总数量
// 使得适配器能够基于新的游标数据更新界面展示,并保证相关数据统计逻辑的准确性,确保后续各种基于笔记数据的操作和显示都是基于最新的有效数据。
@Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
calcNotesCount();
}
// 私有方法用于计算笔记的总数量首先将mNotesCount初始化为0然后遍历游标中的所有笔记项通过getCount方法获取笔记项总数并移动游标到每个位置
// 对于每个笔记项通过NoteItemData.getNoteType方法判断其类型是否为笔记与Notes.TYPE_NOTE进行比较如果是笔记类型则将笔记总数量加1
// 在遍历过程中如果遇到游标为无效的情况即游标为null则通过Log.e方法输出错误日志信息并直接返回不再继续计算笔记数量
// 以此保证数量统计的准确性是基于有效的游标数据进行的,避免因无效游标导致的错误计算或异常情况。
private void calcNotesCount() {
mNotesCount = 0;
for (int i = 0; i < getCount(); i++) {
Cursor c = (Cursor) getItem(i);
if (c!= null) {
if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) {
mNotesCount++;
}
} else {
Log.e(TAG, "Invalid cursor");
return;
}
}
}
}

@ -0,0 +1,179 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 按照许可要求使用此文件,否则不允许使用。
* 可以通过以下网址获取许可证副本:
* http://www.apache.org/licenses/LICENSE-2.0
*
* 除非适用法律要求或书面同意,软件依据许可证分发是“按现状”分发,
* 不附带任何明示或暗示的保证或条件。请查看许可证了解具体权限和限制。
*/
// 所在包声明表明该类属于笔记应用net.micode.notes的用户界面ui相关模块。
package net.micode.notes.ui;
import android.content.Context;
import android.text.format.DateUtils;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
// NotesListItem类继承自LinearLayout代表笔记列表中的每一个列表项视图负责展示笔记相关的各种信息如标题、时间、提醒图标等
// 并根据不同的笔记类型(如普通笔记、通话记录文件夹、普通文件夹等)以及是否处于多选模式等情况来调整显示内容和样式。
public class NotesListItem extends LinearLayout {
// 用于显示提醒图标的ImageView控件根据笔记是否设置提醒等情况来决定其显示与否以及显示的图标资源帮助用户直观了解笔记的提醒状态。
private ImageView mAlert;
// 用于显示笔记标题的TextView控件会根据笔记类型等因素展示不同格式的标题内容比如普通笔记展示摘要内容文件夹展示文件夹名称及包含文件数量等。
private TextView mTitle;
// 用于显示笔记最后修改时间的TextView控件通过调用DateUtils工具类将时间戳转换为相对时间格式如“几分钟前”“昨天”等进行展示方便用户了解笔记的更新情况。
private TextView mTime;
// 用于显示通话记录相关名称的TextView控件在涉及通话记录的笔记项中展示通话对方名称等信息在其他类型笔记项中通常设置为不可见。
private TextView mCallName;
// 用于存储当前列表项对应的笔记数据对象,方便在其他方法中获取笔记的详细信息来进行展示和逻辑处理,比如获取笔记类型、是否有提醒等属性。
private NoteItemData mItemData;
// 用于表示笔记项是否被选中的CheckBox控件在多选模式下根据笔记项的选中状态显示勾选与否非多选模式下则隐藏该控件用于用户进行多选操作的可视化交互。
private CheckBox mCheckBox;
// 构造方法接收一个Context上下文对象调用父类LinearLayout的构造方法传入上下文然后通过inflate方法加载指定的布局文件R.layout.note_item到当前视图中
// 最后通过findViewById方法找到布局文件中对应的各个子视图控件如ImageView、TextView、CheckBox等并赋值给相应的成员变量完成视图的初始化工作。
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);
}
// 用于绑定数据和设置视图显示状态的方法,接收上下文对象、笔记数据对象、是否处于多选模式的布尔值以及当前笔记项是否被选中的布尔值作为参数,
// 根据这些参数来调整视图中各个控件的显示内容和可见性等状态,使得笔记项能够正确展示对应笔记的数据信息以及符合当前交互模式(多选或非多选)的外观效果。
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
// 如果处于多选模式且当前笔记数据对象的类型为普通笔记Notes.TYPE_NOTE则将CheckBox控件设置为可见并根据传入的选中状态参数设置其勾选状态
// 以便用户能看到该笔记项是否已被选中否则将CheckBox控件隐藏例如在非多选模式或者对于非笔记类型的数据项不需要显示选中状态。
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(checked);
} else {
mCheckBox.setVisibility(View.GONE);
}
// 将传入的笔记数据对象赋值给成员变量mItemData方便后续在其他方法中获取该笔记的详细信息进行处理例如设置背景等操作需要用到笔记相关属性。
mItemData = data;
// 如果当前笔记数据对象的ID等于通话记录文件夹的IDNotes.ID_CALL_RECORD_FOLDER说明当前列表项对应的是通话记录文件夹
// 则进行如下设置:
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
// 隐藏用于显示通话名称的TextView控件因为通话记录文件夹不需要显示通话对方名称。
mCallName.setVisibility(View.GONE);
// 设置提醒图标ImageView为可见用于展示通话记录文件夹对应的特定图标通常用于区分不同类型的文件夹等
mAlert.setVisibility(View.VISIBLE);
// 设置标题TextView的文本外观样式为主要项的样式通过资源ID指定R.style.TextAppearancePrimaryItem使其在视觉上突出显示。
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
// 设置标题TextView的文本内容拼接通话记录文件夹的名称字符串通过资源字符串获取以及包含的文件数量信息通过格式化字符串展示调用getString和format_folder_files_count方法
// 让用户直观了解通话记录文件夹的基本情况。
mTitle.setText(context.getString(R.string.call_record_folder_name)
+ context.getString(R.string.format_folder_files_count, data.getNotesCount()));
// 设置提醒图标ImageView的资源图片为通话记录对应的图标R.drawable.call_record使其显示正确的图标样式来代表通话记录文件夹。
mAlert.setImageResource(R.drawable.call_record);
}
// 如果当前笔记数据对象的父ID等于通话记录文件夹的IDNotes.ID_CALL_RECORD_FOLDER说明当前列表项对应的是通话记录文件夹下的笔记
// 则进行如下设置:
else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) {
// 显示用于显示通话名称的TextView控件并设置其文本内容为笔记数据中获取的通话对方名称以便展示通话相关的具体信息。
mCallName.setVisibility(View.VISIBLE);
mCallName.setText(data.getCallName());
// 设置标题TextView的文本外观样式为次要项的样式通过资源ID指定R.style.TextAppearanceSecondaryItem使其与主要项如文件夹标题等在视觉上有所区分。
mTitle.setTextAppearance(context, R.style.TextAppearanceSecondaryItem);
// 设置标题TextView的文本内容为格式化后的笔记摘要信息通过DataUtils工具类的getFormattedSnippet方法获取格式化后的摘要内容展示笔记的主要内容摘要。
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
// 如果笔记数据对象表示有提醒设置通过hasAlert方法判断则设置提醒图标ImageView为可见并设置其图片资源为提醒对应的图标R.drawable.clock展示提醒状态
// 否则将提醒图标ImageView隐藏表示无提醒设置。
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
}
// 如果当前笔记数据对象既不是通话记录文件夹也不是其下的笔记(即其他普通类型的笔记或文件夹等情况),则进行如下设置:
else {
// 隐藏用于显示通话名称的TextView控件因为不需要展示通话相关信息。
mCallName.setVisibility(View.GONE);
// 设置标题TextView的文本外观样式为主要项的样式通过资源ID指定R.style.TextAppearancePrimaryItem使其在视觉上突出显示。
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
// 如果笔记数据对象的类型是文件夹Notes.TYPE_FOLDER则设置标题TextView的文本内容为文件夹的摘要信息通过getSnippet方法获取拼接包含的文件数量信息通过格式化字符串展示
// 展示文件夹名称及其中包含的笔记数量情况同时将提醒图标ImageView隐藏因为文件夹通常不需要展示提醒相关图标。
if (data.getType() == Notes.TYPE_FOLDER) {
mTitle.setText(data.getSnippet()
+ context.getString(R.string.format_folder_files_count,
data.getNotesCount()));
mAlert.setVisibility(View.GONE);
}
// 如果笔记数据对象的类型是普通笔记非文件夹类型则设置标题TextView的文本内容为格式化后的笔记摘要信息通过DataUtils工具类的getFormattedSnippet方法获取格式化后的摘要内容展示笔记的主要内容摘要
// 并且根据笔记是否有提醒设置通过hasAlert方法判断来决定提醒图标ImageView的显示与否及显示的图标资源有提醒则显示提醒图标无提醒则隐藏与前面处理类似。
else {
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
}
}
// 设置用于显示时间的TextView控件的文本内容通过DateUtils工具类的getRelativeTimeSpanString方法将笔记数据中的最后修改时间戳转换为相对时间格式的字符串进行展示
// 方便用户直观了解笔记的最近更新时间情况。
mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate()));
// 调用setBackground方法根据笔记数据对象来设置当前列表项的背景样式使得不同类型的笔记或文件夹等在界面上能有不同的背景视觉效果增强区分度和美观性。
setBackground(data);
}
// 私有方法,用于根据笔记数据对象来设置当前列表项的背景资源样式,根据笔记类型以及其在列表中的位置等相关属性来选择合适的背景资源进行设置,
// 以实现不同笔记项在界面上展示不同的背景效果,例如普通笔记可能根据是否为首尾项、是否单独等情况设置不同背景,文件夹则统一设置对应的文件夹背景样式。
private void setBackground(NoteItemData data) {
int id = data.getBgColorId();
// 如果笔记数据对象的类型是普通笔记Notes.TYPE_NOTE则根据笔记在列表中的位置等相关属性进一步判断来设置不同的背景资源
if (data.getType() == Notes.TYPE_NOTE) {
// 如果笔记是单独的可能是列表中唯一的一个笔记或者与前后笔记在某些逻辑上是独立的通过isSingle方法判断或者是某个文件夹后的单独一个笔记通过isOneFollowingFolder方法判断
// 则设置当前列表项的背景资源为对应单独笔记的背景资源通过NoteItemBgResources工具类的getNoteBgSingleRes方法获取资源ID并设置使其有特定的视觉效果。
if (data.isSingle() || data.isOneFollowingFolder()) {
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id));
}
// 如果笔记是列表中的最后一个笔记通过isLast方法判断则设置当前列表项的背景资源为对应最后一个笔记的背景资源通过NoteItemBgResources工具类的getNoteBgLastRes方法获取资源ID并设置展示相应的外观效果。
else if (data.isLast()) {
setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id));
}
// 如果笔记是列表中的第一个笔记通过isFirst方法判断或者是某个文件夹后的多个笔记中的第一个通过isMultiFollowingFolder方法判断
// 则设置当前列表项的背景资源为对应第一个笔记的背景资源通过NoteItemBgResources工具类的getNoteBgFirstRes方法获取资源ID并设置呈现对应的视觉样式。
else if (data.isFirst() || data.isMultiFollowingFolder()) {
setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id));
}
// 如果笔记不属于上述几种特殊位置情况即普通的中间笔记则设置当前列表项的背景资源为普通笔记的默认背景资源通过NoteItemBgResources工具类的getNoteBgNormalRes方法获取资源ID并设置保持统一的视觉效果。
else {
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
}
}
// 如果笔记数据对象的类型不是普通笔记即文件夹等其他类型则统一设置当前列表项的背景资源为文件夹对应的背景资源通过NoteItemBgResources工具类的getFolderBgRes方法获取资源ID并设置
// 使得文件夹在界面上有统一的外观样式与普通笔记区分开来。
else {
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
}
// 用于获取当前列表项对应的笔记数据对象的方法,外部代码可以通过调用该方法获取到绑定在该列表项上的笔记详细信息,方便进行其他相关的逻辑处理或数据传递操作。
public NoteItemData getItemData() {
return mItemData;
}
}

@ -0,0 +1,147 @@
/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* 按照许可要求使用此文件,否则不允许使用。
* 可以通过以下网址获取许可证副本:
* http://www.apache.org/licenses/LICENSE-2.0
*
* 除非适用法律要求或书面同意,软件依据许可证分发是“按现状”分发,
* 不附带任何明示或暗示的保证或条件。请查看许可证了解具体权限和限制。
*/
// 所在包声明表明该类属于笔记应用net.micode.notes的用户界面ui相关模块。
package net.micode.notes.ui;
import android.content.Context;
import android.text.format.DateUtils;
import android.view.View;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.ResourceParser.NoteItemBgResources;
// NotesListItem类继承自LinearLayout作为笔记列表中单个列表项的视图类负责呈现笔记各方面信息如标题、时间、提醒标识等
// 会依据笔记具体类型(普通笔记、通话记录文件夹、普通文件夹等)以及是否处于多选模式来灵活调整各部分的显示内容与样式。
public class NotesListItem extends LinearLayout {
// 用于展示提醒图标的ImageView根据笔记是否设置提醒决定其显示与否及显示的具体图标辅助用户知晓笔记的提醒情况。
private ImageView mAlert;
// 展示笔记标题的TextView会依照笔记类型不同展示相应格式的标题内容像普通笔记显示摘要文件夹显示名称及包含文件数量等。
private TextView mTitle;
// 显示笔记最后修改时间的TextView借助DateUtils将时间戳转换为相对时间格式如“几分钟前”等展示便于用户掌握笔记更新情况。
private TextView mTime;
// 针对通话记录相关笔记用于显示通话对方名称的TextView在其他类型笔记中通常设为不可见。
private TextView mCallName;
// 存储当前列表项对应的笔记数据对象,方便后续获取笔记详细属性来处理展示逻辑等操作。
private NoteItemData mItemData;
// 多选模式下用于表示笔记项是否被选中的CheckBox在相应模式下依据选中状态显示勾选情况非多选模式则隐藏实现多选交互的可视化。
private CheckBox mCheckBox;
// 构造方法,接收上下文对象,初始化视图相关操作:
// 1. 调用父类LinearLayout构造方法传入上下文。
// 2. 通过inflate加载指定布局文件R.layout.note_item到当前视图。
// 3. 利用findViewById找到布局内各子视图控件并赋值给对应成员变量完成视图初始化。
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);
}
// 绑定数据与设置视图显示状态的方法,根据传入参数调整各控件显示内容和可见性,确保正确展示笔记信息并适配交互模式外观:
public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) {
// 根据多选模式及笔记类型决定CheckBox的显示与勾选状态
if (choiceMode && data.getType() == Notes.TYPE_NOTE) {
mCheckBox.setVisibility(View.VISIBLE);
mCheckBox.setChecked(checked);
} else {
mCheckBox.setVisibility(View.GONE);
}
// 保存传入的笔记数据对象,供后续操作使用。
mItemData = data;
// 依据笔记数据对象的不同情况设置各控件显示内容和可见性:
if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) {
// 当前为通话记录文件夹时的设置:
mCallName.setVisibility(View.GONE);
mAlert.setVisibility(View.VISIBLE);
mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem);
mTitle.setText(context.getString(R.string.call_record_folder_name)
+ 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);
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
} else {
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()));
mAlert.setVisibility(View.GONE);
} else {
// 若是普通笔记类型,设置标题为格式化摘要内容,并根据提醒设置决定提醒图标的显示。
mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet()));
if (data.hasAlert()) {
mAlert.setImageResource(R.drawable.clock);
mAlert.setVisibility(View.VISIBLE);
} else {
mAlert.setVisibility(View.GONE);
}
}
}
// 设置时间文本为相对时间格式字符串,展示笔记最后修改时间。
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()) {
setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id));
} else if (data.isLast()) {
setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id));
} else if (data.isFirst() || data.isMultiFollowingFolder()) {
setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id));
} else {
setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id));
}
} else {
// 非普通笔记(如文件夹)统一设置对应的文件夹背景资源。
setBackgroundResource(NoteItemBgResources.getFolderBgRes());
}
}
// 对外提供获取当前列表项对应的笔记数据对象的方法,方便外部代码获取笔记详细信息用于其他逻辑处理或数据传递。
public NoteItemData getItemData() {
return mItemData;
}
}

@ -0,0 +1,88 @@
/*
* 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.
*/
// 包声明表明该类所在的包名为net.micode.notes.ui
package net.micode.notes.ui;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
// AlarmInitReceiver类继承自BroadcastReceiver用于接收系统广播并执行与闹钟初始化相关的操作比如根据笔记的提醒时间设置相应的闹钟
public class AlarmInitReceiver extends BroadcastReceiver {
// 定义查询数据库时使用的投影即要查询的列用于获取笔记的ID和提醒日期这两列信息
private static final String [] PROJECTION = new String [] {
NoteColumns.ID,
NoteColumns.ALERTED_DATE
};
// 定义列索引常量方便后续从游标Cursor中获取对应列的数据这里分别对应ID列和提醒日期列的索引
private static final int COLUMN_ID = 0;
private static final int COLUMN_ALERTED_DATE = 1;
// 重写BroadcastReceiver的onReceive方法当该广播接收器接收到匹配的广播时会被调用在此方法中执行具体的闹钟初始化逻辑
@Override
public void onReceive(Context context, Intent intent) {
// 获取当前的系统时间(以毫秒为单位),用于后续与笔记的提醒日期进行比较筛选
long currentDate = System.currentTimeMillis();
// 通过ContentResolver查询数据库获取满足特定条件的笔记记录游标Cursor
// 查询条件是提醒日期大于当前日期且笔记类型为普通笔记Notes.TYPE_NOTE的记录只获取前面定义的投影列信息
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);
// 创建一个用于触发闹钟提醒的意图Intent指定其对应的广播接收类为AlarmReceiver
Intent sender = new Intent(context, AlarmReceiver.class);
// 设置意图的数据部分将笔记的ID附加到对应的内容URI上用于标识具体是哪个笔记的闹钟提醒
sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID)));
// 创建一个PendingIntent用于包装前面的sender意图使得可以在合适的时间由系统触发该意图对应的广播
// 这里使用的请求码为0具体的触发情况等由后续设置闹钟时关联使用
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0);
// 获取系统的AlarmManager服务用于设置闹钟相关操作
AlarmManager alermManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
// 通过AlarmManager设置闹钟使用RTC_WAKEUP模式表示在指定的绝对时间唤醒设备来触发闹钟提醒
// 指定提醒的时间为前面获取的笔记提醒日期关联的PendingIntent就是前面创建的用于触发广播的那个
alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent);
} while (c.moveToNext());
}
// 关闭游标,释放相关资源,避免内存泄漏等问题
c.close();
}
}
}

@ -0,0 +1,40 @@
/*
* 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.
*/
// 包声明表明该类所在的包名为net.micode.notes.ui
package net.micode.notes.ui;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
// AlarmReceiver类继承自BroadcastReceiverBroadcastReceiver用于接收系统发送的广播消息在Android中常用于实现各种系统事件的响应逻辑例如这里用于响应闹钟触发相关的广播。
public class AlarmReceiver extends BroadcastReceiver {
// 重写BroadcastReceiver的onReceive方法该方法会在接收到与之匹配的广播时被调用在此类中主要用于启动与闹钟提醒相关的Activity即AlarmAlertActivity
@Override
public void onReceive(Context context, Intent intent) {
// 通过Intent的setClass方法将原本传入的意图intent的目标类修改为AlarmAlertActivity.class也就是将广播意图重新定向到用于展示闹钟提醒详细信息的Activity。
intent.setClass(context, AlarmAlertActivity.class);
// 给意图添加FLAG_ACTIVITY_NEW_TASK标志这是因为BroadcastReceiver接收到广播时所处的上下文环境可能没有与之关联的任务栈Task
// 添加该标志可以确保启动的Activity能够在一个新的任务栈中被创建并展示给用户避免因缺少任务栈而导致启动失败的问题。
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 使用传入的上下文context来启动经过上述设置后的意图对应的Activity也就是启动AlarmAlertActivity让其展示闹钟提醒的相关界面例如提醒内容摘要、操作按钮等供用户进行后续操作。
context.startActivity(intent);
}
}

@ -0,0 +1,245 @@
/*
* 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.
*/
// 包声明表明该类所在的包名为net.micode.notes.ui
package net.micode.notes.ui;
import java.text.DateFormatSymbols;
import java.util.Calendar;
import net.micode.notes.R;
import android.content.Context;
import android.text.format.DateFormat;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.NumberPicker;
// DateTimePicker类继承自FrameLayout它是一个自定义的视图组件用于实现日期和时间的选择功能用户可以通过该组件方便地设置年、月、日、时、分以及选择12/24小时制等。
public class DateTimePicker extends FrameLayout {
// 定义默认的启用状态默认为启用true用于控制整个DateTimePicker组件及其内部子控件的可交互状态。
private static final boolean DEFAULT_ENABLE_STATE = true;
// 定义半天的小时数用于12小时制相关的时间计算和判断例如区分上午和下午的时间范围。
private static final int HOURS_IN_HALF_DAY = 12;
// 定义一天的总小时数用于24小时制相关的操作和判断。
private static final int HOURS_IN_ALL_DAY = 24;
// 定义一周的天数,用于日期选择器(如显示一周内的日期选项等)相关的操作。
private static final int DAYS_IN_ALL_WEEK = 7;
// 定义日期选择器NumberPicker的最小值通常从0开始表示一周内日期选项的最小索引。
private static final int DATE_SPINNER_MIN_VAL = 0;
// 定义日期选择器NumberPicker的最大值根据一周7天最大值为6索引从0开始
private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1;
// 定义24小时制视图下小时选择器NumberPicker的最小值即0点。
private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0;
// 定义24小时制视图下小时选择器NumberPicker的最大值即23点。
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23;
// 定义12小时制视图下小时选择器NumberPicker的最小值通常为112小时制习惯从1开始计数
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1;
// 定义12小时制视图下小时选择器NumberPicker的最大值即12点。
private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12;
// 定义分钟选择器NumberPicker的最小值即0分钟。
private static final int MINUT_SPINNER_MIN_VAL = 0;
// 定义分钟选择器NumberPicker的最大值即59分钟。
private static final int MINUT_SPINNER_MAX_VAL = 59;
// 定义上午/下午AM/PM选择器NumberPicker的最小值通常为0对应AM
private static final int AMPM_SPINNER_MIN_VAL = 0;
// 定义上午/下午AM/PM选择器NumberPicker的最大值通常为1对应PM
private static final int AMPM_SPINNER_MAX_VAL = 1;
// 用于显示日期的NumberPicker控件用户可以通过它选择具体的日期以一周内的某一天来表示
private final NumberPicker mDateSpinner;
// 用于显示小时的NumberPicker控件根据设置的12/24小时制显示相应范围的小时选项供用户选择。
private final NumberPicker mHourSpinner;
// 用于显示分钟的NumberPicker控件提供0到59分钟的选项供用户选择。
private final NumberPicker mMinuteSpinner;
// 用于显示上午/下午AM/PM的NumberPicker控件在12小时制下用于区分时间段仅在非24小时制时可见。
private final NumberPicker mAmPmSpinner;
// Calendar对象用于存储和操作当前选择的日期和时间信息方便进行各种日期时间的计算和设置。
private Calendar mDate;
// 用于存储一周内各天显示名称的字符串数组,例如“周一”“周二”等,会根据系统设置进行本地化显示。
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
// 用于标记当前时间是否处于上午AM根据小时数等情况进行更新用于12小时制下的显示和逻辑判断。
private boolean mIsAm;
// 用于标记是否处于24小时制视图true表示24小时制false表示12小时制决定了小时选择器和上午/下午选择器的显示及相关逻辑。
private boolean mIs24HourView;
// 用于存储组件的启用状态,初始化为默认的启用状态,通过相应方法可以修改并控制组件及其内部控件的可操作性。
private boolean mIsEnabled = DEFAULT_ENABLE_STATE;
// 用于标记是否处于初始化阶段,在初始化过程中一些逻辑处理可能与正常使用阶段有所不同,避免不必要的重复操作或异常情况。
private boolean mInitialising;
// 定义一个接口类型的变量用于设置日期时间改变时的回调监听器外部类可以实现该接口来监听用户在DateTimePicker上操作导致的日期时间变化情况。
private OnDateTimeChangedListener mOnDateTimeChangedListener;
// 内部类实现了NumberPicker.OnValueChangeListener接口用于监听日期选择器mDateSpinner的值变化事件。
// 当用户在日期选择器上选择了不同的日期时会触发该监听器的onValueChange方法进而更新内部的日期信息mDate以及相关的显示控件并通知外部监听器日期时间已改变。
private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
// 根据新选择的日期与旧日期的差值调整内部的Calendar对象mDate的日期信息实现日期的变更。
mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal);
// 更新日期显示相关的控件,确保界面上显示的日期信息是最新的。
updateDateControl();
// 调用onDateTimeChanged方法通知外部监听器日期时间已发生改变触发相应的回调逻辑如果有设置监听器的话
onDateTimeChanged();
}
};
// 内部类实现了NumberPicker.OnValueChangeListener接口用于监听小时选择器mHourSpinner的值变化事件。
// 当用户在小时选择器上选择了不同的小时值时会触发该监听器的onValueChange方法根据12/24小时制等不同情况进行日期、时间以及相关显示控件的更新并通知外部监听器日期时间已改变。
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) {
// 在12小时制下如果当前是下午!mIsAm且从11点HOURS_IN_HALF_DAY - 1切换到12点HOURS_IN_HALF_DAY则日期需要加一天标记日期已改变。
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;
}
// 在12小时制下如果当前是上午mIsAm且从12点HOURS_IN_HALF_DAY切换到11点HOURS_IN_HALF_DAY - 1则日期需要减一天标记日期已改变。
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;
}
// 如果是从11点切换到12点或者从12点切换到11点还需要切换上午/下午AM/PM的标记并更新对应的显示控件。
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 {
// 在24小时制下如果从23点HOURS_IN_ALL_DAY - 1切换到0点则日期需要加一天标记日期已改变。
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
}
// 在24小时制下如果从0点切换到23点则日期需要减一天标记日期已改变。
else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
}
// 根据当前选择的小时值以及上午/下午标记在12小时制下计算出用于设置到内部Calendar对象mDate的小时数并进行设置。
int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm? 0 : HOURS_IN_HALF_DAY);
mDate.set(Calendar.HOUR_OF_DAY, newHour);
// 通知外部监听器日期时间已发生改变,触发相应的回调逻辑(如果有设置监听器的话)。
onDateTimeChanged();
// 如果日期发生了改变更新当前的年、月、日信息到组件内部的Calendar对象mDate确保整体日期时间信息的一致性。
if (isDateChanged) {
setCurrentYear(cal.get(Calendar.YEAR));
setCurrentMonth(cal.get(Calendar.MONTH));
setCurrentDay(cal.get(Calendar.DAY_OF_MONTH));
}
}
};
// 内部类实现了NumberPicker.OnValueChangeListener接口用于监听分钟选择器mMinuteSpinner的值变化事件。
// 当用户在分钟选择器上选择了不同的分钟值时会触发该监听器的onValueChange方法根据分钟值的变化情况进行日期、时间以及相关显示控件的更新并通知外部监听器日期时间已改变。
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;
// 如果从最大分钟值59切换到最小分钟值0则小时数需要加1表示时间向后推移了一小时。
if (oldVal == maxValue && newVal == minValue) {
offset += 1;
}
// 如果从最小分钟值0切换到最大分钟值59则小时数需要减1表示时间向前推移了一小时。
else if (oldVal == minValue && newVal == maxValue) {
offset -= 1;
}
if (offset!= 0) {
// 根据分钟值变化导致的小时数偏移调整内部的Calendar对象mDate的小时信息实现时间的变更。
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();
}
}
// 设置内部的Calendar对象mDate的分钟信息为用户选择的新分钟值。
mDate.set(Calendar.MINUTE, newVal);
// 通知外部监听器日期时间已发生改变,触发相应的回调逻辑(如果有设置监听器的话)。
onDateTimeChanged();
}
};
// 内部类实现了NumberPicker.OnValueChangeListener接口用于监听上午/下午AM/PM选择器mAmPmSpinner的值变化事件。
// 当用户在上午/下午选择器上切换了选项时会触发该监听器的onValueChange方法相应地调整内部的日期时间信息以及更新相关显示控件并通知外部监听器日期时间已改变。
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mIsAm =!mIsAm;
// 如果切换到上午AM则将小时数减去12小时从下午时间转换到上午时间对应的小时调整
if (mIsAm) {
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY);
}
// 如果切换到下午PM则将小时数加上12小时从上午时间转换到下午时间对应的小时调整
else {
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY);
}
// 更新上午/下午显示相关的控件,确保界面上显示的信息与内部状态一致。
updateAmPmControl();
// 通知外部监听器日期时间已发生改变,触发相应的回调逻辑(如果有设置监听器的话)。
onDateTimeChanged();
}
};
// 定义一个接口用于外部类实现以便在DateTimePicker的日期时间发生改变时接收到通知并进行相应的处理。
public interface OnDateTimeChangedListener {
void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute);
}
// 构造函数创建一个DateTimePicker实例使用当前系统时间作为初始时间进行初始化。
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis());
}
// 构造函数创建一个DateTimePicker实例并使用指定的日期时间以毫秒为单位的时间戳作为初始时间进行初始化。
public DateTimePicker(Context context, long date) {
this(context, date, DateFormat.is24HourFormat(context));
}
// 构造函数创建一个DateTimePicker实例使用指定的日期时间以毫秒为单位的时间戳和是否为24小时制作为参数进行初始化这是最完整的初始化构造函数。
public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context);
// 获取一个Calendar实例用于存储和操作日期时间信息初始化为当前时间如果没有传入特定日期时间的话
mDate = Calendar.getInstance();
mInitialising = true;
// 根据当前小时数判断是否处于上午AM用于初始化上午/下午标记如果当前小时大于等于12则为下午即!mIsAm
mIsAm = getCurrentHourOfDay() >= HOURS

@ -0,0 +1,125 @@
/*
* 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.
*/
// 包声明表明该类所在的包名为net.micode.notes.ui
package net.micode.notes.ui;
import java.util.Calendar;
import net.micode.notes.R;
import net.micode.notes.ui.DateTimePicker;
import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
// DateTimePickerDialog类继承自AlertDialog同时实现了OnClickListener接口它是一个自定义的对话框类用于展示日期和时间选择的界面并在用户操作后返回选择的日期时间结果方便用户在应用中进行日期时间的设置操作。
public class DateTimePickerDialog extends AlertDialog implements OnClickListener {
// Calendar对象用于存储当前在对话框中显示和操作的日期时间信息初始化为当前系统时间后续会根据用户在日期时间选择器中的操作进行更新。
private Calendar mDate = Calendar.getInstance();
// 用于标记是否处于24小时制视图决定了日期时间选择器以及对话框标题中时间显示的格式12小时制或24小时制
private boolean mIs24HourView;
// 定义一个接口类型的变量,用于设置当用户在对话框中点击确定按钮并设置好日期时间后触发的回调监听器,外部类可实现该接口来处理用户选择的日期时间结果。
private OnDateTimeSetListener mOnDateTimeSetListener;
// 用于显示日期和时间选择界面的DateTimePicker实例用户通过它来实际操作选择具体的年、月、日、时、分等信息。
private DateTimePicker mDateTimePicker;
// 定义一个接口,供外部类实现,当用户在对话框中完成日期时间设置并点击确定按钮后,会调用该接口中的方法,将对话框实例以及选择的日期时间(以毫秒为单位的时间戳)传递给外部类进行后续处理。
public interface OnDateTimeSetListener {
void OnDateTimeSet(AlertDialog dialog, long date);
}
// 构造函数创建一个DateTimePickerDialog实例传入上下文Context以及初始的日期时间以毫秒为单位的时间戳参数用于初始化对话框的相关属性和显示内容。
public DateTimePickerDialog(Context context, long date) {
super(context);
// 创建一个DateTimePicker实例用于在对话框中展示日期时间选择的界面它内部包含了各种选择器如日期、小时、分钟等选择器供用户操作。
mDateTimePicker = new DateTimePicker(context);
// 将创建的DateTimePicker视图设置为对话框的主要显示内容使得对话框展示出日期时间选择的界面给用户。
setView(mDateTimePicker);
// 为DateTimePicker设置日期时间改变的监听器当用户在DateTimePicker上操作改变了日期、时间等信息时会触发该监听器的onDateTimeChanged方法。
mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() {
public void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
// 根据用户在DateTimePicker上选择的新日期时间信息更新内部存储的Calendar对象mDate的年、月、日、时、分等字段使其保持与用户选择一致。
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方法根据更新后的日期时间信息来更新对话框的标题使其显示当前选择的日期时间。
updateTitle(mDate.getTimeInMillis());
}
});
// 设置内部存储的Calendar对象mDate的时间为传入的初始日期时间并将秒数设置为0确保初始时间的准确性和一致性。
mDate.setTimeInMillis(date);
mDate.set(Calendar.SECOND, 0);
// 将DateTimePicker的当前显示日期时间设置为与内部存储的Calendar对象mDate一致保证初始界面显示的时间是传入的初始时间。
mDateTimePicker.setCurrentDate(mDate.getTimeInMillis());
// 设置对话框的“确定”按钮文本从资源文件中获取对应的字符串以及点击该按钮的监听器为当前类实例因为实现了OnClickListener接口当用户点击“确定”按钮时会触发onClick方法进行相应处理。
setButton(context.getString(R.string.datetime_dialog_ok), this);
// 设置对话框的“取消”按钮文本从资源文件中获取对应的字符串以及点击该按钮的监听器为null意味着点击“取消”按钮时不做额外的特定操作通常会关闭对话框
setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null);
// 根据系统当前的时间格式设置是否为24小时制来初始化对话框的时间显示格式调用set24HourView方法进行设置同时会影响DateTimePicker内部时间选择器的显示格式。
set24HourView(DateFormat.is24HourFormat(this.getContext()));
// 初次更新对话框的标题使其显示初始的日期时间信息调用updateTitle方法根据当前的日期时间来设置合适的标题内容。
updateTitle(mDate.getTimeInMillis());
}
// 用于设置对话框是否采用24小时制视图的方法通过传入的布尔值参数来更新内部的mIs24HourView标记并相应地影响日期时间选择器以及对话框标题中时间显示的格式。
public void set24HourView(boolean is24HourView) {
mIs24HourView = is24HourView;
}
// 用于设置当用户在对话框中点击确定按钮后触发的回调监听器的方法外部类可以通过传入实现了OnDateTimeSetListener接口的实例来处理用户选择的日期时间结果。
public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) {
mOnDateTimeSetListener = callBack;
}
// 私有方法用于更新对话框的标题内容根据传入的日期时间以毫秒为单位的时间戳以及当前设置的时间显示格式24小时制或12小时制等信息生成合适的标题字符串并设置给对话框。
private void updateTitle(long date) {
int flag =
DateUtils.FORMAT_SHOW_YEAR | // 设置显示年份的格式标志
DateUtils.FORMAT_SHOW_DATE | // 设置显示日期(月、日等)的格式标志
DateUtils.FORMAT_SHOW_TIME; // 设置显示时间(时、分等)的格式标志
// 根据是否为24小时制视图添加对应的时间格式标志用于控制时间在标题中的显示格式24小时制或12小时制
flag |= mIs24HourView? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_12HOUR;
// 使用DateUtils工具类的formatDateTime方法根据传入的上下文、日期时间以及格式标志等信息生成格式化后的日期时间字符串并设置为对话框的标题内容。
setTitle(DateUtils.formatDateTime(this.getContext(), date, flag));
}
// 实现OnClickListener接口的方法当用户点击对话框中的按钮如“确定”按钮时会被调用根据按钮的点击情况以及是否设置了OnDateTimeSetListener监听器进行相应的操作例如将用户选择的日期时间结果传递给外部监听器进行处理。
public void onClick(DialogInterface arg0, int arg1) {
if (mOnDateTimeSetListener!= null) {
// 如果设置了OnDateTimeSetListener监听器调用其OnDateTimeSet方法将当前对话框实例以及用户选择的日期时间通过内部存储的Calendar对象获取时间戳传递给外部类进行后续处理。
mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis());
}
}
}

@ -0,0 +1,101 @@
/*
* 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.
*/
// 包声明表明该类所在的包名为net.micode.notes.ui
package net.micode.notes.ui;
import android.content.Context;
import android.database.Cursor;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.LinearLayout;
import android.widget.TextView;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.NoteColumns;
// FoldersListAdapter类继承自CursorAdapter它是一个用于将数据库游标Cursor中的数据适配到ListView等可展示列表视图组件上的适配器类
// 在这里主要用于处理文件夹相关数据的展示,比如将文件夹的名称等信息展示在列表中。
public class FoldersListAdapter extends CursorAdapter {
// 定义查询数据库时使用的投影即要查询的列用于获取文件夹相关信息这里包含文件夹的ID和摘要可能用于显示文件夹名称等相关用途两列。
public static final String [] PROJECTION = {
NoteColumns.ID,
NoteColumns.SNIPPET
};
// 定义列索引常量方便后续从游标Cursor中获取对应列的数据这里分别对应ID列和名称相关列通过SNIPPET列来获取文件夹名称等情况的索引。
public static final int ID_COLUMN = 0;
public static final int NAME_COLUMN = 1;
// 构造函数用于创建FoldersListAdapter实例接收上下文Context和数据库游标Cursor作为参数
// 调用父类CursorAdapter的构造函数进行初始化将传入的游标与适配器关联起来以便后续处理数据展示相关操作。
public FoldersListAdapter(Context context, Cursor c) {
super(context, c);
// TODO Auto-generated constructor stub
}
// 重写CursorAdapter的抽象方法newView该方法的作用是创建一个新的视图View用于展示游标中的每一项数据
// 在这里返回一个自定义的FolderListItem实例它继承自LinearLayout用于展示文件夹相关信息的具体布局。
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return new FolderListItem(context);
}
// 重写CursorAdapter的抽象方法bindView该方法用于将游标中当前位置的数据绑定到指定的视图View进行具体的数据展示设置
// 比如设置文本内容到对应的TextView等控件中在这里根据游标中的数据判断文件夹名称的显示内容并调用FolderListItem的bind方法进行展示设置。
@Override
public void bindView(View view, Context context, Cursor cursor) {
if (view instanceof FolderListItem) {
// 判断如果当前游标中获取的文件夹ID等于根文件夹IDNotes.ID_ROOT_FOLDER具体含义由Notes类定义
// 则显示特定的表示根文件夹的字符串从资源文件中获取对应的文本否则显示游标中对应列NAME_COLUMN获取到的文件夹名称。
String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER)? context
.getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN);
((FolderListItem) view).bind(folderName);
}
}
// 自定义的方法用于获取指定位置的文件夹名称通过传入上下文Context和列表中的位置position参数
// 根据位置获取对应的游标Cursor然后按照前面的逻辑判断并返回相应的文件夹名称方便外部调用获取具体的文件夹名称信息。
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);
}
// 内部私有类继承自LinearLayout用于定义展示文件夹信息的具体布局和相关操作逻辑它是每个列表项的具体视图实现。
private class FolderListItem extends LinearLayout {
// 用于显示文件夹名称的TextView控件通过在构造函数中查找布局文件中的对应控件实例进行后续操作。
private TextView mName;
// 构造函数接收上下文Context参数调用父类LinearLayout的构造函数进行初始化
// 并通过inflate方法将指定的布局文件R.layout.folder_list_item填充到当前视图中然后获取布局中的TextView控件实例。
public FolderListItem(Context context) {
super(context);
inflate(context, R.layout.folder_list_item, this);
mName = (TextView) findViewById(R.id.tv_folder_name);
}
// 自定义的方法用于将传入的文件夹名称设置到对应的TextViewmName控件上进行具体的文本显示设置使得列表项能正确展示文件夹名称信息。
public void bind(String name) {
mName.setText(name);
}
}
}

@ -0,0 +1,269 @@
/*
* 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.
*/
// 包声明表明该类所在的包名为net.micode.notes.ui
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;
// NoteEditActivity类继承自Activity它是用于编辑笔记的主要Activity实现了多个接口来处理用户交互、笔记设置变更以及文本内容变化等相关逻辑
// 提供了丰富的功能,如文本编辑、样式设置、提醒设置、分享、删除笔记等操作。
public class NoteEditActivity extends Activity implements OnClickListener,
NoteSettingChangedListener, OnTextViewChangeListener {
// 内部类,用于持有笔记头部相关视图控件的引用,方便在外部类中对这些控件进行统一操作和管理,例如设置文本、控制显示隐藏等。
private class HeadViewHolder {
public TextView tvModified;
public ImageView ivAlertIcon;
public TextView tvAlertDate;
public ImageView ibSetBgColor;
}
// 用于存储背景颜色选择按钮如黄色、红色等按钮与对应的颜色资源ID的映射关系方便根据按钮ID获取对应的颜色资源标识以进行背景颜色相关的操作。
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);
}
// 用于存储背景颜色选择按钮对应的选中状态指示器如黄色按钮选中时对应的黄色选中指示器视图与颜色资源ID的映射关系
// 便于根据颜色资源ID找到对应的选中指示器视图并控制其显示隐藏以反馈当前选中的背景颜色。
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);
}
// 用于存储字体大小选择按钮如大字体、小字体等按钮与对应的字体大小资源ID的映射关系方便根据按钮ID获取相应的字体大小设置以进行字体大小相关的操作。
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);
}
// 用于存储字体大小选择按钮对应的选中状态指示器如大字体按钮选中时对应的大字体选中指示器视图与字体大小资源ID的映射关系
// 便于根据字体大小资源ID找到对应的选中指示器视图并控制其显示隐藏以反馈当前选中的字体大小。
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;
// 用于编辑笔记文本内容的EditText控件用户在该控件中输入、修改笔记的具体文字内容同时也会根据其他设置如字体大小等更新显示样式。
private EditText mNoteEditor;
// 包含笔记编辑区域mNoteEditor的整体布局视图用于设置其背景等相关样式使其与笔记的整体风格和设置保持一致。
private View mNoteEditorPanel;
// 代表正在编辑的笔记对象,封装了笔记的各种属性(如文本内容、背景颜色、提醒设置等)以及相关的操作方法(如保存、加载等),是与笔记数据交互的核心对象。
private WorkingNote mWorkingNote;
// 用于获取和操作应用的共享偏好设置SharedPreferences可以保存和读取一些用户的个性化设置信息例如字体大小偏好设置等。
private SharedPreferences mSharedPrefs;
// 用于记录当前选中的字体大小资源ID通过读取共享偏好设置初始化并在字体大小切换时更新以确保文本编辑区域能正确应用对应的字体大小样式。
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创建时调用的方法用于进行一些初始化操作如设置Activity的布局内容根据传入的Intent初始化活动状态等是整个Activity生命周期的起始方法。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 设置该Activity对应的布局文件为R.layout.note_edit该布局文件定义了笔记编辑页面的整体UI结构包含各种视图控件的布局和样式等。
this.setContentView(R.layout.note_edit);
// 如果是首次创建该ActivitysavedInstanceState为null且无法成功初始化活动状态initActivityState方法返回false
// 则直接结束该Activity不再进行后续操作因为可能缺少必要的启动参数等导致无法正常展示编辑页面。
if (savedInstanceState == null &&!initActivityState(getIntent())) {
finish();
return;
}
// 调用初始化资源的方法,用于获取布局中的各个视图控件实例,并进行一些相关的初始设置,例如设置点击监听器等。
initResources();
}
/**
* 当前Activity在内存不足时可能会被系统销毁。当再次被用户启动时需要恢复之前的状态
* 该方法会在这种情况下被调用用于从保存的状态中恢复Activity的相关数据和设置。
*/
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// 如果保存的状态不为空且包含特定的额外数据通过Intent.EXTRA_UID标识则尝试重新初始化活动状态
// 若初始化失败则结束该Activity若成功则表示从之前被销毁的状态中恢复过来了并在日志中记录恢复信息。
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");
}
}
// 用于根据传入的Intent初始化Activity的状态根据Intent的不同动作ACTION_VIEW或ACTION_INSERT_OR_EDIT等以及携带的额外数据
// 进行相应的操作如加载已有笔记、创建新笔记等并返回初始化是否成功的标志是决定Activity能否正常展示编辑功能的关键方法。
private boolean initActivityState(Intent intent) {
// 初始化为空表示尚未确定要编辑的笔记对象后续根据Intent的具体情况进行赋值和加载操作。
mWorkingNote = null;
// 判断Intent的动作是否为查看ACTION_VIEW如果是查看已有笔记的情况则进行以下操作。
if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) {
// 获取Intent中传递的笔记ID如果没有传递则默认为0后续会根据该ID来查找并加载对应的笔记数据。
long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0);
mUserQuery = "";
// 如果Intent中包含搜索相关的额外数据SearchManager.EXTRA_DATA_KEY说明是从搜索结果进入编辑页面
// 则根据传递的搜索数据获取笔记ID并获取用户的搜索查询字符串用于后续在笔记中展示搜索匹配情况等操作。
if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) {
noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY));
mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY);
}
// 通过DataUtils工具类方法检查该笔记ID对应的笔记是否在笔记数据库中可见是否存在且符合类型要求等
// 如果不存在则跳转到笔记列表页面NotesListActivity并显示提示信息告知用户笔记不存在然后结束当前编辑Activity。
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 {
// 如果笔记存在则通过WorkingNote的加载方法根据笔记ID加载对应的笔记对象到mWorkingNote中
// 如果加载失败则记录错误日志并结束当前编辑Activity若加载成功则继续后续的初始化设置。
mWorkingNote = WorkingNote.load(this, noteId);
if (mWorkingNote == null) {
Log.e(TAG, "load note failed with note id" + noteId);
finish();
return false;
}
}
// 设置软键盘的显示模式使其初始状态为隐藏并且当软键盘弹出时调整Activity的布局以适应软键盘显示避免遮挡内容。
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())) {
// 处理创建新笔记或编辑已有笔记的情况以下是获取创建或编辑笔记所需的各种额外数据如文件夹ID、小部件相关信息、背景资源ID等。
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));
// 尝试解析通话记录相关的笔记内容如果传入的通话日期callDate和电话号码phoneNumber都不为空
// 则根据这两个信息查找是否已存在对应的笔记,如果存在则加载该笔记,若不存在则创建一个空的笔记并转换为通话记录笔记格式,填充相应的通话数据。
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);

@ -0,0 +1,259 @@
/*
* 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.
*/
// 包声明表明该类所在的包名为net.micode.notes.ui
package net.micode.notes.ui;
import android.content.Context;
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.R;
import java.util.HashMap;
import java.util.Map;
// NoteEditText类继承自EditText它是一个自定义的文本编辑视图类在基础的文本编辑功能之上添加了一些与特定业务逻辑相关的功能
// 比如处理不同类型链接的点击、根据按键操作触发相应的文本变更回调等功能,用于在笔记编辑场景下更灵活地处理文本输入与交互操作。
public class NoteEditText extends EditText {
// 用于日志记录的标签,方便在日志输出中识别该类相关的日志信息,便于调试和查看运行情况。
private static final String TAG = "NoteEditText";
// 用于记录当前该文本编辑视图在整体布局中的索引位置,例如在列表模式下多个文本编辑视图组成列表时,标识其顺序位置,方便进行相关操作和逻辑处理。
private int mIndex;
// 用于记录在按下删除键KEYCODE_DEL之前文本的选择起始位置以便后续判断是否执行特定的删除逻辑比如在特定条件下触发删除整个文本编辑视图等操作。
private int mSelectionStartBeforeDelete;
// 定义表示电话号码链接的协议头字符串,用于识别文本中是否包含电话号码类型的链接内容,后续可根据此进行相应的处理,如点击跳转拨号等操作。
private static final String SCHEME_TEL = "tel:";
// 定义表示超文本链接HTTP协议的协议头字符串用于识别文本中是否包含网页链接内容以便进行如点击打开网页等相关操作。
private static final String SCHEME_HTTP = "http:";
// 定义表示电子邮件链接的协议头字符串,用于识别文本中是否包含邮件链接内容,方便进行如点击启动邮件客户端等相应处理。
private static final String SCHEME_EMAIL = "mailto:";
// 用于存储不同链接协议如tel、http、mailto等与对应的资源ID通常用于显示给用户的操作提示字符串资源的映射关系
// 方便根据链接类型获取相应的提示文本资源,例如在弹出的菜单中显示合适的操作选项名称。
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);
}
// 定义一个接口,用于与外部进行交互,当文本编辑视图发生特定的文本变更事件(如删除、添加文本、文本内容改变等情况)时,
// 会回调该接口中的相应方法供外部类如包含该文本编辑视图的Activity进行相关逻辑处理实现对文本编辑操作的监听和响应。
public interface OnTextViewChangeListener {
/**
* 当按下删除键({@link KeyEvent#KEYCODE_DEL})且文本内容为空时,触发该方法,用于删除当前的文本编辑视图相关文本内容,
* 外部类可根据索引等信息进行相应的布局和数据更新操作。
*/
void onEditTextDelete(int index, String text);
/**
* 当按下回车键({@link KeyEvent#KEYCODE_ENTER})时,触发该方法,用于在当前文本编辑视图之后添加新的文本编辑视图及相应文本内容,
* 外部类可以根据传递的索引和文本信息进行布局添加等操作,实现类似换行添加新内容的效果。
*/
void onEditTextEnter(int index, String text);
/**
* 当文本内容发生改变时,触发该方法,用于根据文本是否为空来决定隐藏或显示相关的菜单项等操作,例如根据文本有无隐藏或显示编辑相关的功能选项。
*/
void onTextChange(int index, boolean hasText);
}
// 用于持有实现了OnTextViewChangeListener接口的实例外部类通过设置该实例来监听文本编辑视图的相关文本变更事件以便进行对应的业务逻辑处理。
private OnTextViewChangeListener mOnTextViewChangeListener;
// 构造函数接收上下文Context参数调用父类EditText的另一个构造函数进行初始化同时将索引mIndex初始化为0
// 该构造函数通常在代码中通过 `new` 关键字创建实例时使用,用于简单创建一个文本编辑视图实例并设置初始索引值。
public NoteEditText(Context context) {
super(context, null);
mIndex = 0;
}
// 用于设置当前文本编辑视图在整体布局中的索引位置的方法,外部类可以通过调用该方法来更新索引值,确保在进行相关操作时能准确识别每个文本编辑视图的顺序和位置。
public void setIndex(int index) {
mIndex = index;
}
// 用于设置实现了OnTextViewChangeListener接口的监听器实例的方法外部类通过传入实现了该接口的对象
// 使得文本编辑视图能够在特定文本变更事件发生时回调对应的接口方法,实现对文本编辑操作的监听和响应。
public void setOnTextViewChangeListener(OnTextViewChangeListener listener) {
mOnTextViewChangeListener = listener;
}
// 构造函数接收上下文Context和属性集AttributeSet参数调用父类EditText的对应构造函数进行初始化
// 并采用系统默认的编辑文本样式android.R.attr.editTextStyle来设置文本编辑视图的外观和基本属性
// 该构造函数通常在布局文件中通过 XML 配置创建该视图实例时被调用,以解析并应用布局中定义的相关属性设置。
public NoteEditText(Context context, AttributeSet attrs) {
super(context, attrs, android.R.attr.editTextStyle);
}
// 构造函数接收上下文Context、属性集AttributeSet和默认样式defStyle参数调用父类EditText的对应构造函数进行初始化
// 该构造函数提供了更灵活的样式定制功能可根据传入的默认样式参数来设置文本编辑视图的外观和属性不过这里暂未做额外的自定义初始化逻辑TODO 部分)。
public NoteEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
// 重写父类EditText的触摸事件处理方法onTouchEvent用于处理用户在文本编辑视图上的触摸操作
// 这里主要实现了根据触摸位置设置文本选择的功能,例如用户点击文本中的某个位置时,将光标定位到对应的位置。
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 获取触摸点的原始 X 坐标(相对于文本编辑视图左上角)。
int x = (int) event.getX();
// 获取触摸点的原始 Y 坐标(相对于文本编辑视图左上角)。
int y = (int) event.getY();
// 调整 X 坐标,减去文本编辑视图的左内边距,使其坐标基于文本内容区域的左上角。
x -= getTotalPaddingLeft();
// 调整 Y 坐标,减去文本编辑视图的上内边距,使其坐标基于文本内容区域的左上角。
y -= getTotalPaddingTop();
// 进一步调整 X 坐标,加上文本编辑视图的水平滚动偏移量,以处理滚动情况下的正确位置计算。
x += getScrollX();
// 进一步调整 Y 坐标,加上文本编辑视图的垂直滚动偏移量,以处理滚动情况下的正确位置计算。
y += getScrollY();
// 获取文本编辑视图的文本布局对象,用于后续根据坐标计算文本位置相关操作。
Layout layout = getLayout();
// 根据触摸点的垂直坐标Y坐标获取对应的文本行号确定触摸点所在的文本行。
int line = layout.getLineForVertical(y);
// 根据触摸点所在的文本行以及水平坐标X坐标获取对应的文本偏移量即确定触摸点在该行文本中的具体位置偏移量。
int off = layout.getOffsetForHorizontal(line, x);
// 根据计算得到的文本偏移量,设置文本的选择位置,即将光标定位到触摸点对应的文本位置处,方便用户进行后续的文本编辑操作。
Selection.setSelection(getText(), off);
break;
}
// 调用父类EditText的onTouchEvent方法确保其他默认的触摸事件处理逻辑也能正常执行例如处理长按弹出复制粘贴等菜单操作等。
return super.onTouchEvent(event);
}
// 重写父类EditText的按键按下事件处理方法onKeyDown用于捕获特定按键按下的操作
// 这里主要针对回车键KEYCODE_ENTER和删除键KEYCODE_DEL进行了相关逻辑处理例如记录一些必要的状态信息等。
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
// 如果设置了OnTextViewChangeListener监听器返回false可能是为了让后续的默认回车键处理逻辑如换行等先不执行
// 而是等待在按键抬起onKeyUp时由自定义的逻辑来处理回车键相关操作比如添加新的文本编辑视图等。
if (mOnTextViewChangeListener!= null) {
return false;
}
break;
case KeyEvent.KEYCODE_DEL:
// 当按下删除键时,记录当前文本的选择起始位置,用于后续在按键抬起时判断是否执行特定的删除操作,例如删除整个文本编辑视图等情况。
mSelectionStartBeforeDelete = getSelectionStart();
break;
default:
break;
}
// 调用父类EditText的onKeyDown方法确保其他默认的按键按下事件处理逻辑也能正常执行例如处理其他按键的功能如方向键移动光标等
return super.onKeyDown(keyCode, event);
}
// 重写父类EditText的按键抬起事件处理方法onKeyUp用于在特定按键抬起时执行相应的业务逻辑
// 这里针对删除键KEYCODE_DEL和回车键KEYCODE_ENTER进行了详细的操作处理根据不同情况触发相应的文本变更回调方法等。
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_DEL:
if (mOnTextViewChangeListener!= null) {
// 判断如果在按下删除键之前文本的选择起始位置为0意味着可能要删除整个文本内容且当前文本编辑视图的索引不为0不是第一个视图
// 则调用OnTextViewChangeListener接口的onEditTextDelete方法通知外部类进行相应的删除操作例如从布局中移除该文本编辑视图等
// 并返回true表示该按键事件已被处理阻止父类的默认删除键抬起处理逻辑通常是删除单个字符等操作执行。
if (0 == mSelectionStartBeforeDelete && mIndex!= 0) {
mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString());
return true;
}
} else {
// 如果没有设置OnTextViewChangeListener监听器则在日志中记录提示信息便于调试发现问题因为此时无法执行相应的自定义删除逻辑。
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
case KeyEvent.KEYCODE_ENTER:
if (mOnTextViewChangeListener!= null) {
// 获取当前文本的选择起始位置,用于后续截取要添加到新文本编辑视图中的文本内容。
int selectionStart = getSelectionStart();
// 截取从选择起始位置到文本末尾的内容作为要添加到新文本编辑视图中的文本通过调用subSequence方法获取子串并转换为字符串类型。
String text = getText().subSequence(selectionStart, length()).toString();
// 将当前文本编辑视图中的文本内容更新为从开头到选择起始位置的部分,相当于把要添加到新视图的文本分离出来,
// 后续会通过onEditTextEnter方法将该文本添加到新创建的文本编辑视图中。
setText(getText().subSequence(0, selectionStart));
// 调用OnTextViewChangeListener接口的onEditTextEnter方法通知外部类添加新的文本编辑视图及相应文本内容
// 传入当前索引加1作为新视图的索引表示添加在当前视图之后以及截取的文本内容作为新视图的初始文本。
mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text);
} else {
// 如果没有设置OnTextViewChangeListener监听器则在日志中记录提示信息便于调试发现问题因为此时无法执行相应的自定义回车键相关逻辑。
Log.d(TAG, "OnTextViewChangeListener was not seted");
}
break;
default:
break;
}
// 调用父类EditText的onKeyUp方法确保其他默认的按键抬起事件处理逻辑也能正常执行例如处理按键抬起后的一些状态重置等操作。
return super.onKeyUp(keyCode, event);
}
// 重写父类EditText的焦点改变事件处理方法onFocusChanged用于在文本编辑视图的焦点发生改变获得焦点或失去焦点时执行相应的逻辑
// 这里根据焦点状态以及文本内容是否为空调用OnTextViewChangeListener接口的onTextChange方法通知外部类进行相关操作例如显示或隐藏某些菜单项等。
@Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (mOnTextViewChangeListener!= null) {
// 如果失去焦点且文本内容为空调用OnTextViewChangeListener接口的onTextChange方法通知外部类进行相应操作
// 例如隐藏与文本编辑相关的功能选项等传入当前索引以及表示文本为空的标志false
if (!focused && TextUtils.isEmpty(getText())) {
mOnTextViewChangeListener.onTextChange(mIndex, false);
} else {
// 如果获得焦点或者文本内容不为空调用OnTextViewChangeListener接口的onTextChange方法通知外部类进行相应操作
// 例如显示与文本编辑相关的功能选项等传入当前索引以及表示文本不为空的标志true
mOnTextViewChangeListener.onTextChange(mIndex, true);
}
}
// 调用父类EditText的onFocusChanged方法确保其他默认的焦点改变事件处理逻辑也能正常执行例如处理焦点改变后的一些界面更新等操作。
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
// 重写父类EditText的创建上下文菜单方法onCreateContextMenu用于在长按文本编辑视图弹出上下文菜单时添加自定义的菜单项及相应的点击处理逻辑
// 这里主要针对文本中包含的链接URLSpan类型进行处理根据链接类型添加对应的操作菜单项例如点击电话号码链接可拨打电话等操作。
@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);
// 从文本的选中范围由min和max确定内获取所有的URLSpan类型的链接对象数组这些链接对象代表了文本中包含的各种链接信息
// 后续会
Loading…
Cancel
Save