From e4cadc5fa11f9377d32312a65b36c83120a7fb31 Mon Sep 17 00:00:00 2001 From: hyf <2173636536@qq.com> Date: Tue, 31 Dec 2024 01:02:50 +0800 Subject: [PATCH] =?UTF-8?q?=E8=83=A1=E4=BA=91=E9=A3=9E--ui=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../net/micode/notes/ui/AlarmReceiver.java | 14 + .../net/micode/notes/ui/DateTimePicker.java | 205 ++++++++++++--- .../micode/notes/ui/DateTimePickerDialog.java | 43 +++- .../src/net/micode/notes/ui/DropdownMenu.java | 25 ++ .../micode/notes/ui/FoldersListAdapter.java | 25 +- .../net/micode/notes/ui/NoteEditActivity.java | 211 +++++++++++++++- .../src/net/micode/notes/ui/NoteEditText.java | 69 ++++- .../src/net/micode/notes/ui/NoteItemData.java | 239 +++++++++++++++++- .../micode/notes/ui/NotesListActivity.java | 111 ++++++-- .../net/micode/notes/ui/NotesListAdapter.java | 62 ++++- .../net/micode/notes/ui/NotesListItem.java | 50 +++- .../notes/ui/NotesPreferenceActivity.java | 143 ++++++++++- 12 files changed, 1114 insertions(+), 83 deletions(-) diff --git a/src/Notes-master/src/net/micode/notes/ui/AlarmReceiver.java b/src/Notes-master/src/net/micode/notes/ui/AlarmReceiver.java index 54e503b..7d941dc 100644 --- a/src/Notes-master/src/net/micode/notes/ui/AlarmReceiver.java +++ b/src/Notes-master/src/net/micode/notes/ui/AlarmReceiver.java @@ -21,10 +21,24 @@ import android.content.Context; import android.content.Intent; public class AlarmReceiver extends BroadcastReceiver { + /** + * 当接收到广播时会触发该方法,此方法是BroadcastReceiver接口中定义的抽象方法,必须实现。 + * 在这里的功能主要是处理闹钟提醒相关广播被触发后的逻辑,启动对应的Activity来展示闹钟提醒界面等相关操作。 + * + * @param context 上下文对象,用于获取系统服务、启动Activity等操作,它提供了与应用所在的环境相关的信息。 + * @param intent 接收到的广播Intent,里面可能包含了一些与闹钟提醒相关的数据,比如触发闹钟的来源等信息(虽然此代码中未体现对其数据的获取使用)。 + */ @Override public void onReceive(Context context, Intent intent) { + // 通过setClass方法修改Intent的目标组件,将原本可能指向其他地方(或者没有明确指向)的Intent, + // 重新设置为指向AlarmAlertActivity,也就是将广播触发后的下一步操作引导到展示闹钟提醒界面的Activity上。 intent.setClass(context, AlarmAlertActivity.class); + // 给Intent添加一个标志位FLAG_ACTIVITY_NEW_TASK,这个标志表示当启动Activity时, + // 如果当前任务栈(Task)不存在或者不适合启动该Activity,系统会创建一个新的任务栈来启动这个Activity。 + // 因为BroadcastReceiver的生命周期比较特殊,它没有自己的UI界面相关的任务栈,所以需要添加这个标志确保Activity能正常启动。 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // 使用传入的上下文对象启动设置好的Intent对应的Activity,这样就会从接收到闹钟提醒广播, + // 跳转到展示具体闹钟提醒信息的AlarmAlertActivity界面,让用户看到闹钟提醒相关的内容(如对话框、提示信息等)。 context.startActivity(intent); } } diff --git a/src/Notes-master/src/net/micode/notes/ui/DateTimePicker.java b/src/Notes-master/src/net/micode/notes/ui/DateTimePicker.java index 496b0cd..03cde72 100644 --- a/src/Notes-master/src/net/micode/notes/ui/DateTimePicker.java +++ b/src/Notes-master/src/net/micode/notes/ui/DateTimePicker.java @@ -28,85 +28,129 @@ import android.view.View; import android.widget.FrameLayout; import android.widget.NumberPicker; +// DateTimePicker类继承自FrameLayout,可作为界面中的布局容器使用,用于实现日期和时间选择的功能 public class DateTimePicker extends FrameLayout { + // 默认的启用状态,设置为true,表示默认启用该控件 private static final boolean DEFAULT_ENABLE_STATE = true; + // 半天包含的小时数,即12小时(用于12小时制相关逻辑) private static final int HOURS_IN_HALF_DAY = 12; + // 一整天包含的小时数,即24小时(用于24小时制相关逻辑) private static final int HOURS_IN_ALL_DAY = 24; + // 一周包含的天数,用于日期选择器相关逻辑,例如循环显示一周内的日期等情况 private static final int DAYS_IN_ALL_WEEK = 7; + // 日期选择器的最小可选值,通常设为0(比如对应一周内第一天的索引等情况) private static final int DATE_SPINNER_MIN_VAL = 0; + // 日期选择器的最大可选值,根据一周的天数减1来设置,因为索引从0开始 private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1; + // 24小时制视图下小时选择器的最小可选值,即0点 private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0; + // 24小时制视图下小时选择器的最大可选值,即23点 private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; + // 12小时制视图下小时选择器的最小可选值,即1点(12小时制中通常显示1 - 12 private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; + // 12小时制视图下小时选择器的最大可选值,即12点 private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12; + // 分钟选择器的最小可选值,即0分钟 private static final int MINUT_SPINNER_MIN_VAL = 0; + // 分钟选择器的最大可选值,即59分钟 private static final int MINUT_SPINNER_MAX_VAL = 59; + // AM/PM选择器的最小可选值,通常设为0(对应AM private static final int AMPM_SPINNER_MIN_VAL = 0; + // AM/PM选择器的最大可选值,设为1(对应PM) private static final int AMPM_SPINNER_MAX_VAL = 1; + // 用于选择日期的NumberPicker控件,显示在界面上供用户选择具体日期 private final NumberPicker mDateSpinner; + // 用于选择小时的NumberPicker控件,根据是24小时制还是12小时制显示不同范围的小时值供选择 private final NumberPicker mHourSpinner; + // 用于选择分钟的NumberPicker控件,可选择0 - 59分钟范围内的值 private final NumberPicker mMinuteSpinner; + // 用于选择上午/下午(在12小时制下)的NumberPicker控件,显示AM或PM供选择 private final NumberPicker mAmPmSpinner; + // Calendar对象,用于记录当前选中的日期和时间,方便进行各种时间相关的计算和操作 private Calendar mDate; + // 用于存储要在日期选择器中显示的一周内各天的字符串表示,例如"周一"、"周二"等 private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; - + + // 标记当前是否是上午,用于12小时制下时间显示和逻辑处理 private boolean mIsAm; + // 标记当前是否是24小时制视图,如果为true则按24小时制显示时间,否则按12小时制显示 private boolean mIs24HourView; + // 记录该DateTimePicker控件的启用状态,初始化为默认启用状态 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) { + // 根据新旧值的差值,在Calendar对象中调整日期,实现日期的增减操作 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(); + // 如果不是24小时制视图(即12小时制) if (!mIs24HourView) { + // 如果当前不是上午,且旧值是11(12小时制下半天的最后一小时),新值变为12(切换到下午) if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { cal.setTimeInMillis(mDate.getTimeInMillis()); + // 将日期增加一天,因为从上午切换到下午跨越了中午12点,到了下一天 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()); + // 将日期减少一天,因为从下午切换到上午跨越了中午12点,回到了前一天 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; + // 更新上午/下午选择器的显示内容,确保显示正确的AM/PM状态 updateAmPmControl(); } } else { + // 如果是24小时制视图,且从23点变为0点(跨天情况) 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; } } + // 根据当前是否是上午以及小时选择器的值,计算出正确的小时数(用于设置到Calendar对象中) 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)); @@ -115,12 +159,14 @@ public class DateTimePicker extends FrameLayout { } }; + // 分钟选择器的值变化监听器,处理分钟值改变时涉及的时间调整逻辑,如可能导致小时、日期变化等情况 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) { @@ -131,6 +177,7 @@ public class DateTimePicker extends FrameLayout { mHourSpinner.setValue(getCurrentHour()); updateDateControl(); int newHour = getCurrentHourOfDay(); + // 如果新的小时数大于等于12(半天的小时数),则标记为下午 if (newHour >= HOURS_IN_HALF_DAY) { mIsAm = false; updateAmPmControl(); @@ -140,85 +187,113 @@ public class DateTimePicker extends FrameLayout { } } mDate.set(Calendar.MINUTE, newVal); + // 通知外部监听器(如果有设置的话)时间发生了变化,传递相关时间参数 onDateTimeChanged(); } }; - + + // 上午/下午选择器的值变化监听器,处理上午/下午切换时涉及的小时数调整等逻辑 private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { mIsAm = !mIsAm; + // 如果切换到下午,将小时数增加半天的小时数(12小时) if (mIsAm) { mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); } else { + // 如果切换到上午,将小时数减少半天的小时数(12小时) 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); } + // 构造方法,接受一个Context参数,使用当前时间作为初始日期创建DateTimePicker实例 public DateTimePicker(Context context) { this(context, System.currentTimeMillis()); } + // 构造方法,接受一个Context参数和日期的时间戳(long类型),根据指定日期创建DateTimePicker实例,默认按当前系统设置的时间格式(24小时制或12小时制)显示 public DateTimePicker(Context context, long date) { this(context, date, DateFormat.is24HourFormat(context)); } + // 完整的构造方法,接受Context、日期时间戳和是否是24小时制视图的布尔值作为参数,用于创建DateTimePicker实例并进行初始化设置 public DateTimePicker(Context context, long date, boolean is24HourView) { super(context); + // 获取当前时间作为初始的日期和时间设置 mDate = Calendar.getInstance(); mInitialising = true; + // 根据当前小时数判断是否是上午(如果大于等于12小时则为下午) mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; + // 加载datetime_picker布局文件到该控件中,构建其内部的视图结构 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); + // 获取用于显示上午/下午的字符串数组(根据系统语言等设置获取对应的AM/PM字符串 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(); + // 更新小时选择器的显示范围等设置,根据是否是24小时制进行相应调整 updateHourControl(); + // 更新上午/下午选择器的显示状态,根据是否是24小时制显示或隐藏该控件 updateAmPmControl(); + // 设置是否是24小时制视图,根据传入参数进行相应的显示和逻辑调整 set24HourView(is24HourView); + // 设置当前日期为传入的指定日期 // set to current time setCurrentDate(date); - + + // 设置该DateTimePicker控件及其内部各个选择器的启用状态 setEnabled(isEnabled()); + // 初始化完成,将初始化标记设为false // set the content descriptions mInitialising = false; } + // 重写父类的setEnabled方法,用于设置整个DateTimePicker控件及其内部各个时间选择器的启用状态 @Override public void setEnabled(boolean enabled) { if (mIsEnabled == enabled) { return; } + + super.setEnabled(enabled); mDateSpinner.setEnabled(enabled); mMinuteSpinner.setEnabled(enabled); @@ -227,11 +302,13 @@ public class DateTimePicker extends FrameLayout { mIsEnabled = enabled; } + // 重写父类的isEnabled方法,用于获取整个DateTimePicker控件的启用状态 @Override public boolean isEnabled() { return mIsEnabled; } - + + // 获取当前日期的时间戳(以毫秒为单位),返回当前记录在mDate中的日期对应的时间戳 /** * Get the current date in millis * @@ -241,6 +318,7 @@ public class DateTimePicker extends FrameLayout { return mDate.getTimeInMillis(); } + // 根据传入的日期时间戳(long类型)设置当前日期,内部会进一步解析并设置具体的年、月、日、时、分等信息 /** * Set the current date * @@ -252,6 +330,7 @@ public class DateTimePicker extends FrameLayout { 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 @@ -262,28 +341,37 @@ public class DateTimePicker extends FrameLayout { * @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 + * @return 当前的年份,通过Calendar对象获取对应的年份字段值 */ public int getCurrentYear() { return mDate.get(Calendar.YEAR); } /** - * Set current year + * 设置当前日期的年份 * - * @param year The current year + * @param year 要设置的年份值 + * 逻辑说明:如果当前不是在初始化阶段(mInitialising为false),并且传入的年份与当前记录的年份相同, + * 则不进行任何操作直接返回,避免不必要的重复设置。否则,将Calendar对象中的年份字段设置为传入的年份值, + * 接着调用updateDateControl方法更新日期显示相关内容,最后通过onDateTimeChanged方法通知外部监听器(如果有的话)日期发生了变化。 */ public void setCurrentYear(int year) { if (!mInitialising && year == getCurrentYear()) { @@ -295,18 +383,21 @@ public class DateTimePicker extends FrameLayout { } /** - * Get current month in the year + * 获取当前日期中的月份信息(注意,返回值范围是0 - 11,0表示一月,11表示十二月) * - * @return The current month in the year + * @return 当前的月份,通过Calendar对象获取对应的月份字段值 */ public int getCurrentMonth() { return mDate.get(Calendar.MONTH); } /** - * Set current month in the year + * 设置当前日期的月份 * - * @param month The month in the year + * @param month 要设置的月份值(范围同样是0 - 11,对应不同的月份) + * 逻辑说明:如果当前不是在初始化阶段(mInitialising为false),并且传入的月份与当前记录的月份相同, + * 则不进行任何操作直接返回,避免不必要的重复设置。否则,将Calendar对象中的月份字段设置为传入的月份值, + * 接着调用updateDateControl方法更新日期显示相关内容,最后通过onDateTimeChanged方法通知外部监听器(如果有的话)日期发生了变化。 */ public void setCurrentMonth(int month) { if (!mInitialising && month == getCurrentMonth()) { @@ -318,18 +409,21 @@ public class DateTimePicker extends FrameLayout { } /** - * Get current day of the month + * 获取当前日期是该月中的第几天 * - * @return The day of the month + * @return 当前日在月中的天数,通过Calendar对象获取对应的日字段值 */ public int getCurrentDay() { return mDate.get(Calendar.DAY_OF_MONTH); } /** - * Set current day of the month + * 设置当前日期是该月中的第几天 * - * @param dayOfMonth The day of the month + * @param dayOfMonth 要设置的日数值(在合理的范围内,根据不同月份的天数限制) + * 逻辑说明:如果当前不是在初始化阶段(mInitialising为false),并且传入的日数值与当前记录的日数值相同, + * 则不进行任何操作直接返回,避免不必要的重复设置。否则,将Calendar对象中的日字段设置为传入的日数值, + * 接着调用updateDateControl方法更新日期显示相关内容,最后通过onDateTimeChanged方法通知外部监听器(如果有的话)日期发生了变化。 */ public void setCurrentDay(int dayOfMonth) { if (!mInitialising && dayOfMonth == getCurrentDay()) { @@ -341,13 +435,21 @@ public class DateTimePicker extends FrameLayout { } /** - * Get current hour in 24 hour mode, in the range (0~23) - * @return The current hour in 24 hour mode + * 获取当前在24小时制模式下的小时数,范围是0 - 23 + * + * @return 当前24小时制下的小时数值,通过Calendar对象获取对应的小时字段值(HOUR_OF_DAY字段) */ public int getCurrentHourOfDay() { return mDate.get(Calendar.HOUR_OF_DAY); } + /** + * 根据是否是24小时制视图,获取当前的小时数表示 + * 如果是24小时制视图,直接返回24小时制下的小时数(与getCurrentHourOfDay方法返回值一致); + * 如果是12小时制视图,对24小时制下的小时数进行转换,将大于12的小时数减去12,0点转换为12点,以符合12小时制的显示习惯。 + * + * @return 根据时间制转换后的当前小时数 + */ private int getCurrentHour() { if (mIs24HourView){ return getCurrentHourOfDay(); @@ -362,9 +464,14 @@ public class DateTimePicker extends FrameLayout { } /** - * Set current hour in 24 hour mode, in the range (0~23) + * 设置当前在24小时制模式下的小时数 * - * @param hourOfDay + * @param hourOfDay 要设置的24小时制下的小时数值(范围0 - 23) + * 逻辑说明:如果当前不是在初始化阶段(mInitialising为false),并且传入的小时数值与当前记录的24小时制小时数值相同, + * 则不进行任何操作直接返回,避免不必要的重复设置。否则,先将Calendar对象中的小时字段(HOUR_OF_DAY)设置为传入的小时数值。 + * 若不是24小时制视图(即12小时制),根据传入的小时数值判断是上午还是下午,并更新相应的上午/下午标记(mIsAm), + * 同时对传入的小时数值进行调整(如大于12点的情况减去12),然后调用updateAmPmControl方法更新上午/下午选择器的显示内容。 + * 最后将小时选择器的值设置为调整后的小时数值,并通过onDateTimeChanged方法通知外部监听器(如果有的话)时间发生了变化。 */ public void setCurrentHour(int hourOfDay) { if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { @@ -390,16 +497,19 @@ public class DateTimePicker extends FrameLayout { } /** - * Get currentMinute + * 获取当前的分钟数 * - * @return The Current Minute + * @return 当前分钟数值,通过Calendar对象获取对应的分钟字段值 */ public int getCurrentMinute() { return mDate.get(Calendar.MINUTE); } /** - * Set current minute + * 设置当前的分钟数 + * 逻辑说明:如果当前不是在初始化阶段(mInitialising为false),并且传入的分钟数值与当前记录的分钟数值相同, + * 则不进行任何操作直接返回,避免不必要的重复设置。否则,先将分钟选择器的值设置为传入的分钟数值, + * 再将Calendar对象中的分钟字段设置为传入的分钟数值,最后通过onDateTimeChanged方法通知外部监听器(如果有的话)时间发生了变化。 */ public void setCurrentMinute(int minute) { if (!mInitialising && minute == getCurrentMinute()) { @@ -411,16 +521,22 @@ public class DateTimePicker extends FrameLayout { } /** - * @return true if this is in 24 hour view else false. + * 判断当前是否处于24小时制视图 + * + * @return 如果是24小时制视图返回true,否则返回false */ public boolean is24HourView () { return mIs24HourView; } /** - * Set whether in 24 hour or AM/PM mode. + * 设置当前是采用24小时制还是AM/PM(12小时制)模式显示时间 * - * @param is24HourView True for 24 hour mode. False for AM/PM mode. + * @param is24HourView 传入true表示设置为24小时制模式,传入false表示设置为12小时制(AM/PM)模式 + * 逻辑说明:如果传入的设置值与当前的时间制视图状态(mIs24HourView)相同,则不进行任何操作直接返回。 + * 否则,更新mIs24HourView标记为传入的设置值,根据新的时间制设置上午/下午选择器(mAmPmSpinner)的可见性(24小时制下隐藏,12小时制下显示), + * 获取当前24小时制下的小时数,调用updateHourControl方法更新小时选择器的范围设置(根据24小时制或12小时制调整最小值和最大值), + * 调用setCurrentHour方法更新小时选择器的显示值,最后调用updateAmPmControl方法再次确保上午/下午选择器的显示状态正确。 */ public void set24HourView(boolean is24HourView) { if (mIs24HourView == is24HourView) { @@ -434,6 +550,17 @@ public class DateTimePicker extends FrameLayout { updateAmPmControl(); } + /** + * 用于更新日期选择器(mDateSpinner)的显示内容,实现按周循环显示日期的功能 + * 具体操作如下: + * 1. 获取一个新的Calendar实例,并将其时间设置为当前记录的日期时间(mDate中的时间)。 + * 2. 将该Calendar实例的日期往前推(DAYS_IN_ALL_WEEK / 2 + 1)天,为后续循环生成一周内的日期做准备。 + * 3. 清空日期选择器当前显示的内容(设置为null)。 + * 4. 通过循环,每次将Calendar实例的日期往后推一天,使用DateFormat对日期进行格式化(格式为"MM.dd EEEE",即显示月.日 星期X的格式), + * 并将格式化后的字符串存储到mDateDisplayValues数组中,该数组用于设置日期选择器要显示的内容。 + * 5. 将日期选择器的显示内容设置为生成的mDateDisplayValues数组,将日期选择器的值设置为一周中间的位置(DAYS_IN_ALL_WEEK / 2), + * 最后调用invalidate方法使日期选择器重新绘制,以显示更新后的内容。 + */ private void updateDateControl() { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(mDate.getTimeInMillis()); @@ -448,6 +575,12 @@ public class DateTimePicker extends FrameLayout { mDateSpinner.invalidate(); } + /** + * 根据当前是否是24小时制视图,更新上午/下午选择器(mAmPmSpinner)的显示状态 + * 如果是24小时制视图,将上午/下午选择器设置为不可见(GONE); + * 如果不是24小时制视图(即12小时制),根据当前的上午/下午标记(mIsAm)设置上午/下午选择器的值(AM对应0,PM对应1), + * 并将上午/下午选择器设置为可见(VISIBLE)。 + */ private void updateAmPmControl() { if (mIs24HourView) { mAmPmSpinner.setVisibility(View.GONE); @@ -458,6 +591,13 @@ public class DateTimePicker extends FrameLayout { } } + /** + * 根据当前是否是24小时制视图,更新小时选择器(mHourSpinner)的范围设置(最小值和最大值) + * 如果是24小时制视图,将小时选择器的最小值设置为HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW(即0), + * 最大值设置为HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW(即23); + * 如果不是24小时制视图(即12小时制),将小时选择器的最小值设置为HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW(即1), + * 最大值设置为HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW(即12)。 + */ private void updateHourControl() { if (mIs24HourView) { mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); @@ -469,13 +609,20 @@ public class DateTimePicker extends FrameLayout { } /** - * Set the callback that indicates the 'Set' button has been pressed. - * @param callback the callback, if null will do nothing + * 设置用于监听日期和时间变化的回调接口,外部类可通过实现OnDateTimeChangedListener接口, + * 并将实现类的实例传入该方法,来接收DateTimePicker控件中时间发生变化的通知。 + * 如果传入的回调参数为null,则不进行任何操作。 + * + * @param callback 实现了OnDateTimeChangedListener接口的实例,用于接收时间变化的回调通知 */ public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) { mOnDateTimeChangedListener = callback; } + /** + * 内部方法,用于在日期、时间相关属性发生变化时,通知外部监听器(如果有设置的话)时间发生了变化, + * 传递当前DateTimePicker的实例以及当前的年、月、日、时、分等时间信息给外部监听器的onDateTimeChanged方法。 + */ private void onDateTimeChanged() { if (mOnDateTimeChangedListener != null) { mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), diff --git a/src/Notes-master/src/net/micode/notes/ui/DateTimePickerDialog.java b/src/Notes-master/src/net/micode/notes/ui/DateTimePickerDialog.java index 2c47ba4..d967e58 100644 --- a/src/Notes-master/src/net/micode/notes/ui/DateTimePickerDialog.java +++ b/src/Notes-master/src/net/micode/notes/ui/DateTimePickerDialog.java @@ -28,22 +28,33 @@ 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小时制视图,用于控制时间的显示格式 private boolean mIs24HourView; + // 定义一个接口类型的变量,用于监听用户设置好日期和时间后的操作,外部类可实现该接口来响应设置事件 private OnDateTimeSetListener mOnDateTimeSetListener; + // 用于实际进行日期和时间选择操作的DateTimePicker控件实例 private DateTimePicker mDateTimePicker; + // 定义一个接口,用于在用户点击对话框中的确定按钮,设置好日期和时间后进行回调,外部类需实现该接口来处理具体逻辑 public interface OnDateTimeSetListener { void OnDateTimeSet(AlertDialog dialog, long date); } + // 构造方法,接受一个Context参数和日期的时间戳(long类型),用于创建DateTimePickerDialog实例并进行初始化设置 public DateTimePickerDialog(Context context, long date) { super(context); + + // 创建一个DateTimePicker实例,用于在对话框中展示日期和时间选择的界面 mDateTimePicker = new DateTimePicker(context); + // 将DateTimePicker控件设置为对话框的内容视图,使其显示在对话框中供用户操作 setView(mDateTimePicker); + // 为DateTimePicker设置日期和时间变化的监听器,当用户在DateTimePicker中选择的日期或时间发生变化时, + // 会触发该监听器中的onDateTimeChanged方法,在该方法内更新mDate对象记录的日期和时间信息,并调用updateTitle方法更新对话框标题显示的当前日期和时间 mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { public void onDateTimeChanged(DateTimePicker view, int year, int month, int dayOfMonth, int hourOfDay, int minute) { @@ -55,32 +66,56 @@ public class DateTimePickerDialog extends AlertDialog implements OnClickListener updateTitle(mDate.getTimeInMillis()); } }); + + // 设置当前日期和时间为传入的指定日期时间戳对应的时间,同时将秒数设置为0(通常在选择时间时精确到分钟即可) mDate.setTimeInMillis(date); mDate.set(Calendar.SECOND, 0); + + // 将DateTimePicker显示的当前日期和时间设置为mDate所记录的时间,确保初始显示与传入的指定时间一致 mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); + + // 设置对话框的确定按钮,按钮文本通过资源字符串获取(R.string.datetime_dialog_ok),并将当前类(实现了OnClickListener接口)作为点击监听器传入, + // 当用户点击确定按钮时会触发onClick方法进行相应处理 setButton(context.getString(R.string.datetime_dialog_ok), this); + + // 设置对话框的取消按钮,按钮文本通过资源字符串获取(R.string.datetime_dialog_cancel),传入null作为点击监听器,表示使用默认的取消行为(关闭对话框) setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); + + // 根据系统当前的时间格式设置(是否是24小时制)来设置该对话框的时间显示格式,调用set24HourView方法进行设置 set24HourView(DateFormat.is24HourFormat(this.getContext())); + + // 初次创建对话框时,根据传入的初始日期时间戳更新对话框标题显示的当前日期和时间 updateTitle(mDate.getTimeInMillis()); } + // 设置该对话框的时间显示格式是否为24小时制,传入true表示设置为24小时制,传入false表示设置为12小时制(AM/PM) public void set24HourView(boolean is24HourView) { mIs24HourView = is24HourView; } + // 设置用于监听用户设置好日期和时间后的回调接口,外部类实现OnDateTimeSetListener接口后,将实例传入该方法, + // 以便在用户点击确定按钮完成设置时接收通知并进行相应处理 public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { mOnDateTimeSetListener = callBack; } + // 私有方法,用于根据当前日期时间以及是否是24小时制视图等信息,更新对话框标题显示的日期和时间字符串 + private void updateTitle(long date) { private void updateTitle(long date) { int flag = - DateUtils.FORMAT_SHOW_YEAR | - DateUtils.FORMAT_SHOW_DATE | - DateUtils.FORMAT_SHOW_TIME; + DateUtils.FORMAT_SHOW_YEAR |// 表示在格式化日期时间字符串时显示年份 + DateUtils.FORMAT_SHOW_DATE |// 表示显示日期(月、日等) + DateUtils.FORMAT_SHOW_TIME;// 表示显示时间(时、分等) + + // 根据当前是否是24小时制视图,添加相应的格式化标志,用于控制时间的显示格式(24小时制或12小时制带AM/PM) + flag |= mIs24HourView? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_12HOUR; flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); } + // 实现OnClickListener接口的onClick方法,当用户点击对话框中的按钮(这里主要是确定按钮)时触发该方法, + // 如果设置了OnDateTimeSetListener监听器,会调用其OnDateTimeSet方法,将当前对话框实例以及选择好的日期时间戳传递给外部监听器, + // 以便外部类进行相应的后续处理(比如保存选择的时间等操作) public void onClick(DialogInterface arg0, int arg1) { if (mOnDateTimeSetListener != null) { mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); diff --git a/src/Notes-master/src/net/micode/notes/ui/DropdownMenu.java b/src/Notes-master/src/net/micode/notes/ui/DropdownMenu.java index 613dc74..c0b0737 100644 --- a/src/Notes-master/src/net/micode/notes/ui/DropdownMenu.java +++ b/src/Notes-master/src/net/micode/notes/ui/DropdownMenu.java @@ -27,17 +27,35 @@ import android.widget.PopupMenu.OnMenuItemClickListener; import net.micode.notes.R; +// DropdownMenu类用于创建一个下拉菜单的功能组件,通常可以关联一个按钮,点击按钮弹出包含相应菜单项的菜单 public class DropdownMenu { + // 用于显示下拉菜单的按钮,用户点击该按钮会触发弹出菜单的操作 private Button mButton; + // PopupMenu实例,用于实现弹出式菜单的功能,它会依附在指定的View(这里是mButton)上显示 private PopupMenu mPopupMenu; + // 代表菜单对象,用于操作和获取菜单中的具体菜单项等相关信息 private Menu mMenu; + // 构造方法,用于初始化DropdownMenu实例,接受上下文环境(Context)、关联的按钮(Button)以及菜单资源ID(int类型)作为参数 public DropdownMenu(Context context, Button button, int menuId) { + // 将传入的按钮赋值给成员变量mButton,后续操作会基于这个按钮来触发弹出菜单等行为 mButton = button; + + // 为按钮设置背景资源,这里使用的是名为"dropdown_icon"的资源(通常是一个图标,用于提示用户该按钮可弹出菜单) mButton.setBackgroundResource(R.drawable.dropdown_icon); + + // 创建一个PopupMenu实例,将传入的上下文环境和关联的按钮作为参数传入,使得弹出菜单能够在正确的界面环境下依附该按钮显示 mPopupMenu = new PopupMenu(context, mButton); + + // 获取PopupMenu中的Menu对象,赋值给成员变量mMenu,以便后续对菜单进行各种操作,比如添加菜单项、查找菜单项等 mMenu = mPopupMenu.getMenu(); + + // 通过菜单填充器(MenuInflater)将指定资源ID对应的菜单布局文件(通常定义了菜单项等信息)填充到mMenu中, + // 这样就构建好了具有具体菜单项的菜单 mPopupMenu.getMenuInflater().inflate(menuId, mMenu); + + // 为按钮设置点击事件监听器,当按钮被点击时,会触发onClick方法,在该方法内调用PopupMenu的show方法, + // 从而显示弹出式菜单供用户选择菜单项 mButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { mPopupMenu.show(); @@ -45,16 +63,23 @@ public class DropdownMenu { }); } + // 设置下拉菜单中菜单项的点击事件监听器,外部类实现OnMenuItemClickListener接口后, + // 通过传入该接口的实现实例,可以监听每个菜单项被点击时的事件,并进行相应的处理逻辑 + // 如果PopupMenu实例不为空,则将传入的监听器设置给PopupMenu,使其能够响应菜单项的点击操作 public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { if (mPopupMenu != null) { mPopupMenu.setOnMenuItemClickListener(listener); } } + // 根据传入的菜单项ID,在菜单(mMenu)中查找对应的MenuItem实例并返回,方便外部类对特定菜单项进行进一步操作, + // 比如获取菜单项的属性、设置菜单项的状态等 public MenuItem findItem(int id) { return mMenu.findItem(id); } + // 设置按钮上显示的文本内容,通常可以用于显示当前选中的菜单项相关信息或者作为菜单的标题等, + // 通过修改按钮的文本(setText方法)来实现显示效果的更新 public void setTitle(CharSequence title) { mButton.setText(title); } diff --git a/src/Notes-master/src/net/micode/notes/ui/FoldersListAdapter.java b/src/Notes-master/src/net/micode/notes/ui/FoldersListAdapter.java index 96b77da..029eb87 100644 --- a/src/Notes-master/src/net/micode/notes/ui/FoldersListAdapter.java +++ b/src/Notes-master/src/net/micode/notes/ui/FoldersListAdapter.java @@ -28,26 +28,37 @@ 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所在列的索引,值为0,方便后续从Cursor中准确获取对应的数据 public static final int ID_COLUMN = 0; + // 定义一个常量,表示在查询结果的Cursor中,文件夹名称(这里用NAME_COLUMN指代,可能实际对应NoteColumns.SNIPPET等相关列)所在列的索引,值为1 public static final int NAME_COLUMN = 1; + // 构造方法,接受上下文环境(Context)和一个Cursor对象作为参数,调用父类(CursorAdapter)的构造方法进行初始化, + // 这里的TODO注释表示后续可能需要完善该构造方法的具体逻辑,目前仅完成了基本的初始化调用父类构造函数操作。 public FoldersListAdapter(Context context, Cursor c) { super(context, c); // TODO Auto-generated constructor stub } + // 重写CursorAdapter的newView方法,该方法用于创建一个新的视图(View)对象,用于展示列表中的每一项数据。 + // 在这里返回一个新的FolderListItem实例,意味着每个列表项的视图将由FolderListItem来构建和管理。 @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return new FolderListItem(context); } + // 重写CursorAdapter的bindView方法,该方法用于将数据绑定到已经创建好的视图(View)上,即将Cursor中的数据填充到对应的视图控件中进行显示。 + // 首先判断传入的视图是否是FolderListItem类型,如果是,则根据Cursor中获取的数据来设置文件夹名称显示的文本内容。 + // 如果文件夹的ID等于特定的根文件夹ID(Notes.ID_ROOT_FOLDER),则显示一个特定的字符串(从资源中获取,可能是表示根文件夹的提示文本), + // 否则显示从Cursor中获取的对应名称列(NAME_COLUMN)的字符串内容,并通过FolderListItem的bind方法将名称设置到对应的TextView控件中进行显示。 @Override public void bindView(View view, Context context, Cursor cursor) { if (view instanceof FolderListItem) { @@ -57,21 +68,31 @@ public class FoldersListAdapter extends CursorAdapter { } } + // 自定义的方法,用于获取指定位置(position)的文件夹名称。 + // 通过调用getItem方法(继承自CursorAdapter)获取对应位置的Cursor对象,然后根据与bindView方法类似的逻辑, + // 判断文件夹ID是否为根文件夹ID,来决定返回特定字符串还是从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); } + // 定义一个内部类FolderListItem,继承自LinearLayout,用于表示列表中每个文件夹项对应的视图布局结构以及相关操作。 private class FolderListItem extends LinearLayout { + // 定义一个TextView控件,用于显示文件夹的名称信息。 private TextView mName; + + // 构造方法,接受上下文环境(Context)作为参数,调用父类(LinearLayout)的构造方法进行初始化, + // 然后通过inflate方法加载名为R.layout.folder_list_item的布局文件到当前视图中, + // 最后从加载后的布局中找到ID为R.id.tv_folder_name的TextView控件并赋值给mName成员变量,以便后续操作。 public FolderListItem(Context context) { super(context); inflate(context, R.layout.folder_list_item, this); mName = (TextView) findViewById(R.id.tv_folder_name); } + // 自定义的方法,用于将传入的文件夹名称字符串设置到mName(TextView)控件上进行显示,实现数据与视图的绑定操作。 public void bind(String name) { mName.setText(name); } diff --git a/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java b/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java index 96a9ff8..0023e71 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java @@ -15,7 +15,7 @@ */ package net.micode.notes.ui; - +// 这里假设你的包名是com.example.yourpackage,实际需替换为正确的包名 import android.app.Activity; import android.app.AlarmManager; import android.app.AlertDialog; @@ -71,19 +71,23 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; - +// NoteEditActivity类,继承自Activity(或AppCompatActivity,取决于实际的父类继承情况),实现了多个接口,用于处理笔记编辑相关的各种交互逻辑, +// 例如点击事件、设置变更监听、文本变更监听等,是一个用于编辑笔记的Activity public class NoteEditActivity extends Activity implements OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener { + // 内部类HeadViewHolder,用于封装笔记头部视图相关的UI组件,方便管理和操作 private class HeadViewHolder { public TextView tvModified; - + // 用于显示笔记修改时间的TextView组件 public ImageView ivAlertIcon; - + // 用于显示提醒图标(如闹钟图标等)的ImageView组件 public TextView tvAlertDate; - + // 用于显示提醒时间相关信息的TextView组件 public ImageView ibSetBgColor; + // 用于设置背景颜色的ImageView按钮组件 } + // 静态的Map,用于将背景颜色选择按钮的资源ID(如R.id.iv_bg_yellow等)映射到对应的颜色常量(如ResourceParser.YELLOW等),方便根据按钮ID获取对应的颜色标识 private static final Map sBgSelectorBtnsMap = new HashMap(); static { sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); @@ -93,6 +97,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); } + // 静态的Map,用于将颜色常量(如ResourceParser.YELLOW等)映射到对应的背景颜色选择按钮被选中时的显示资源ID(如R.id.iv_bg_yellow_select等), + // 用于在选择颜色后显示相应的选中效果 private static final Map sBgSelectorSelectionMap = new HashMap(); static { sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); @@ -102,6 +108,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); } + // 静态的Map,用于将字体大小选择按钮所在的布局资源ID(如R.id.ll_font_large等)映射到对应的字体大小常量(如ResourceParser.TEXT_LARGE等),方便根据按钮布局ID获取对应的字体大小标识 private static final Map sFontSizeBtnsMap = new HashMap(); static { sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); @@ -110,6 +117,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER); } + // 静态的Map,用于将字体大小常量(如ResourceParser.TEXT_LARGE等)映射到对应的字体大小选择按钮被选中时的显示资源ID(如R.id.iv_large_select等), + // 用于在选择字体大小后显示相应的选中效果 private static final Map sFontSelectorSelectionMap = new HashMap(); static { sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); @@ -118,46 +127,68 @@ public class NoteEditActivity extends Activity implements OnClickListener, sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select); } + // 用于日志记录的标签,方便在日志中识别该Activity相关的输出信息 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; + // 用于获取和操作应用的共享偏好设置,可用于存储和读取如字体大小、用户个性化设置等相关信息 private SharedPreferences mSharedPrefs; + + // 当前选择的字体大小ID,对应于ResourceParser中定义的字体大小常量,用于确定笔记编辑区域显示的字体大小 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'); + // 用于存储笔记编辑中多行文本(如果是列表模式等情况)的LinearLayout,每个子项可能是一个包含文本和复选框的自定义布局,方便处理多行文本编辑逻辑 private LinearLayout mEditTextList; + // 用户查询的文本内容,可能用于搜索、筛选等功能相关,比如在笔记中查找特定内容等场景 private String mUserQuery; + // 用于正则表达式匹配的Pattern对象,可能用于在笔记内容中按照特定规则查找、匹配文本等操作,具体取决于业务逻辑中如何使用 private Pattern mPattern; + // Activity创建时调用的方法,进行一些初始化操作,如设置布局、恢复Activity状态等 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + // 设置该Activity对应的布局文件,即加载笔记编辑界面的UI布局 this.setContentView(R.layout.note_edit); + // 如果是首次创建(savedInstanceState为null)且初始化Activity状态失败,则直接结束该Activity并返回 if (savedInstanceState == null && !initActivityState(getIntent())) { finish(); return; } + // 初始化各种资源,如查找UI组件、设置点击事件监听器等 initResources(); } @@ -168,6 +199,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); + // 如果保存的实例状态不为null且包含特定的用户ID信息(通过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)); @@ -179,16 +211,21 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + // 根据传入的Intent初始化Activity的状态,根据不同的Intent动作(如查看、新建/编辑等)来加载相应的笔记数据或者创建新的笔记对象 private boolean initActivityState(Intent intent) { + // 初始化正在编辑的笔记对象为null /** * If the user specified the {@link Intent#ACTION_VIEW} but not provided with id, * then jump to the NotesListActivity */ mWorkingNote = null; + // 如果Intent的动作是查看笔记(Intent.ACTION_VIEW) if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { + // 获取笔记的ID,如果没有传入则默认为0 long noteId = intent.getLongExtra(Intent.EXTRA_UID, 0); mUserQuery = ""; + // 如果Intent中包含搜索结果相关的额外数据(通过SearchManager.EXTRA_DATA_KEY标识),则更新笔记ID和用户查询文本 /** * Starting from the searched result */ @@ -197,6 +234,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, mUserQuery = intent.getStringExtra(SearchManager.USER_QUERY); } + // 检查该笔记ID对应的笔记在数据库中是否可见(是否存在且满足一定可见条件),如果不可见,则跳转到笔记列表页面,并提示笔记不存在,然后结束当前Activity if (!DataUtils.visibleInNoteDatabase(getContentResolver(), noteId, Notes.TYPE_NOTE)) { Intent jump = new Intent(this, NotesListActivity.class); startActivity(jump); @@ -204,6 +242,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, finish(); return false; } else { + // 从数据库加载对应笔记ID的笔记对象,如果加载失败则记录错误日志并结束当前Activity mWorkingNote = WorkingNote.load(this, noteId); if (mWorkingNote == null) { Log.e(TAG, "load note failed with note id" + noteId); @@ -211,19 +250,28 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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())) { + // 如果Intent的动作是新建或编辑笔记(Intent.ACTION_INSERT_OR_EDIT) + + // 获取文件夹ID,如果没有传入则默认为0 // New note long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); + // 获取小部件ID,如果没有传入则默认为无效的小部件ID(AppWidgetManager.INVALID_APPWIDGET_ID) int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); + // 获取小部件类型,如果没有传入则默认为无效的小部件类型(Notes.TYPE_WIDGET_INVALIDE) int widgetType = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, Notes.TYPE_WIDGET_INVALIDE); + // 获取背景资源ID,如果没有传入则调用ResourceParser获取默认的背景颜色资源ID int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, ResourceParser.getDefaultBgId(this)); + // 解析通话记录笔记相关信息,如果Intent中包含电话号码和通话日期,则尝试根据这些信息获取对应的笔记ID, + // 如果找到对应笔记ID则加载该笔记对象,否则创建一个空的笔记对象并转换为通话笔记格式 // Parse call-record note String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0); @@ -241,11 +289,15 @@ public class NoteEditActivity extends Activity implements OnClickListener, return false; } } else { + // 如果没有通话记录相关信息,则创建一个空的笔记对象 mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, bgResId); mWorkingNote.convertToCallNote(phoneNumber, callDate); } + + // 设置软键盘的显示模式,初始显示软键盘并且根据内容调整布局大小 } else { + // 如果Intent没有指定有效的动作,则记录错误日志并结束当前Activity,因为不支持这种情况 mWorkingNote = WorkingNote.createEmptyNote(this, folderId, widgetId, widgetType, bgResId); } @@ -258,66 +310,96 @@ public class NoteEditActivity extends Activity implements OnClickListener, finish(); return false; } + + // 设置正在编辑的笔记对象的设置状态变更监听器为当前Activity(实现了NoteSettingChangedListener接口) mWorkingNote.setOnSettingStatusChangedListener(this); return true; } + // Activity重新恢复到前台可见时调用的方法,用于重新初始化笔记编辑界面的显示内容等 @Override protected void onResume() { super.onResume(); initNoteScreen(); } + // 初始化笔记编辑界面的显示内容,如设置字体大小、根据笔记模式(是否为复选框列表模式等)显示相应内容、更新头部视图的时间显示等 private void initNoteScreen() { + // 设置笔记编辑区域的文本外观,根据当前选择的字体大小ID获取对应的文本外观资源并应用 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()); } + + // 遍历背景颜色选择按钮选中状态对应的资源ID映射表,将所有选中状态的显示组件设置为不可见,用于清除之前可能的选中显示效果 for (Integer id : sBgSelectorSelectionMap.keySet()) { findViewById(sBgSelectorSelectionMap.get(id)).setVisibility(View.GONE); } + + // 设置笔记头部视图的背景资源,根据正在编辑的笔记对象获取对应的标题背景资源ID并应用 mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); + // 设置笔记编辑区域的背景资源,根据正在编辑的笔记对象获取对应的背景颜色资源ID并应用 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() { private void showAlertHeader() { + // 判断正在编辑的笔记对象(mWorkingNote)是否设置了时钟提醒(hasClockAlert方法返回true表示设置了提醒) if (mWorkingNote.hasClockAlert()) { + // 获取当前系统时间(以毫秒为单位,从1970年1月1日00:00:00 UTC到当前时间的毫秒数) long time = System.currentTimeMillis(); + // 如果当前时间大于笔记设置的提醒时间 if (time > mWorkingNote.getAlertDate()) { + // 将提醒时间文本视图(mNoteHeaderHolder.tvAlertDate)的内容设置为表示提醒已过期的字符串资源(R.string.note_alert_expired mNoteHeaderHolder.tvAlertDate.setText(R.string.note_alert_expired); } else { + // 如果当前时间未超过提醒时间,则调用DateUtils的方法获取相对时间跨度字符串,用于显示距离提醒时间还有多久, + // 参数分别为提醒时间、当前时间以及时间跨度的单位(这里是以分钟为单位,DateUtils.MINUTE_IN_MILLIS表示一分钟对应的毫秒数) 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); }; } - + // 当Activity接收到新的Intent时会调用此方法,它首先调用父类的onNewIntent方法,然后根据新传入的Intent重新初始化Activity的状态 @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); + // 调用initActivityState方法,传入新的Intent,根据Intent中的信息来重新加载笔记数据、设置相关属性等,以适应新的启动意图, + // 比如从其他地方重新启动该Activity并传递了不同的参数,就会在此处更新Activity的状态 initActivityState(intent); } - + // 当系统即将销毁Activity以回收内存或者进行配置变更(如屏幕旋转)等情况时,会调用此方法来保存Activity的当前状态信息, + // 以便后续可以恢复到当前状态。这里先调用父类的onSaveInstanceState方法来执行默认的保存操作,然后进行自定义的保存逻辑 @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); @@ -328,51 +410,73 @@ public class NoteEditActivity extends Activity implements OnClickListener, */ if (!mWorkingNote.existInDatabase()) { saveNote(); + // 将正在编辑的笔记对象的ID存入传出的Bundle(outState)中,键为Intent.EXTRA_UID,以便后续恢复状态时能获取到该笔记的标识信息 } outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); + // 在日志中记录正在保存的工作笔记的ID信息,方便调试和查看状态保存情况,使用TAG作为日志标签方便识别是该Activity相关的日志输出 Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); } - + // 用于分发触摸事件,Activity接收到触摸事件后会先经过此方法处理,在这里可以拦截触摸事件或者将其传递给子视图进行处理。 + // 主要用于处理背景颜色选择器和字体大小选择器在显示时,触摸事件在其范围外的情况 @Override public boolean dispatchTouchEvent(MotionEvent ev) { + // 如果背景颜色选择器(mNoteBgColorSelector)当前处于可见状态,并且触摸事件不在其显示范围内(通过inRangeOfView方法判断) if (mNoteBgColorSelector.getVisibility() == View.VISIBLE && !inRangeOfView(mNoteBgColorSelector, ev)) { + // 则将背景颜色选择器设置为不可见,隐藏该选择器视 mNoteBgColorSelector.setVisibility(View.GONE); + // 返回true表示已经处理了该触摸事件,不会再继续传递给其他视图进行处理,即拦截了此次触摸事件对背景颜色选择器的后续影响 return true; } if (mFontSizeSelector.getVisibility() == View.VISIBLE && !inRangeOfView(mFontSizeSelector, ev)) { + // 将字体大小选择器设置为不可见,隐藏该选择器视图 mFontSizeSelector.setVisibility(View.GONE); + // 返回true表示已经处理了该触摸事件,拦截了此次触摸事件对字体大小选择器的后续影响,不再继续传递给其他视图 return true; } + // 如果上述条件都不满足,即触摸事件不在需要特殊处理的选择器范围外,或者选择器本身就不可见等情况, + // 则调用父类的dispatchTouchEvent方法,将触摸事件按照默认的分发逻辑继续传递给其他视图进行处理 return super.dispatchTouchEvent(ev); } - + // 此方法用于判断给定的触摸事件(MotionEvent)是否在指定的视图(view)范围内,通过比较触摸点的坐标与视图在屏幕上的坐标及尺寸来确定 private boolean inRangeOfView(View view, MotionEvent ev) { int []location = new int[2]; + // 获取指定视图在屏幕上的坐标位置,将结果存入location数组中,通过这种方式可以得到视图左上角顶点相对于屏幕左上角的坐标值 view.getLocationOnScreen(location); + // 获取视图在屏幕上的横坐标(x坐标),即location数组的第一个元素 int x = location[0]; + // 获取视图在屏幕上的纵坐标(y坐标),即location数组的第二个元素 int y = location[1]; + // 判断触摸事件的横坐标(ev.getX()获取触摸点相对于触发触摸事件的视图的横坐标)是否小于视图的横坐标(x), + // 如果小于则说明触摸点在视图的左侧,不在视图范围内,返回false if (ev.getX() < x || ev.getX() > (x + view.getWidth()) || ev.getY() < y || ev.getY() > (y + view.getHeight())) { return false; } + // 判断触摸事件的横坐标是否大于视图的横坐标加上视图的宽度(x + view.getWidth()表示视图的右侧边界横坐标), + // 如果大于则说明触摸点在视图的右侧,不在视图范围内,返回false return true; } + // 判断触摸事件的纵坐标(ev.getY()获取触摸点相对于触发触摸事件的视图的纵坐标)是否小于视图的纵坐标(y), + // 如果小于则说明触摸点在视图的上方,不在视图范围内,返回false private void initResources() { mHeadViewPanel = findViewById(R.id.note_title); + // 创建一个HeadViewHolder实例,用于存储笔记头部视图相关的各个子组件,方便后续统一操作和管理这些组件 mNoteHeaderHolder = new HeadViewHolder(); + // 通过findViewById方法查找布局文件中ID为tv_modified_date的TextView组件,该组件通常用于显示笔记的修改时间, + // 并将其赋值给mNoteHeaderHolder中的tvModified变量,方便后续操作该文本视图(如设置显示的文本内容等) 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); + 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); @@ -397,15 +501,20 @@ public class NoteEditActivity extends Activity implements OnClickListener, mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); } + // 当Activity失去焦点即将进入暂停状态时调用此方法,例如用户切换到其他应用或者按下Home键等情况。 +// 先调用父类的onPause方法执行默认的暂停操作,然后进行自定义的保存笔记数据和清理设置状态相关操作 @Override protected void onPause() { super.onPause(); + // 调用saveNote方法保存笔记数据,如果保存成功(saveNote方法返回true) if(saveNote()) { Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); } clearSettingState(); } + // 用于更新桌面小部件的显示内容,根据正在编辑的笔记对象(mWorkingNote)的小部件类型, + // 构建相应的Intent并发送广播通知系统更新对应的小部件显示 private void updateWidget() { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) { @@ -417,10 +526,13 @@ public class NoteEditActivity extends Activity implements OnClickListener, return; } + // 将正在编辑的笔记对象的小部件ID添加到Intent的额外数据中,键为AppWidgetManager.EXTRA_APPWIDGET_IDS, + // 这样接收广播的小部件更新逻辑可以知道要更新哪个具体的小部件实例 intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { mWorkingNote.getWidgetId() }); + // 处理界面上各个视图的点击事件,根据点击的视图ID来执行相应的操作逻辑,例如显示或隐藏相关视图、更新笔记的属性(如背景颜色、字体大小等) sendBroadcast(intent); setResult(RESULT_OK, intent); } @@ -452,12 +564,17 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + // 当用户按下返回键时调用此方法,先尝试清理设置状态(如隐藏背景颜色选择器、字体大小选择器等), +// 如果清理成功则直接返回,不再执行默认的返回操作;如果清理失败则先保存笔记数据,再执行默认的返回操作 @Override public void onBackPressed() { if(clearSettingState()) { return; } + // 用于清理设置状态,检查背景颜色选择器和字体大小选择器是否处于可见状态,如果处于可见则将其设置为不可见, + // 根据是否有进行隐藏操作来返回相应的布尔值(true表示有隐藏操作,false表示没有需要隐藏的视图) + // 如果清理设置状态失败,则调用saveNote方法保存笔记数据 saveNote(); super.onBackPressed(); } @@ -473,13 +590,19 @@ public class NoteEditActivity extends Activity implements OnClickListener, return false; } + // 当笔记的背景颜色发生改变时调用此方法,用于更新界面上相关视图的显示,以反映背景颜色的变化, + // 例如显示对应的背景颜色选中状态提示、设置笔记编辑区域和头部视图的背景资源等 public void onBackgroundColorChanged() { + // 根据正在编辑的笔记对象(mWorkingNote)当前的背景颜色ID,找到对应的背景颜色选择按钮选中状态显示的视图,并将其设置为可见状态, + // 用于提示用户当前选择的是哪种背景颜色(显示选中效果) findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( View.VISIBLE); mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); } + // 在准备显示选项菜单(Options Menu)时调用此方法,进行一些菜单相关的初始化操作,例如清理菜单、根据笔记的属性(如是否为通话记录笔记、是否有提醒等)加载不同的菜单布局、 + // 更新菜单项的标题显示等,最后返回true表示菜单准备就绪可以显示 @Override public boolean onPrepareOptionsMenu(Menu menu) { if (isFinishing()) { @@ -505,10 +628,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, return true; } + // 处理选项菜单中各个菜单项的点击事件,根据点击的菜单项ID来执行相应的操作逻辑,例如创建新笔记、删除笔记、设置提醒、分享笔记等 @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_new_note: + // 如果点击的菜单项是“新建笔记”(menu_new_note),则调用createNewNote方法创建一个新的笔记 createNewNote(); break; case R.id.menu_delete: @@ -523,6 +648,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, finish(); } }); + // 设置对话框的“取消”按钮(NegativeButton),点击时不执行任何操作(传入null) builder.setNegativeButton(android.R.string.cancel, null); builder.show(); break; @@ -531,10 +657,14 @@ public class NoteEditActivity extends Activity implements OnClickListener, findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); break; case R.id.menu_list_mode: + // 如果点击的菜单项是“列表模式”(menu_list_mode),则切换正在编辑的笔记对象的复选框列表模式状态, + // 如果当前是普通模式(getCheckListMode返回0)则切换为复选框列表模式(TextNote.MODE_CHECK_LIST),否则切换回普通模式(0) mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ? TextNote.MODE_CHECK_LIST : 0); break; case R.id.menu_share: + // 如果点击的菜单项是“分享”(menu_share),则先调用getWorkingText方法获取笔记的文本内容, + // 然后调用sendTo方法将笔记内容分享到支持Intent.ACTION_SEND动作且类型为text/plain的其他应用中 getWorkingText(); sendTo(this, mWorkingNote.getContent()); break; @@ -554,7 +684,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, } private void setReminder() { + // 创建一个DateTimePickerDialog实例,用于显示日期时间选择对话框,传入当前Activity的上下文 DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); + // 为DateTimePickerDialog设置日期时间设置监听器,当用户在对话框中选择好日期时间并点击确定后,会触发该监听器的OnDateTimeSet方法。 d.setOnDateTimeSetListener(new OnDateTimeSetListener() { public void OnDateTimeSet(AlertDialog dialog, long date) { mWorkingNote.setAlertDate(date , true); @@ -563,6 +695,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, d.show(); } + // 此方法用于将笔记内容分享到支持指定Intent动作(Intent.ACTION_SEND)和文本类型(text/plain)的其他应用中,比如分享到短信、邮件等应用。 /** * Share note to apps that support {@link Intent#ACTION_SEND} action * and {@text/plain} type @@ -574,6 +707,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, context.startActivity(intent); } + // 此方法用于创建一个新的笔记,操作流程为先保存当前正在编辑的笔记(如果有),然后关闭当前的NoteEditActivity,再启动一个新的NoteEditActivity进入新建笔记的流程。 private void createNewNote() { // Firstly, save current editing notes saveNote(); @@ -586,6 +720,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, startActivity(intent); } + // 此方法用于删除当前正在编辑的笔记,根据笔记是否存在于数据库以及当前应用是否处于同步模式等情况 private void deleteCurrentNote() { if (mWorkingNote.existInDatabase()) { HashSet ids = new HashSet(); @@ -608,10 +743,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, mWorkingNote.markDeleted(true); } + // 此方法用于判断当前应用是否处于同步模式,通过获取同步账户名称(调用NotesPreferenceActivity的getSyncAccountName方法)并检查其去除空格后的长度是否大于0来确定。 private boolean isSyncMode() { return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; } + // 此方法用于处理笔记的时钟提醒相关设置的变更,例如设置或取消提醒时间等操作,在操作前会先确保笔记已保存(如果未保存则先保存),然后根据不同情况与系统的闹钟服务(AlarmManager)交互来设置或取消提醒。 public void onClockAlertChanged(long date, boolean set) { /** * User could set clock to an unsaved note, so before setting the @@ -642,10 +779,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + // 此方法用于处理在笔记编辑的多行文本列表中删除某一行文本的操作,会调整后续行的索引,并将删除行的文本内容合并到前一行(如果是第一行则直接处理该行),同时更新焦点和光标位置等。 public void onWidgetChanged() { updateWidget(); } + // 遍历从要删除行的下一行(index + 1)开始到最后一行的所有行,调整它们的索引,将其索引减1,使其与新的行数顺序对应。 public void onEditTextDelete(int index, String text) { int childCount = mEditTextList.getChildCount(); if (childCount == 1) { @@ -657,6 +796,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, .setIndex(i - 1); } + // 从多行文本列表中移除指定索引(index)的行视图。 mEditTextList.removeViewAt(index); NoteEditText edit = null; if(index == 0) { @@ -672,6 +812,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, edit.setSelection(length); } + // 此方法用于处理在笔记编辑的多行文本列表中插入一行新文本的操作,会在指定索引位置添加新的行视图,更新相关行的索引,并设置新行的文本内容、焦点以及光标位置等。 public void onEditTextEnter(int index, String text) { /** * Should not happen, check for debug @@ -691,7 +832,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + private void switchToListMode(String text) { + // 首先移除多行文本列表(mEditTextList)中已有的所有视图,清空列表内容,为重新构建列表做准备。 mEditTextList.removeAllViews(); String[] items = text.split("\n"); int index = 0; @@ -701,17 +844,24 @@ public class NoteEditActivity extends Activity implements OnClickListener, index++; } } + // 在多行文本列表末尾添加一个空的列表项视图,可能用于方便用户后续继续添加新的内容,提供一个空白的可编辑项。 mEditTextList.addView(getListItem("", index)); + // 获取多行文本列表中最后一个添加的列表项(即索引为 index 的项)中的文本编辑框(NoteEditText)组件,并请求获取焦点, + // 使得用户切换到列表模式后,光标默认处于最后一个可编辑项处,方便用户直接输入内容。 mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus(); mNoteEditor.setVisibility(View.GONE); mEditTextList.setVisibility(View.VISIBLE); } - +// 此方法用于在给定的完整文本(fullText)中,根据用户输入的查询文本(userQuery)查找匹配的内容,并将匹配的内容设置为高亮显示,最后返回处理后的包含高亮显示效果的 Spannable 对象。 private Spannable getHighlightQueryResult(String fullText, String userQuery) { + // 创建一个 SpannableString 对象,将传入的完整文本(如果传入的 fullText 为 null,则初始化为空字符串)包装起来, + // 以便后续可以对其内容进行样式设置(如设置高亮显示等)操作,SpannableString 类实现了 Spannable 接口,支持设置文本样式。 SpannableString spannable = new SpannableString(fullText == null ? "" : fullText); if (!TextUtils.isEmpty(userQuery)) { mPattern = Pattern.compile(userQuery); + // 创建一个 Matcher 对象,通过调用 Pattern 对象的 matcher 方法并传入完整文本(fullText),用于在文本中执行具体的匹配查找任务, + // 可以通过其方法判断是否找到匹配内容以及获取匹配的位置等信息。 Matcher m = mPattern.matcher(fullText); int start = 0; while (m.find(start)) { @@ -723,10 +873,14 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } return spannable; + // 返回处理后的包含高亮显示效果的 SpannableString 对象,该对象可以用于在界面上展示带有高亮查询结果的文本内容。 } private View getListItem(String item, int index) { + // 通过LayoutInflater从当前Activity的上下文(this)加载一个布局资源(R.layout.note_edit_list_item)创建一个视图对象, + // 该布局资源定义了列表项的具体界面结构,包含文本编辑框、复选框等组件的布局样式。 View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null); + // 从创建的视图中获取文本编辑框(NoteEditText)组件,用于后续设置文本外观、内容以及添加事件监听器等操作。 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)); @@ -735,11 +889,14 @@ public class NoteEditActivity extends Activity implements OnClickListener, if (isChecked) { edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); } else { + // 如果复选框未被选中,设置文本编辑框(edit)的绘制标志为默认的文本抗锯齿(Paint.ANTI_ALIAS_FLAG)和字距调整(Paint.DEV_KERN_TEXT_FLAG)标志, + // 恢复文本的正常显示样式 edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); } } }); + // 判断列表项文本(item)是否以特定的标记(TAG_CHECKED)开头,如果是,表示该项初始状态为已选中状态。 if (item.startsWith(TAG_CHECKED)) { cb.setChecked(true); edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); @@ -750,20 +907,31 @@ public class NoteEditActivity extends Activity implements OnClickListener, item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); } + // 为文本编辑框(edit)设置文本内容变化的监听器(OnTextViewChangeListener),这里传入当前Activity(this),可能在监听器中实现了相应的逻辑, + // 用于在文本编辑框内容改变时进行一些处理,比如实时更新相关数据等操作。 edit.setOnTextViewChangeListener(this); edit.setIndex(index); + // 设置文本编辑框(edit)的文本内容,通过调用 getHighlightQueryResult 方法,根据当前列表项文本(item)和用户查询文本(mUserQuery)获取带有高亮显示效果的文本内容, + // 并设置到文本编辑框中展示给用户。 edit.setText(getHighlightQueryResult(item, mUserQuery)); return view; } public void onTextChange(int index, boolean hasText) { + // 判断传入的索引(index)是否大于等于多行文本列表(mEditTextList)中的子项数量(即行数),如果大于等于则说明索引超出范围, + // 记录错误日志提示不应该出现这种情况,然后直接返回,不进行后续操作。 if (index >= mEditTextList.getChildCount()) { Log.e(TAG, "Wrong index, should not happen"); return; } + // 如果文本编辑框中有文本内容(hasText 为 true),则获取多行文本列表中对应索引位置的视图,并找到其中的复选框(CheckBox)组件,将其设置为可见状态, + // 以便用户可以看到并操作该复选框,通常用于有文本内容时可进行选中相关操作。 + if(hasText) { mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE); } else { + // 如果文本编辑框中没有文本内容(hasText 为 false),则获取多行文本列表中对应索引位置的视图,并找到其中的复选框(CheckBox)组件,将其设置为不可见状态, + // 隐藏复选框,因为没有文本内容时可能不需要显示复选框进行相关操作。 mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE); } } @@ -777,6 +945,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, "")); } mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); + // 设置笔记编辑框(mNoteEditor)的文本内容,通过调用 getHighlightQueryResult 方法,根据正在编辑的笔记对象(mWorkingNote)的内容和用户查询文本(mUserQuery), + // 获取带有高亮显示效果的文本内容,并设置到笔记编辑框中展示给用户,恢复普通文本编辑模式下的文本显示。 mEditTextList.setVisibility(View.GONE); mNoteEditor.setVisibility(View.VISIBLE); } @@ -784,6 +954,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, private boolean getWorkingText() { boolean hasChecked = false; + // 判断正在编辑的笔记对象(mWorkingNote)的复选框列表模式(通过 getCheckListMode 方法获取)是否为 TextNote.MODE_CHECK_LIST(即处于复选框列表模式)。 if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < mEditTextList.getChildCount(); i++) { @@ -792,8 +963,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, 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"); + // 如果复选框处于选中状态,将特定的选中标记(TAG_CHECKED)、空格以及文本编辑框中的文本内容拼接起来,并添加换行符, + // 按照特定格式构建列表项文本内容,添加到 StringBuilder 中,同时将 hasChecked 标记为 true,表示存在已选中的列表项。 hasChecked = true; } else { + // 如果笔记对象不处于复选框列表模式,则直接将笔记编辑框(mNoteEditor)中的文本内容(通过调用其 getText 方法获取文本内容并转换为字符串), + // 设置为正在编辑的笔记对象(mWorkingNote)的工作文本内容,通过调用其 setWorkingText 方法实现。 sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n"); } } @@ -818,6 +993,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, */ setResult(RESULT_OK); } + // 返回保存操作是否成功的布尔值,以便调用此方法的地方根据返回值进行相应的后续处理,比如提示用户保存成功与否等情况。 return saved; } @@ -844,6 +1020,8 @@ public class NoteEditActivity extends Activity implements OnClickListener, sender.putExtra("duplicate", true); sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); showToast(R.string.info_note_enter_desktop); + // 此方法用于将正在编辑的笔记相关的快捷方式发送到桌面,在操作前会先确保笔记已保存(如果是新笔记则先调用saveNote方法保存), + // 然后根据笔记的ID是否有效(大于0表示有效)构建相应的Intent并发送广播来创建桌面快捷方式,若笔记ID无效则提示用户输入内容。 sendBroadcast(sender); } else { /** @@ -856,9 +1034,13 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + // 此方法用于处理正在编辑的笔记内容文本,去除其中特定的标记(如选中、未选中标记),并根据设定的最大长度限制(SHORTCUT_ICON_TITLE_MAX_LEN)对文本进行截断处理, + // 最终返回处理后的适合作为桌面快捷方式图标的标题文本。 private String makeShortcutIconTitle(String content) { content = content.replace(TAG_CHECKED, ""); content = content.replace(TAG_UNCHECKED, ""); + // 判断处理后的内容文本长度是否大于设定的快捷方式图标标题最大长度(SHORTCUT_ICON_TITLE_MAX_LEN),如果大于,则截取从开头到最大长度位置的子字符串作为最终结果, + // 对文本进行截断处理,以满足长度限制要求;如果不大于,则直接返回原内容文本作为最终的快捷方式图标标题文本。 return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0, SHORTCUT_ICON_TITLE_MAX_LEN) : content; } @@ -867,6 +1049,9 @@ public class NoteEditActivity extends Activity implements OnClickListener, showToast(resId, Toast.LENGTH_SHORT); } + // 此重载方法用于根据传入的提示信息对应的资源ID(resId)以及显示时长参数(duration),创建并显示一个Toast提示信息, + // 通过调用Toast的makeText方法传入当前Activity的上下文(this)、提示信息资源ID以及显示时长,然后调用show方法将Toast显示出来, + // 用于在应用中向用户展示简短的提示信息,如操作成功、失败等提示内容。 private void showToast(int resId, int duration) { Toast.makeText(this, resId, duration).show(); } diff --git a/src/Notes-master/src/net/micode/notes/ui/NoteEditText.java b/src/Notes-master/src/net/micode/notes/ui/NoteEditText.java index 2afe2a8..3f60b71 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NoteEditText.java +++ b/src/Notes-master/src/net/micode/notes/ui/NoteEditText.java @@ -37,22 +37,35 @@ 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"; + // 用于记录当前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:" ; + // 创建一个HashMap,用于存储不同协议头字符串与对应的资源ID的映射关系, + // 资源ID可能对应着不同链接类型在界面上显示的提示字符串等信息,方便后续根据链接类型获取相应的显示资源。 private static final Map sSchemaActionResMap = new HashMap(); + // 静态代码块,用于初始化sSchemaActionResMap,将不同协议头字符串与对应的资源ID进行关联。 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); } + // 定义一个内部接口OnTextViewChangeListener,用于与外部类进行交互,通知外部关于文本编辑视图的一些重要事件变化, + // 外部类需要实现该接口来响应这些事件。 /** * Call by the {@link NoteEditActivity} to delete or add edit text */ @@ -60,60 +73,88 @@ public class NoteEditText extends EditText { /** * Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens * and the text is null + * 当按下删除键(KEYCODE_DEL)并且文本内容为空时,调用该方法删除当前的编辑文本,外部实现类可在此方法中执行相应的删除逻辑, + * 参数index表示当前NoteEditText实例的索引,text表示当前编辑文本的内容字符串。 */ void onEditTextDelete(int index, String text); - /** + /** * Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER} * happen + * 当按下回车键(KEYCODE_ENTER)时,调用该方法在当前编辑文本之后添加新的编辑文本,外部实现类可在此方法中执行相应的添加逻辑, + * 参数index表示当前NoteEditText实例的索引,text表示当前编辑文本中光标位置之后的文本内容字符串(可能用于新添加编辑文本的初始内容等)。 */ void onEditTextEnter(int index, String text); - - /** +/** * Hide or show item option when text change + * 当文本内容发生变化时,调用该方法来决定是否隐藏或显示相关的选项(具体取决于外部实现逻辑), + * 参数index表示当前NoteEditText实例的索引,hasText表示当前编辑文本是否有内容(true表示有内容,false表示无内容)。 */ void onTextChange(int index, boolean hasText); } + // 用于存储实现了OnTextViewChangeListener接口的实例,通过设置该实例,使得外部类能够监听该文本编辑视图的相关事件变化。 private OnTextViewChangeListener mOnTextViewChangeListener; - + + // 构造方法,接受一个Context参数,用于创建NoteEditText实例,调用父类(EditText)的构造方法进行初始化, + // 同时将当前实例的索引mIndex初始化为0。 public NoteEditText(Context context) { super(context, null); mIndex = 0; } + // 设置当前NoteEditText实例在一组编辑文本中的索引位置,方便在事件处理等逻辑中区分不同的文本编辑区域。 public void setIndex(int index) { mIndex = index; } + // 设置用于监听文本编辑视图相关事件变化的监听器实例,外部类实现OnTextViewChangeListener接口后,通过传入该实例, + // 使得本类能够在相应事件发生时通知外部类进行处理。 public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { mOnTextViewChangeListener = listener; } + // 构造方法,接受一个Context和一个AttributeSet参数,用于创建NoteEditText实例, + // 调用父类(EditText)带有特定样式属性(android.R.attr.editTextStyle)的构造方法进行初始化, + // 该样式属性会应用默认的EditText样式到当前实例上。 public NoteEditText(Context context, AttributeSet attrs) { super(context, attrs, android.R.attr.editTextStyle); } + // 构造方法,接受一个Context、AttributeSet和defStyle参数,用于创建NoteEditText实例, + // 调用父类(EditText)的构造方法进行初始化,这里的TODO注释表示后续可能需要完善该构造方法的具体逻辑,目前仅完成了基本的调用父类构造函数操作。 public NoteEditText(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub } + // 重写onTouchEvent方法,用于处理触摸事件。主要功能是在用户按下(ACTION_DOWN)屏幕时,根据触摸点的位置来设置文本的选择位置。 + // 通过一系列坐标计算,获取触摸点所在的文本行以及在该行中的水平偏移位置,进而将文本选择位置设置到对应的位置上,方便用户进行后续操作, + // 最后调用父类的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 -= 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; } @@ -121,6 +162,9 @@ public class NoteEditText extends EditText { return super.onTouchEvent(event); } + // 重写onKeyDown方法,用于处理按键按下事件。主要针对回车键(KEYCODE_ENTER)和删除键(KEYCODE_DEL)进行特殊处理, + // 当按下回车键时,如果设置了OnTextViewChangeListener监听器,则直接返回false(可能后续由外部决定是否处理回车键按下的逻辑); + // 当按下删除键时,记录下当前文本选择的起始位置,以便后续在删除键抬起时判断相关删除逻辑,其他按键则按照父类默认逻辑处理。 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { @@ -138,6 +182,12 @@ public class NoteEditText extends EditText { return super.onKeyDown(keyCode, event); } + // 重写onKeyUp方法,用于处理按键抬起事件。针对删除键(KEYCODE_DEL)和回车键(KEYCODE_ENTER)进行特定逻辑处理, + // 当删除键抬起时,如果设置了OnTextViewChangeListener监听器,并且当前文本选择起始位置为0且当前实例索引不为0(可能表示删除当前文本块的情况), + // 则调用监听器的onEditTextDelete方法通知外部进行删除操作,并返回true表示已处理该事件; + // 当回车键抬起时,如果设置了OnTextViewChangeListener监听器,则获取当前文本选择位置后的文本内容,将当前文本内容截取到选择位置之前, + // 然后调用监听器的onEditTextEnter方法通知外部添加新的编辑文本,并传入相关参数,最后返回父类的onKeyUp方法处理结果继续处理其他按键抬起相关逻辑。 + @Override @Override public boolean onKeyUp(int keyCode, KeyEvent event) { switch(keyCode) { @@ -167,6 +217,9 @@ public class NoteEditText extends EditText { return super.onKeyUp(keyCode, event); } + // 重写onFocusChanged方法,用于处理焦点变化事件。当焦点发生变化时,如果设置了OnTextViewChangeListener监听器, + // 根据当前是否失去焦点以及文本内容是否为空,调用监听器的onTextChange方法通知外部进行相应的显示选项等逻辑处理, + // 最后调用父类的onFocusChanged方法继续处理其他焦点变化相关逻辑。 @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { if (mOnTextViewChangeListener != null) { @@ -179,6 +232,12 @@ public class NoteEditText extends EditText { super.onFocusChanged(focused, direction, previouslyFocusedRect); } + // 重写onCreateContextMenu方法,用于创建上下文菜单(通常长按文本等操作时弹出)。 + // 如果当前编辑文本内容是Spanned类型(表示包含富文本信息,比如链接等),则获取当前文本选择区域内的URLSpan对象数组(可能包含多个链接), + // 如果只有一个链接,则根据链接的URL判断其协议头(如是否是tel、http、mailto等),从sSchemaActionResMap中获取对应的资源ID, + // 如果没有匹配的协议头,则使用默认资源ID(R.string.note_link_other),然后创建一个菜单项添加到上下文菜单中, + // 并设置菜单项的点击监听器,当点击该菜单项时,调用对应的URLSpan的onClick方法(通常会触发相应的链接跳转等操作), + // 最后调用父类的onCreateContextMenu方法继续添加其他默认的上下文菜单选项等操作。 @Override protected void onCreateContextMenu(ContextMenu menu) { if (getText() instanceof Spanned) { diff --git a/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java b/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java index 0f5a878..1768560 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java +++ b/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java @@ -25,8 +25,9 @@ import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.tool.DataUtils; - +// NoteItemData类用于封装笔记相关的数据信息,从数据库查询结果(Cursor)中提取并整理数据,同时提供了一些方法用于获取和判断这些数据的相关属性。 public class NoteItemData { + // 定义一个字符串数组,用于指定从数据库查询笔记相关信息时需要获取的列名,涵盖了笔记的各种属性,如ID、提醒日期、背景颜色ID、创建日期等等。 static final String [] PROJECTION = new String [] { NoteColumns.ID, NoteColumns.ALERTED_DATE, @@ -42,6 +43,7 @@ public class NoteItemData { NoteColumns.WIDGET_TYPE, }; + // 定义一系列常量,用于表示各个数据列在查询结果的Cursor中的索引位置,方便后续从Cursor中准确获取对应的数据,提高代码的可读性和可维护性。 private static final int ID_COLUMN = 0; private static final int ALERTED_DATE_COLUMN = 1; private static final int BG_COLOR_ID_COLUMN = 2; @@ -55,14 +57,249 @@ public class NoteItemData { private static final int WIDGET_ID_COLUMN = 10; private static final int WIDGET_TYPE_COLUMN = 11; + // 笔记的唯一标识符,存储从数据库获取的笔记ID值。 private long mId; + // 笔记的提醒日期,存储对应的时间戳信息,若没有提醒则可能为0等特定值。 private long mAlertDate; + // 笔记的背景颜色ID,用于确定笔记在界面上显示的背景颜色相关设置。 private int mBgColorId; + // 笔记的创建日期,存储对应的时间戳信息,用于记录笔记创建的时间。 private long mCreatedDate; + // 标记笔记是否有附件,通过从数据库获取的值转换为布尔类型(大于0表示有附件,设为true,否则为false)。 private boolean mHasAttachment; + // 笔记的最后修改日期,存储对应的时间戳信息,用于记录笔记最后一次被修改的时间。 private long mModifiedDate; + // 与笔记相关的数量信息(具体含义可能根据业务逻辑确定,比如关联的子笔记数量等),从数据库获取对应整数值。 private int mNotesCount; + // 笔记的父级ID,用于表示笔记所属的文件夹等上级容器的ID,通过数据库查询获取对应长整数值。 private long mParentId; + // 笔记的摘要信息(可能是简短描述等内容),从数据库获取字符串内容,并进行一些特定字符串替换操作(去除一些标记字符串)。 + private String mSnippet; + // 笔记的类型,通过从数据库获取的整数值来表示不同类型的笔记(具体类型值的含义由业务逻辑定义,如普通笔记、系统笔记等)。 + private int mType; + // 笔记关联的小部件ID(如果有相关小部件的话),从数据库获取对应整数值。 + private int mWidgetId; + // 笔记关联的小部件类型(同样根据业务逻辑确定不同类型小部件的表示值),从数据库获取对应整数值。 + private int mWidgetType; + // 联系人姓名相关信息,初始为空字符串,后续根据笔记所属文件夹等情况可能从联系人数据中获取并赋值。 + private String mName; + // 电话号码相关信息,初始为空字符串,若笔记属于通话记录文件夹等相关情况,会尝试获取对应的电话号码。 + private String mPhoneNumber; + + // 标记当前笔记数据对应的记录是否是查询结果中的最后一项,通过在构造方法中根据Cursor判断并赋值。 + private boolean mIsLastItem; + // 标记当前笔记数据对应的记录是否是查询结果中的第一项,通过在构造方法中根据Cursor判断并赋值。 + private boolean mIsFirstItem; + // 标记查询结果中是否只有一条记录(即当前笔记数据是否是唯一的一条记录),通过在构造方法中根据Cursor记录数量判断并赋值。 + private boolean mIsOnlyOneItem; + // 标记当前笔记是否是某个文件夹后面仅跟随的一条笔记(根据笔记类型以及前后记录的情况判断),通过在构造方法中特定逻辑判断并赋值。 + private boolean mIsOneNoteFollowingFolder; + // 标记当前笔记是否是某个文件夹后面跟随的多条笔记中的一条(根据笔记类型以及前后记录的情况判断),通过在构造方法中特定逻辑判断并赋值。 + private boolean mIsMultiNotesFollowingFolder; + + // 构造方法,接受上下文环境(Context)和一个Cursor对象作为参数,用于从Cursor中提取并初始化笔记相关的数据信息,同时进行一些额外的逻辑判断和数据处理。 + public NoteItemData(Context context, Cursor cursor) { + // 从Cursor中获取笔记的ID值,并赋值给成员变量mId。 + mId = cursor.getLong(ID_COLUMN); + // 从Cursor中获取笔记的提醒日期时间戳,并赋值给成员变量mAlertDate。 + mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); + // 从Cursor中获取笔记的背景颜色ID,并赋值给成员变量mBgColorId。 + mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); + // 从Cursor中获取笔记的创建日期时间戳,并赋值给成员变量mCreatedDate。 + mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN); + // 根据从Cursor中获取的表示是否有附件的整数值,转换为布尔类型赋值给mHasAttachment,大于0表示有附件,设为true,否则为false。 + mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0)? true : false; + // 从Cursor中获取笔记的最后修改日期时间戳,并赋值给成员变量mModifiedDate。 + mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN); + // 从Cursor中获取笔记相关的数量信息(如子笔记数量等),并赋值给成员变量mNotesCount。 + mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); + // 从Cursor中获取笔记的父级ID,并赋值给成员变量mParentId。 + mParentId = cursor.getLong(PARENT_ID_COLUMN); + // 从Cursor中获取笔记的摘要信息字符串,赋值给成员变量mSnippet,并进行特定字符串替换操作,去除可能存在的一些标记字符串(如NoteEditActivity.TAG_CHECKED和NoteEditActivity.TAG_UNCHECKED)。 + mSnippet = cursor.getString(SNIPPET_COLUMN); + mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace( + NoteEditActivity.TAG_UNCHECKED, ""); + // 从Cursor中获取笔记的类型整数值,并赋值给成员变量mType。 + mType = cursor.getInt(TYPE_COLUMN); + // 从Cursor中获取笔记关联的小部件ID,并赋值给成员变量mWidgetId。 + mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); + // 从Cursor中获取笔记关联的小部件类型整数值,并赋值给成员变量mWidgetType。 + mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); + + // 初始化电话号码为空字符串,后续根据笔记所属文件夹情况判断是否获取实际电话号码。 + mPhoneNumber = ""; + // 如果笔记的父级ID等于特定的通话记录文件夹ID(Notes.ID_CALL_RECORD_FOLDER),则尝试从数据中获取对应的电话号码。 + if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { + mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); + // 如果获取到的电话号码不为空字符串,尝试通过电话号码获取对应的联系人姓名。 + if (!TextUtils.isEmpty(mPhoneNumber)) { + mName = Contact.getContact(context, mPhoneNumber); + // 如果获取联系人姓名失败(返回null),则将电话号码作为联系人姓名显示(可能是一种兜底处理方式)。 + if (mName == null) { + mName = mPhoneNumber; + } + } + } + + // 如果联系人姓名仍为null(可能前面获取过程都失败了),则将其设置为空字符串,确保该成员变量有合理的默认值。 + if (mName == null) { + mName = ""; + } + + // 调用checkPostion方法,根据Cursor的相关信息判断并设置当前笔记数据在查询结果中的位置相关属性,如是否是第一项、最后一项等以及与文件夹关联的笔记数量情况等属性。 + checkPostion(cursor); + } + + // 私有方法,用于根据Cursor的相关信息判断并设置当前笔记数据在查询结果中的位置相关属性,如是否是第一项、最后一项等以及与文件夹关联的笔记数量情况等属性。 + private void checkPostion(Cursor cursor) { + // 根据Cursor的isLast方法判断当前记录是否是最后一项,将结果赋值给mIsLastItem成员变量。 + mIsLastItem = cursor.isLast()? true : false; + // 根据Cursor的isFirst方法判断当前记录是否是第一项,将结果赋值给mIsFirstItem成员变量。 + mIsFirstItem = cursor.isFirst()? true : false; + // 根据Cursor的记录数量判断是否只有一条记录,将结果赋值给mIsOnlyOneItem成员变量。 + mIsOnlyOneItem = (cursor.getCount() == 1); + // 初始设置为当前笔记不是某个文件夹后面跟随的多条笔记中的一条,后续根据具体逻辑判断是否更改该值。 + mIsMultiNotesFollowingFolder = false; + // 初始设置为当前笔记不是某个文件夹后面仅跟随的一条笔记,后续根据具体逻辑判断是否更改该值。 + mIsOneNoteFollowingFolder = false; + + // 如果笔记类型是普通笔记(Notes.TYPE_NOTE)并且不是第一项记录(说明前面可能有其他记录),则进行以下判断逻辑。 + if (mType == Notes.TYPE_NOTE &&!mIsFirstItem) { + // 获取当前记录在Cursor中的位置索引。 + int position = cursor.getPosition(); + // 将Cursor指针移动到前一条记录(用于查看前一条记录的类型等信息)。 + if (cursor.moveToPrevious()) { + // 判断前一条记录的类型是否是文件夹类型(Notes.TYPE_FOLDER)或者系统类型(Notes.TYPE_SYSTEM),如果是,则说明当前笔记可能与文件夹存在关联情况,继续后续判断。 + if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER + || cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) { + // 判断Cursor的总记录数是否大于当前位置索引加1(即当前笔记后面是否还有其他记录),如果是,则说明当前笔记是某个文件夹后面跟随的多条笔记中的一条,设置相应标记为true。 + if (cursor.getCount() > (position + 1)) { + mIsMultiNotesFollowingFolder = true; + } else { + // 否则说明当前笔记是某个文件夹后面仅跟随的一条笔记,设置相应标记为true。 + mIsOneNoteFollowingFolder = true; + } + } + // 将Cursor指针再移回原来的位置(确保后续操作不受影响,因为前面移动了指针),如果移动失败则抛出异常(表示出现了不合理的游标操作情况)。 + 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; + } + + // 判断当前笔记数据对应的记录是否是查询结果中的第一项,返回对应的布尔值。 + public boolean isFirst() { + return mIsFirstItem; + } + + // 判断查询结果中是否只有当前这一条笔记数据记录,返回对应的布尔值。 + public boolean isSingle() { + return mIsOnlyOneItem; + } + + // 获取笔记的唯一标识符ID,返回对应的长整数值。 + public long getId() { + return mId; + } + + // 获取笔记的提醒日期时间戳,返回对应的长整数值。 + public long getAlertDate() { + return mAlertDate; + } + + // 获取笔记的创建日期时间戳,返回对应的长整数值。 + public long getCreatedDate() { + return mCreatedDate; + } + + // 判断笔记是否有附件,返回对应的布尔值。 + public boolean hasAttachment() { + return mHasAttachment; + } + + // 获取笔记的最后修改日期时间戳,返回对应的长整数值。 + public long getModifiedDate() { + return mModifiedDate; + } + + // 获取笔记的背景颜色ID,返回对应的整数值。 + public int getBgColorId() { + return mBgColorId; + } + + // 获取笔记的父级ID,返回对应的长整数值。 + public long getParentId() { + return mParentId; + } + + // 获取笔记相关的数量信息(如子笔记数量等),返回对应的整数值。 + public int getNotesCount() { + return mNotesCount; + } + + // 获取笔记所属文件夹的ID(与getParentId方法返回值相同,可能为了语义更清晰在不同场景使用),返回对应的长整数值。 + public long getFolderId () { + return mParentId; + } + + // 获取笔记的类型(如普通笔记、文件夹类型等,根据业务逻辑定义的类型值),返回对应的整数值。 + public int getType() { + return mType; + } + + // 获取笔记关联的小部件类型(根据业务逻辑定义的不同小部件类型值),返回对应的整数值。 + public int getWidgetType() { + return mWidgetType; + } + + // 获取笔记关联的小部件ID,返回对应的整数值。 + public int getWidgetId() { + return mWidgetId; + } + + // 获取笔记的摘要信息字符串,返回对应的字符串内容。 + public String getSnippet() { + return mSnippet; + } + + // 判断笔记是否设置了提醒(通过提醒日期是否大于0来判断),返回对应的布尔值。 + public boolean hasAlert() { + return (mAlertDate > 0); + } + + // 判断笔记是否属于通话记录(通过父级ID是否是通话记录文件夹ID以及电话号码是否为空来判断),返回对应的布尔值。 + public boolean isCallRecord() { + return (mParentId == Notes.ID_CALL_RECORD_FOLDER &&!TextUtils.isEmpty(mPhoneNumber)); + } + + // 静态方法,用于从给定的Cursor中获取笔记的类型(通过获取对应类型列的整数值),返回对应的整数值,方便在其他地方直接获取笔记类型信息而无需创建NoteItemData实例。 + public static int getNoteType(Cursor cursor) { + return cursor.getInt(TYPE_COLUMN); + } +} +希望上述注释后的代码能帮助你清晰地理解 NoteItemData 类的具体功能、数据处理逻辑以及各个方法的作用。如果还有其他疑问,请随时向我提问。需要注意的是,代码中涉及到的部分类(如 NoteColumns、Notes、DataUtils、Contact、NoteEditActivity)的具体实现没有给出,可能需要结合它们的完整定义来进一步深入理解相关业务逻辑细节。 + private String mSnippet; private int mType; private int mWidgetId; diff --git a/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java b/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java index e843aec..1456ec7 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java @@ -147,6 +147,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt setAppInfoFromRawRes(); } + // 当启动的其他Activity返回结果时调用此方法,用于处理返回的结果数据,根据请求码(requestCode)和结果码(resultCode)来执行相应的逻辑。 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK @@ -157,6 +158,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + // 此方法用于从原始资源文件(可能是文本文件等)中读取应用的介绍信息,并将其保存为一条笔记记录,同时将一个表示是否已添加介绍信息的共享偏好设置项设置为true, +// 用于标记已经完成了首次使用时介绍信息的插入操作,下次启动应用时就不会重复执行此操作。 private void setAppInfoFromRawRes() { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) { @@ -190,6 +193,9 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + // 创建一个空的WorkingNote对象,通过调用WorkingNote的createEmptyNote静态方法,传入当前Activity的上下文(this)、根文件夹ID(Notes.ID_ROOT_FOLDER)、 + // 无效的桌面小部件ID(AppWidgetManager.INVALID_APPWIDGET_ID)、无效的小部件类型(Notes.TYPE_WIDGET_INVALIDE)以及默认的颜色资源(ResourceParser.RED)等参数, + // 用于构建一个初始的笔记对象,准备保存介绍信息到该笔记中。 WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER, AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE, ResourceParser.RED); @@ -210,7 +216,12 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } private void initResources() { + // 获取系统的内容解析器(ContentResolver),通过调用当前Activity的 `getContentResolver` 方法, + // 内容解析器用于与应用的内容提供器(Content Provider)交互,可对数据库等数据源进行查询、插入、更新、删除等操作, + // 在这里可能用于后续查询笔记数据等相关操作。 mContentResolver = this.getContentResolver(); + // 创建一个 `BackgroundQueryHandler` 实例,传入刚才获取的内容解析器,这个类可能是自定义的用于在后台线程处理查询操作的处理器, + // 以便在执行一些耗时的数据查询等操作时,不会阻塞主线程,保证界面的流畅性。 mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); mCurrentFolderId = Notes.ID_ROOT_FOLDER; mNotesListView = (ListView) findViewById(R.id.notes_list); @@ -232,8 +243,11 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener { + // 定义一个 `DropdownMenu` 类型的成员变量,用于展示下拉菜单,可能包含一些与多选操作相关的额外功能选项, + // 具体功能和样式由 `DropdownMenu` 类的实现决定,比如全选、反选等操作可能会放在这个下拉菜单中。 private DropdownMenu mDropDownMenu; private ActionMode mActionMode; + // 当进入多选操作模式时(例如用户长按 `ListView` 中的某个项后进入多选模式),会调用此方法来创建操作菜单等初始化操作。 private MenuItem mMoveMenu; public boolean onCreateActionMode(ActionMode mode, Menu menu) { @@ -252,6 +266,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt mNotesListView.setLongClickable(false); mAddNewNote.setVisibility(View.GONE); + // 通过 `LayoutInflater` 从当前Activity的上下文(NotesListActivity.this,这里使用外部类名来明确上下文)加载一个布局资源(R.layout.note_list_dropdown_menu)创建一个视图, + // 这个视图将作为操作模式的自定义视图,用于展示一些额外的操作界面元素,比如上述提到的下拉菜单等内容。 View customView = LayoutInflater.from(NotesListActivity.this).inflate( R.layout.note_list_dropdown_menu, null); mode.setCustomView(customView); @@ -262,6 +278,7 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt public boolean onMenuItemClick(MenuItem item) { mNotesListAdapter.selectAll(!mNotesListAdapter.isAllSelected()); updateMenu(); + // 返回 `true` 表示操作模式创建成功,系统可以继续进行后续的多选模式相关操作,比如显示操作菜单等。 return true; } @@ -286,6 +303,10 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } + // 此方法是 `ListView.MultiChoiceModeListener` 接口中的方法,当用户在多选操作模式下点击了操作菜单中的某个菜单项时会被调用, +// 开发者需要在此方法中编写逻辑来处理不同菜单项点击对应的具体业务操作,比如点击“删除”菜单项执行删除选中笔记的操作等。 +// 目前方法体中是自动生成的 `TODO` 注释,意味着还需要根据实际需求添加相应的业务逻辑代码来处理菜单项点击事件, +// 返回值为 `false`,同样按照接口的默认约定,如果返回 `false` 可能表示未处理该点击事件(具体也要看使用场景和框架的要求)。 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { // TODO Auto-generated method stub return false; @@ -296,12 +317,16 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt return false; } + // 此方法是 `ListView.MultiChoiceModeListener` 接口中的方法,当多选操作模式被销毁(例如用户按下返回键或者完成多选操作等情况退出多选模式)时会被调用, + // 用于在操作模式结束后进行一些清理和恢复界面初始状态的操作。 public void onDestroyActionMode(ActionMode mode) { mNotesListAdapter.setChoiceMode(false); mNotesListView.setLongClickable(true); mAddNewNote.setVisibility(View.VISIBLE); } + // 此方法用于手动结束当前的操作模式,通过调用 `mActionMode`(在 `onCreateActionMode` 方法中初始化的 `ActionMode` 对象)的 `finish` 方法来实现, + // 触发操作模式的销毁流程,会进而调用 `onDestroyActionMode` 等相关方法进行后续的清理和界面恢复操作。 public void finishActionMode() { mActionMode.finish(); } @@ -313,10 +338,17 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } public boolean onMenuItemClick(MenuItem item) { + // 调用 `NotesListAdapter` 的 `setCheckedItem` 方法,传入发生选中状态改变的列表项的位置(`position`)以及新的选中状态(`checked`)参数, + // 适配器内部会根据这些信息更新其内部记录的选中项相关的数据结构,以便准确统计选中的数量、判断是否全选等情况。 + // 此方法实现了 `OnMenuItemClickListener` 接口,用于处理菜单项点击事件,当用户点击了设置了此点击监听器的菜单项时会被调用, + // 在这里主要用于判断当前是否有选中的笔记项,如果没有选中任何笔记项,显示一个提示 `Toast`,告知用户需要先选择笔记项才能进行操作,并返回 `true` 表示已处理该点击事件, + // 避免事件继续向上传递导致其他不必要的处理逻辑执行。 if (mNotesListAdapter.getSelectedCount() == 0) { Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none), Toast.LENGTH_SHORT).show(); return true; + // 如果有选中的笔记项,这里后续应该添加具体的业务逻辑代码来处理不同菜单项点击对应的操作,目前代码没有完整实现这部分逻辑,需要开发者进一步补充完善。 + // 返回值目前未明确设定,按正常逻辑应该根据具体业务处理情况返回 `true`(表示已处理)或 `false`(表示未处理)相应的点击事件。 } switch (item.getItemId()) { @@ -343,14 +375,21 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt return false; } return true; + // 如果成功处理了对应的菜单项点击事件(即点击的是“删除”或“移动”菜单项并执行了相应的逻辑),则返回 `true`, + // 表示已经处理了该点击操作,通常可以告知调用者或者框架不需要再对该点击事件进行其他额外处理了。 +return true; } } private class NewNoteOnTouchListener implements OnTouchListener { + // 重写 `OnTouchListener` 接口中的 `onTouch` 方法,当触摸事件发生在绑定了此监听器的视图(这里是 `mAddNewNote` 按钮对应的视图 `v`)上时,该方法会被调用, + // 并传入触摸事件相关的信息(视图 `v` 和 `MotionEvent` 对象 `event`),根据触摸事件的不同类型(如按下、移动、抬起等)来执行相应的逻辑处理,返回值表示是否消费了该触摸事件(`true` 表示消费,`false` 表示未消费,事件可能会继续传递)。 public boolean onTouch(View v, MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { + // 获取当前设备屏幕的默认显示对象,通过调用 `getWindowManager().getDefaultDisplay()` 方法, + // 用于后续获取屏幕相关的尺寸信息等操作,例如屏幕的高度、宽度等,不同设备的屏幕显示属性可能不同,这里获取默认的显示配置。 Display display = getWindowManager().getDefaultDisplay(); int screenHeight = display.getHeight(); int newNoteViewHeight = mAddNewNote.getHeight(); @@ -404,6 +443,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } return false; + // 如果触摸事件没有在上述任何一个 `switch` 分支中被消费(即没有返回 `true`),则返回 `false`,表示当前监听器没有处理该触摸事件, + // 触摸事件可能会继续向上传递给其他的触摸监听器或者父视图等进行处理,具体行为取决于整个视图层级的触摸事件传递机制。 } }; @@ -416,7 +457,20 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt String.valueOf(mCurrentFolderId) }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); } - + // `FOLDER_NOTE_LIST_QUERY_TOKEN`:是一个自定义的查询令牌(Token),用于标识此次查询的类型,方便在查询完成后根据这个令牌来区分不同的查询结果并进行相应处理, + // 例如在 `onQueryComplete` 方法中会通过这个令牌判断是哪个查询任务完成了。 + // `null`:是一个可以传递给查询处理器的额外对象(通常称为Cookie),在这里没有使用,所以传入 `null`,如果有需要可以传入一个自定义的对象, + // 在查询完成后的回调中可以获取到这个对象进行一些额外的相关操作(具体取决于业务逻辑需求)。 + // `Notes.CONTENT_NOTE_URI`:应该是一个定义好的表示笔记内容的内容提供者(Content Provider)的统一资源标识符(URI), + // 指明了要从哪里查询笔记数据,也就是告诉系统要去哪个数据源(通常是数据库对应的内容提供者)查询相应的笔记信息。 + // `NoteItemData.PROJECTION`:是一个字符串数组,定义了查询结果中要返回的列名,也就是指定了从数据库中查询笔记数据时,具体要获取哪些字段的信息, + // 例如可能包含笔记的标题、内容、创建时间等字段,只获取需要的字段可以提高查询效率,减少不必要的数据传输和处理。 + // `selection`:就是前面根据当前文件夹情况确定的查询筛选条件字符串,用于在数据库中筛选出符合条件的笔记记录,比如筛选出某个文件夹下的笔记等。 + // `new String[] { String.valueOf(mCurrentFolderId) }`:是一个字符串数组,用于给查询筛选条件中的占位符(通常在 `selection` 字符串中用 `?` 表示)赋值, + // 在这里将当前文件夹的ID转换为字符串后放入数组中,传递给查询操作,使得筛选条件能够准确地基于当前文件夹ID进行筛选,例如筛选出父ID等于当前文件夹ID的笔记。 + // `NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"`:是一个排序规则字符串,指定了查询结果按照笔记的类型(`NoteColumns.TYPE`)降序排列, + // 并且在类型相同的情况下,按照修改日期(`NoteColumns.MODIFIED_DATE`)降序排列,这样查询出来的笔记列表数据在展示时会按照特定的顺序呈现给用户, + // 比如先展示某种特定类型的最新修改的笔记等情况。 private final class BackgroundQueryHandler extends AsyncQueryHandler { public BackgroundQueryHandler(ContentResolver contentResolver) { super(contentResolver); @@ -460,6 +514,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } }); builder.show(); + // 最后调用 `builder` 对象的 `show` 方法,将构建好的包含文件夹列表的对话框显示在界面上,让用户可以进行文件夹选择操作, + // 根据用户的选择执行相应的业务逻辑,实现将笔记移动到所选文件夹等功能。 } private void createNewNote() { @@ -555,6 +611,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt mTitleBar.setText(data.getSnippet()); } mTitleBar.setVisibility(View.VISIBLE); + // 创建一个意图(Intent)对象,用于启动一个新的Activity,指定要启动的Activity类为 `NoteEditActivity.class`, + // 意味着点击相应按钮或者触发相关操作后,系统将启动 `NoteEditActivity` 这个Activity,让用户可以在其中查看和编辑指定的笔记。 } public void onClick(View v) { @@ -563,9 +621,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt createNewNote(); break; default: + // 向意图中添加额外的数据信息,通过 `putExtra` 方法,将笔记的唯一标识符(`data.getId()`,这里的 `data` 应该是包含笔记相关信息的对象,通过其 `getId` 方法获取笔记ID)添加进去,键为 `Intent.EXTRA_UID`, + // 这样在 `NoteEditActivity` 中可以获取到这个参数,根据笔记ID从数据库等数据源查询并加载相应的笔记数据,进行展示和编辑操作。 break; } } + // 使用当前Activity(`this`)启动刚才创建的意图对应的Activity,并传入请求码 `REQUEST_CODE_OPEN_NODE`, + // 请求码用于在 `onActivityResult` 方法中区分不同的启动Activity返回的结果,这里特定的请求码(`REQUEST_CODE_OPEN_NODE`)表示是打开已有笔记这个操作返回的结果, + // 启动后,用户将进入 `NoteEditActivity` 查看和编辑指定的笔记,完成后会返回到当前Activity,可在 `onActivityResult` 方法中处理返回的相关数据和结果。 private void showSoftInput() { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); @@ -578,11 +641,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); } - +// 此方法用于显示一个创建或修改文件夹的对话框,根据传入的参数 `create` 判断是创建还是修改操作,在对话框中进行相应的界面设置、事件监听以及数据处理逻辑, +// 比如设置标题、按钮文本、文本框初始内容等,并且处理用户点击确定和取消按钮的操作,包括验证文件夹名称的合法性、执行插入或更新文件夹数据等操作。 private void showCreateOrModifyFolderDialog(final boolean create) { final AlertDialog.Builder builder = new AlertDialog.Builder(this); View view = LayoutInflater.from(this).inflate(R.layout.dialog_edit_text, null); final EditText etName = (EditText) view.findViewById(R.id.et_foler_name); + // 此方法用于显示一个创建或修改文件夹的对话框,根据传入的参数 `create` 判断是创建还是修改操作,在对话框中进行相应的界面设置、事件监听以及数据处理逻辑, +// 比如设置标题、按钮文本、文本框初始内容等,并且处理用户点击确定和取消按钮的操作,包括验证文件夹名称的合法性、执行插入或更新文件夹数据等操作。 showSoftInput(); if (!create) { if (mFocusNoteDataItem != null) { @@ -596,7 +662,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt etName.setText(""); builder.setTitle(this.getString(R.string.menu_create_folder)); } - +// 此方法用于显示一个创建或修改文件夹的对话框,根据传入的参数 `create` 判断是创建还是修改操作,在对话框中进行相应的界面设置、事件监听以及数据处理逻辑, +// 比如设置标题、按钮文本、文本框初始内容等,并且处理用户点击确定和取消按钮的操作,包括验证文件夹名称的合法性、执行插入或更新文件夹数据等操作。 builder.setPositiveButton(android.R.string.ok, null); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { @@ -608,7 +675,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt final Button positive = (Button)dialog.findViewById(android.R.id.button1); positive.setOnClickListener(new OnClickListener() { public void onClick(View v) { - hideSoftInput(etName); + hideSoftInput(etName);// 此方法用于显示一个创建或修改文件夹的对话框,根据传入的参数 `create` 判断是创建还是修改操作,在对话框中进行相应的界面设置、事件监听以及数据处理逻辑, +// 比如设置标题、按钮文本、文本框初始内容等,并且处理用户点击确定和取消按钮的操作,包括验证文件夹名称的合法性、执行插入或更新文件夹数据等操作。 String name = etName.getText().toString(); if (DataUtils.checkVisibleFolderName(mContentResolver, name)) { Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name), @@ -639,6 +707,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt if (TextUtils.isEmpty(etName.getText())) { positive.setEnabled(false); + // 获取系统的输入方法管理器(InputMethodManager)实例,通过调用 `getSystemService` 方法传入 `Context.INPUT_METHOD_SERVICE` 作为服务类型标识来获取, + // 该管理器用于控制软键盘的显示、隐藏等相关操作,是与系统输入方法交互的重要组件。 } /** * When the name edit text is null, disable the positive button @@ -648,7 +718,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt // TODO Auto-generated method stub } - +// 调用输入方法管理器的 `hideSoftInputFromWindow` 方法来隐藏软键盘,传入参数 `view.getWindowToken()`,它代表与指定视图(`view`)关联的窗口令牌, + // 用于标识要操作的软键盘所属的窗口,第二个参数 `0` 表示隐藏软键盘的相关选项(这里 `0` 通常表示默认的隐藏方式,不进行额外的特殊处理)。 public void onTextChanged(CharSequence s, int start, int before, int count) { if (TextUtils.isEmpty(etName.getText())) { positive.setEnabled(false); @@ -662,7 +733,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } }); - } + }// 调用输入方法管理器的 `hideSoftInputFromWindow` 方法来隐藏软键盘,传入参数 `view.getWindowToken()`,它代表与指定视图(`view`)关联的窗口令牌, + // 用于标识要操作的软键盘所属的窗口,第二个参数 `0` 表示隐藏软键盘的相关选项(这里 `0` 通常表示默认的隐藏方式,不进行额外的特殊处理)。 @Override public void onBackPressed() { @@ -686,7 +758,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt default: break; } - } + }// 调用输入方法管理器的 `hideSoftInputFromWindow` 方法来隐藏软键盘,传入参数 `view.getWindowToken()`,它代表与指定视图(`view`)关联的窗口令牌, + // 用于标识要操作的软键盘所属的窗口,第二个参数 `0` 表示隐藏软键盘的相关选项(这里 `0` 通常表示默认的隐藏方式,不进行额外的特殊处理)。 private void updateWidget(int appWidgetId, int appWidgetType) { Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); @@ -706,7 +779,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt sendBroadcast(intent); setResult(RESULT_OK, intent); } - +// 调用输入方法管理器的 `hideSoftInputFromWindow` 方法来隐藏软键盘,传入参数 `view.getWindowToken()`,它代表与指定视图(`view`)关联的窗口令牌, + // 用于标识要操作的软键盘所属的窗口,第二个参数 `0` 表示隐藏软键盘的相关选项(这里 `0` 通常表示默认的隐藏方式,不进行额外的特殊处理)。 private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() { public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { if (mFocusNoteDataItem != null) { @@ -715,7 +789,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt menu.add(0, MENU_FOLDER_DELETE, 0, R.string.menu_folder_delete); menu.add(0, MENU_FOLDER_CHANGE_NAME, 0, R.string.menu_folder_change_name); } - } + }// 获取系统的输入方法管理器(InputMethodManager)实例,通过调用 `getSystemService` 方法传入 `Context.INPUT_METHOD_SERVICE` 作为服务类型标识来获取, + // 该管理器用于控制软键盘的显示、隐藏等相关操作,是与系统输入方法交互的重要组件。 }; @Override @@ -758,7 +833,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } return true; - } + }// 获取系统的输入方法管理器(InputMethodManager)实例,通过调用 `getSystemService` 方法传入 `Context.INPUT_METHOD_SERVICE` 作为服务类型标识来获取, + // 该管理器用于控制软键盘的显示、隐藏等相关操作,是与系统输入方法交互的重要组件。 @Override public boolean onPrepareOptionsMenu(Menu menu) { @@ -777,7 +853,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } return true; } - +// 调用输入方法管理器的 `hideSoftInputFromWindow` 方法来隐藏软键盘,传入参数 `view.getWindowToken()`,它代表与指定视图(`view`)关联的窗口令牌, + // 用于标识要操作的软键盘所属的窗口,第二个参数 `0` 表示隐藏软键盘的相关选项(这里 `0` 通常表示默认的隐藏方式,不进行额外的特殊处理)。 @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { @@ -817,13 +894,15 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } return true; } - +// 获取系统的输入方法管理器(InputMethodManager)实例,通过调用 `getSystemService` 方法传入 `Context.INPUT_METHOD_SERVICE` 作为服务类型标识来获取, + // 该管理器用于控制软键盘的显示、隐藏等相关操作,是与系统输入方法交互的重要组件。 @Override public boolean onSearchRequested() { startSearch(null, false, null /* appData */, false); return true; } - +// 调用输入方法管理器的 `hideSoftInputFromWindow` 方法来隐藏软键盘,传入参数 `view.getWindowToken()`,它代表与指定视图(`view`)关联的窗口令牌, + // 用于标识要操作的软键盘所属的窗口,第二个参数 `0` 表示隐藏软键盘的相关选项(这里 `0` 通常表示默认的隐藏方式,不进行额外的特殊处理)。 private void exportNoteToText() { final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this); new AsyncTask() { @@ -875,7 +954,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt Intent intent = new Intent(from, NotesPreferenceActivity.class); from.startActivityIfNeeded(intent, -1); } - +// 获取系统的输入方法管理器(InputMethodManager)实例,通过调用 `getSystemService` 方法传入 `Context.INPUT_METHOD_SERVICE` 作为服务类型标识来获取, + // 该管理器用于控制软键盘的显示、隐藏等相关操作,是与系统输入方法交互的重要组件。 private class OnListItemClickListener implements OnItemClickListener { public void onItemClick(AdapterView parent, View view, int position, long id) { @@ -916,7 +996,8 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } - +// 获取系统的输入方法管理器(InputMethodManager)实例,通过调用 `getSystemService` 方法传入 `Context.INPUT_METHOD_SERVICE` 作为服务类型标识来获取, + // 该管理器用于控制软键盘的显示、隐藏等相关操作,是与系统输入方法交互的重要组件。 private void startQueryDestinationFolders() { String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?"; selection = (mState == ListEditState.NOTE_LIST) ? selection: diff --git a/src/Notes-master/src/net/micode/notes/ui/NotesListAdapter.java b/src/Notes-master/src/net/micode/notes/ui/NotesListAdapter.java index 51c9cb9..5f78d6d 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesListAdapter.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesListAdapter.java @@ -32,56 +32,82 @@ import java.util.Iterator; public class NotesListAdapter extends CursorAdapter { + // 用于日志输出的标记,方便在调试等情况下识别该类相关的日志信息 private static final String TAG = "NotesListAdapter"; + // 上下文对象,用于获取系统相关资源等操作 private Context mContext; + // 用于记录每个位置(对应数据项)是否被选中的映射表,键为位置索引(整数),值为是否选中(布尔值) private HashMap mSelectedIndex; + // 记录笔记的数量 private int mNotesCount; + // 表示是否处于选择模式(例如多选模式等情况) + private boolean mChoiceMode; + // 内部类,用于封装与应用小部件相关的属性(如小部件的ID和类型) public static class AppWidgetAttribute { public int widgetId; public int widgetType; }; + // 构造函数,接收一个上下文对象,初始化一些成员变量 public NotesListAdapter(Context context) { + // 调用父类(CursorAdapter)的构造函数,传入上下文和初始化为null的游标 super(context, null); + // 创建一个新的HashMap用于存储选中状态信息 mSelectedIndex = new HashMap(); mContext = context; mNotesCount = 0; } + // 创建一个新的视图(View)对象,用于显示列表中的一项数据 @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { + // 创建并返回一个NotesListItem类型的视图,该视图应该是用于展示笔记列表项的自定义视图 return new NotesListItem(context); } + // 将数据绑定到给定的视图上,用于显示具体的数据内容 @Override public void bindView(View view, Context context, Cursor cursor) { + // 判断视图是否是NotesListItem类型的实例 if (view instanceof NotesListItem) { + // 根据给定的上下文和游标创建一个NoteItemData对象,用于获取笔记相关的数据 NoteItemData itemData = new NoteItemData(context, cursor); + // 调用NotesListItem的bind方法,将相关数据、选择模式状态以及该项是否被选中的信息传递进去,进行数据绑定显示等操作 ((NotesListItem) view).bind(context, itemData, mChoiceMode, isSelectedItem(cursor.getPosition())); } } + // 设置指定位置的项的选中状态 public void setCheckedItem(final int position, final boolean checked) { + // 将指定位置的选中状态存入mSelectedIndex映射表中 mSelectedIndex.put(position, checked); + // 通知数据集已发生改变,以便相关的UI组件(如ListView等)能相应地更新显示 notifyDataSetChanged(); } + // 判断是否处于选择模式 public boolean isInChoiceMode() { return mChoiceMode; } + // 设置选择模式,同时清空之前的选中状态记录 public void setChoiceMode(boolean mode) { mSelectedIndex.clear(); mChoiceMode = mode; } - + + // 全选或全不选所有符合条件(笔记类型为Notes.TYPE_NOTE)的项 public void selectAll(boolean checked) { + // 获取当前游标对象,用于遍历数据 Cursor cursor = getCursor(); + // 遍历数据集的每一项 for (int i = 0; i < getCount(); i++) { + // 将游标移动到指定位置 if (cursor.moveToPosition(i)) { + // 判断该项对应的笔记类型是否为Notes.TYPE_NOTE,如果是,则设置其选中状态 if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) { setCheckedItem(i, checked); } @@ -89,14 +115,21 @@ public class NotesListAdapter extends CursorAdapter { } } + // 获取所有已选中项的ID,存储在一个HashSet中返回 public HashSet getSelectedItemIds() { + // 创建一个用于存储已选中项ID的HashSet对象 HashSet itemSet = new HashSet(); + // 遍历mSelectedIndex中记录的所有位置(键) for (Integer position : mSelectedIndex.keySet()) { + // 如果该位置对应的项是被选中的 if (mSelectedIndex.get(position) == true) { + // 获取该项对应的ID Long id = getItemId(position); + // 如果ID是Notes.ID_ROOT_FOLDER(可能是一个特殊的、不符合预期的ID),则在日志中记录错误信息(通常表示不应该出现这种情况) if (id == Notes.ID_ROOT_FOLDER) { Log.d(TAG, "Wrong item id, should not happen"); } else { + // 将符合条件的ID添加到HashSet中 itemSet.add(id); } } @@ -105,21 +138,32 @@ public class NotesListAdapter extends CursorAdapter { return itemSet; } + // 获取所有已选中的应用小部件相关属性信息,存储在一个HashSet中返回 public HashSet getSelectedWidget() { + // 创建一个用于存储已选中小部件属性的HashSet对象 HashSet itemSet = new HashSet(); + // 遍历mSelectedIndex中记录的所有位置(键) for (Integer position : mSelectedIndex.keySet()) { + // 如果该位置对应的项是被选中的 if (mSelectedIndex.get(position) == true) { + // 获取对应位置的数据项(游标形式) Cursor c = (Cursor) getItem(position); if (c != null) { + // 创建一个AppWidgetAttribute对象,用于存储小部件相关属性 AppWidgetAttribute widget = new AppWidgetAttribute(); + // 根据游标创建一个NoteItemData对象,用于获取小部件相关的数据 NoteItemData item = new NoteItemData(mContext, c); + // 设置小部件的ID属性 widget.widgetId = item.getWidgetId(); + // 设置小部件的类型属性 widget.widgetType = item.getWidgetType(); + // 将封装好的小部件属性对象添加到HashSet中 itemSet.add(widget); /** - * Don't close cursor here, only the adapter could close it + * 此处不要关闭游标,只有适配器本身才能关闭它,避免外部错误关闭游标导致问题 */ } else { + // 如果游标为空,记录错误日志信息,表示游标无效 Log.e(TAG, "Invalid cursor"); return null; } @@ -128,13 +172,18 @@ public class NotesListAdapter extends CursorAdapter { return itemSet; } + // 获取已选中项的数量 public int getSelectedCount() { + // 获取mSelectedIndex中所有值(即每个位置对应的选中状态布尔值)的集合 Collection values = mSelectedIndex.values(); + // 如果集合为空(可能没有任何选中记录等情况),返回0 if (null == values) { return 0; } + // 获取集合的迭代器,用于遍历集合中的元素(布尔值) Iterator iter = values.iterator(); int count = 0; + // 遍历集合中的每个布尔值元素,如果为true,表示该项被选中,数量加1 while (iter.hasNext()) { if (true == iter.next()) { count++; @@ -143,30 +192,39 @@ public class NotesListAdapter extends CursorAdapter { return count; } + // 判断是否所有项都被选中 public boolean isAllSelected() { + // 获取已选中项的数量 int checkedCount = getSelectedCount(); + // 如果选中数量不为0(有选中项)且选中数量等于笔记的总数量,说明所有项都被选中了,返回true,否则返回false return (checkedCount != 0 && checkedCount == mNotesCount); } + // 判断指定位置的项是否被选中 public boolean isSelectedItem(final int position) { + // 如果该位置对应的选中状态记录为null(可能还未设置等情况),则返回false,表示未选中 if (null == mSelectedIndex.get(position)) { return false; } + // 返回该位置对应的实际选中状态(布尔值) return mSelectedIndex.get(position); } + // 当内容发生改变时调用的方法,这里调用了calcNotesCount方法来重新计算笔记数量 @Override protected void onContentChanged() { super.onContentChanged(); calcNotesCount(); } + // 当游标发生改变时调用的方法,同样调用calcNotesCount方法来重新计算笔记数量 @Override public void changeCursor(Cursor cursor) { super.changeCursor(cursor); calcNotesCount(); } + // 计算笔记的数量,遍历数据集,统计符合条件(笔记类型为Notes.TYPE_NOTE)的项的数量 private void calcNotesCount() { mNotesCount = 0; for (int i = 0; i < getCount(); i++) { diff --git a/src/Notes-master/src/net/micode/notes/ui/NotesListItem.java b/src/Notes-master/src/net/micode/notes/ui/NotesListItem.java index 1221e80..7b9df30 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesListItem.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesListItem.java @@ -37,85 +37,133 @@ public class NotesListItem extends LinearLayout { private TextView mCallName; private NoteItemData mItemData; private CheckBox mCheckBox; - + // NotesListItem类的构造函数,用于初始化该列表项相关的视图组件 public NotesListItem(Context context) { + // 调用父类(应该是某个视图类的构造函数,传递上下文对象进行初始化) super(context); + // 从给定的布局资源(R.layout.note_item)中加载视图层次结构,并将其填充到当前的视图(this)中 inflate(context, R.layout.note_item, this); + // 通过ID查找并获取布局中的ImageView组件,用于显示提醒图标等,ID为R.id.iv_alert_icon mAlert = (ImageView) findViewById(R.id.iv_alert_icon); + // 通过ID查找并获取布局中的TextView组件,用于显示标题内容,ID为R.id.tv_title mTitle = (TextView) findViewById(R.id.tv_title); + // 通过ID查找并获取布局中的TextView组件,用于显示时间相关信息,ID为R.id.tv_time mTime = (TextView) findViewById(R.id.tv_time); + // 通过ID查找并获取布局中的TextView组件,用于显示呼叫名称相关信息,ID为R.id.tv_name mCallName = (TextView) findViewById(R.id.tv_name); + // 通过安卓系统内置的ID(android.R.id.checkbox)查找并获取布局中的CheckBox组件,用于表示该项是否被选中 mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); } + // 用于将数据绑定到该列表项视图上,根据不同的数据情况设置视图的显示内容和状态 public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) { + // 如果处于选择模式(choiceMode为true)并且数据对应的类型是笔记类型(Notes.TYPE_NOTE) if (choiceMode && data.getType() == Notes.TYPE_NOTE) { + // 设置复选框(CheckBox)可见,用于在选择模式下让用户选择该项 mCheckBox.setVisibility(View.VISIBLE); + // 根据传入的checked参数设置复选框的选中状态 mCheckBox.setChecked(checked); } else { + // 如果不满足上述条件,隐藏复选框,表示不在选择模式或者不是笔记类型的项不需要显示复选框 mCheckBox.setVisibility(View.GONE); } + // 将传入的数据对象保存到成员变量mItemData中,方便后续获取和使用 mItemData = data; + // 如果数据的ID等于Notes.ID_CALL_RECORD_FOLDER(可能是特定的文件夹类型的标识) if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { + // 隐藏呼叫名称相关的TextView,因为可能不需要显示 mCallName.setVisibility(View.GONE); + // 显示提醒图标相关的ImageView,可能用于提示特殊情况 mAlert.setVisibility(View.VISIBLE); + // 设置标题的文本外观样式,采用R.style.TextAppearancePrimaryItem样式 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())); + // 设置提醒图标对应的图片资源,这里使用R.drawable.call_record图片资源 mAlert.setImageResource(R.drawable.call_record); } else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { + // 如果数据的父ID等于Notes.ID_CALL_RECORD_FOLDER,表示该项属于通话记录文件夹下的子项 + // 显示呼叫名称相关的TextView,并设置其文本内容为从数据对象中获取的呼叫名称 mCallName.setVisibility(View.VISIBLE); mCallName.setText(data.getCallName()); + // 设置标题的文本外观样式,采用R.style.TextAppearanceSecondaryItem样式 mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); + // 设置标题的文本内容,通过DataUtils工具类对数据中的摘要(snippet)进行格式化后设置 mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); + // 如果数据有提醒(通过hasAlert方法判断),则设置提醒图标相关的ImageView可见,并设置对应的图片资源为R.drawable.clock if (data.hasAlert()) { mAlert.setImageResource(R.drawable.clock); mAlert.setVisibility(View.VISIBLE); } else { + // 如果没有提醒,则隐藏提醒图标 mAlert.setVisibility(View.GONE); } } else { + // 如果不属于上述两种情况(既不是通话记录文件夹本身,也不是其直接子项) + // 隐藏呼叫名称相关的TextView,因为不需要显示 mCallName.setVisibility(View.GONE); + // 设置标题的文本外观样式,采用R.style.TextAppearancePrimaryItem样式 mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); + // 如果数据的类型是文件夹类型(Notes.TYPE_FOLDER) if (data.getType() == Notes.TYPE_FOLDER) { + // 设置标题的文本内容,由数据中的摘要(snippet)和格式化后的包含文件数量的字符串拼接而成,文件数量从数据对象中获取 mTitle.setText(data.getSnippet() + context.getString(R.string.format_folder_files_count, data.getNotesCount())); + // 隐藏提醒图标相关的ImageView,文件夹类型可能不需要显示提醒图标 mAlert.setVisibility(View.GONE); } else { + // 如果是笔记类型(非文件夹类型) + // 设置标题的文本内容,通过DataUtils工具类对数据中的摘要(snippet)进行格式化后设置 mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); + // 如果数据有提醒(通过hasAlert方法判断),则设置提醒图标相关的ImageView可见,并设置对应的图片资源为R.drawable.clock if (data.hasAlert()) { mAlert.setImageResource(R.drawable.clock); mAlert.setVisibility(View.VISIBLE); } else { + // 如果没有提醒,则隐藏提醒图标 mAlert.setVisibility(View.GONE); } } } + // 设置时间相关的TextView的文本内容,通过DateUtils工具类根据数据中的修改日期获取相对时间格式的字符串进行设置 mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); + // 调用setBackground方法,根据数据情况设置该项的背景样式 setBackground(data); } + // 根据传入的NoteItemData数据对象设置该项的背景资源,根据不同的条件选择不同的背景资源进行设置 private void setBackground(NoteItemData data) { + // 获取数据中对应的背景颜色ID int id = data.getBgColorId(); + // 如果数据的类型是笔记类型(Notes.TYPE_NOTE) if (data.getType() == Notes.TYPE_NOTE) { + // 如果该项是单独的(通过isSingle方法判断)或者只有一个后续文件夹(通过isOneFollowingFolder方法判断) + if (data.isSingle() || data.isOneFollowingFolder()) { + // 设置该项的背景资源,通过NoteItemBgResources工具类根据背景颜色ID获取对应的单个笔记背景资源进行设置 if (data.isSingle() || data.isOneFollowingFolder()) { setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id)); } else if (data.isLast()) { + // 如果该项是最后一个(通过isLast方法判断),则设置该项的背景资源,通过NoteItemBgResources工具类根据背景颜色ID获取对应的最后一个笔记背景资源进行设置 setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id)); } else if (data.isFirst() || data.isMultiFollowingFolder()) { + // 如果该项是第一个(通过isFirst方法判断)或者有多个后续文件夹(通过isMultiFollowingFolder方法判断),则设置该项的背景资源,通过NoteItemBgResources工具类根据背景颜色ID获取对应的第一个笔记背景资源进行设置 setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id)); } else { + // 如果不符合上述任何特殊情况,则设置该项的背景资源,通过NoteItemBgResources工具类根据背景颜色ID获取对应的普通笔记背景资源进行设置 setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id)); } + // 如果数据的类型不是笔记类型(即文件夹类型等情况),则设置该项的背景资源,通过NoteItemBgResources工具类获取对应的文件夹背景资源进行设置 } else { setBackgroundResource(NoteItemBgResources.getFolderBgRes()); } } + // 获取当前列表项绑定的数据对象,用于在外部获取该列表项对应的数据信息 public NoteItemData getItemData() { return mItemData; } diff --git a/src/Notes-master/src/net/micode/notes/ui/NotesPreferenceActivity.java b/src/Notes-master/src/net/micode/notes/ui/NotesPreferenceActivity.java index 07c5f7e..4f98018 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesPreferenceActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesPreferenceActivity.java @@ -47,64 +47,86 @@ import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; import net.micode.notes.gtask.remote.GTaskSyncService; - +// NotesPreferenceActivity类继承自PreferenceActivity,用于管理应用中与偏好设置相关的功能和界面展示 public class NotesPreferenceActivity extends PreferenceActivity { - public static final String PREFERENCE_NAME = "notes_preferences"; + // 定义偏好设置文件的名称,用于存储和获取应用相关的偏好设置数据 + 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; + // 在Activity创建时调用的方法,进行一些初始化操作 @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); + // 设置ActionBar上的返回按钮可用,使用应用图标来实现导航返回功能(通常是返回上级界面) /* using the app icon for navigation */ getActionBar().setDisplayHomeAsUpEnabled(true); + // 从指定的XML资源文件(R.xml.preferences)加载偏好设置界面的布局和相关设置项 addPreferencesFromResource(R.xml.preferences); + // 通过键(PREFERENCE_SYNC_ACCOUNT_KEY)查找并获取对应的PreferenceCategory组件,用于后续操作账户相关的偏好设置项 mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); + // 创建一个GTaskReceiver实例,用于接收同步相关广播 mReceiver = new GTaskReceiver(); + // 创建一个IntentFilter,用于定义要接收的广播动作 IntentFilter filter = new IntentFilter(); + // 添加要接收的广播动作,这里接收GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME对应的广播 + filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); + // 注册广播接收器,使其能够接收符合条件的广播消息 filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); registerReceiver(mReceiver, filter); + // 初始化为null,后续会根据实际情况获取账户信息进行赋值 mOriAccounts = null; + // 通过LayoutInflater加载一个自定义的头部视图(R.layout.settings_header),并添加到当前Activity的ListView中作为头部视 View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); getListView().addHeaderView(header, null, true); } + // 在Activity重新恢复可见时调用的方法(例如从其他Activity返回等情况),进行一些更新操作 @Override protected void onResume() { super.onResume(); + // 如果添加了新账户(mHasAddedAccount为true),则进行以下操作 // need to set sync account automatically if user has added a new // account if (mHasAddedAccount) { + // 获取当前的谷歌账户列表 Account[] accounts = getGoogleAccounts(); + // 如果原始账户列表不为null且当前账户数量大于原始账户数量,说明可能添加了新账户 if (mOriAccounts != null && accounts.length > mOriAccounts.length) { + // 遍历新的账户列表 for (Account accountNew : accounts) { + // 标记是否找到匹配的旧账户,初始化为false boolean found = false; + // 遍历原始账户列表,对比账户名称,查找是否已存在相同名称的账户 for (Account accountOld : mOriAccounts) { if (TextUtils.equals(accountOld.name, accountNew.name)) { found = true; break; } } + // 如果没有找到匹配的旧账户,说明是新添加的账户,则设置为同步账户,并跳出循环 if (!found) { setSyncAccount(accountNew.name); break; @@ -113,36 +135,50 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + // 调用refreshUI方法,更新界面显示(加载账户偏好设置项、同步按钮等相关UI元素) refreshUI(); } + // 在Activity销毁时调用的方法,进行一些清理操作,如注销广播接收器 @Override protected void onDestroy() { if (mReceiver != null) { + // 注销广播接收器,避免内存泄漏等问题 unregisterReceiver(mReceiver); } super.onDestroy(); } + // 加载账户偏好设置相关的视图组件和逻辑,例如添加账户相关的偏好设置项到界面上 private void loadAccountPreference() { + // 移除账户分类下的所有已有偏好设置项(可能是为了重新加载最新的设置项等情况) mAccountCategory.removeAll(); + // 创建一个新的Preference对象,用于表示账户相关的偏好设置项 Preference accountPref = new Preference(this); + // 获取当前设置的同步账户名称(如果有的话),作为默认账户名称 final String defaultAccount = getSyncAccountName(this); + // 设置偏好设置项的标题,从字符串资源(R.string.preferences_account_title)中获取 accountPref.setTitle(getString(R.string.preferences_account_title)); + // 设置偏好设置项的摘要内容,从字符串资源(R.string.preferences_account_summary)中获取 accountPref.setSummary(getString(R.string.preferences_account_summary)); + // 设置偏好设置项的点击监听器,当用户点击该设置项时执行以下逻辑 accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { public boolean onPreferenceClick(Preference preference) { + // 如果同步服务没有正在同步(通过GTaskSyncService.isSyncing()判断) 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 { + // 如果正在同步,弹出提示Toast,告知用户不能更改账户 Toast.makeText(NotesPreferenceActivity.this, R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT) .show(); @@ -151,85 +187,121 @@ public class NotesPreferenceActivity extends PreferenceActivity { } }); + // 将创建好的账户偏好设置项添加到账户分类(mAccountCategory)中,以便在界面上显示 mAccountCategory.addPreference(accountPref); } + + // 加载同步按钮相关的逻辑和状态设置,包括按钮文本、点击事件以及同步状态显示等 private void loadSyncButton() { + // 通过ID查找并获取同步按钮(Button)组件,ID为R.id.preference_sync_button Button syncButton = (Button) findViewById(R.id.preference_sync_button); + // 通过ID查找并获取显示上次同步时间的TextView组件,ID为R.id.prefenerece_sync_status_textview 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)); + // 设置按钮的点击事件,点击时调用GTaskSyncService.cancelSync方法取消同步 syncButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { GTaskSyncService.cancelSync(NotesPreferenceActivity.this); } }); } else { + // 如果没有正在同步,设置按钮文本为立即同步相关的文本(从字符串资源中获取) syncButton.setText(getString(R.string.preferences_button_sync_immediately)); + // 设置按钮的点击事件,点击时调用GTaskSyncService.startSync方法启动同步 syncButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { GTaskSyncService.startSync(NotesPreferenceActivity.this); } }); } + // 根据是否设置了同步账户来启用或禁用同步按钮,如果同步账户名称为空则禁用按钮 syncButton.setEnabled(!TextUtils.isEmpty(getSyncAccountName(this))); + // 根据同步服务状态设置上次同步时间的显示内容和可见性 // set last sync time if (GTaskSyncService.isSyncing()) { + // 如果正在同步,设置显示上次同步时间的TextView的文本为同步进度相关的字符串(从同步服务获取),并设置为可见 lastSyncTimeView.setText(GTaskSyncService.getProgressString()); lastSyncTimeView.setVisibility(View.VISIBLE); } else { + // 如果没有正在同步,获取上次同步时间(通过getLastSyncTime方法) long lastSyncTime = getLastSyncTime(this); if (lastSyncTime != 0) { + // 如果上次同步时间不为0,格式化并设置显示上次同步时间的TextView的文本内容,然后设置为可见 lastSyncTimeView.setText(getString(R.string.preferences_last_sync_time, DateFormat.format(getString(R.string.preferences_last_sync_time_format), lastSyncTime))); lastSyncTimeView.setVisibility(View.VISIBLE); } else { + // 如果上次同步时间为0,隐藏该TextView组件 lastSyncTimeView.setVisibility(View.GONE); } } } + // 用于更新整个界面的显示,调用加载账户偏好设置和同步按钮相关的方法 private void refreshUI() { loadAccountPreference(); loadSyncButton(); } + // 弹出选择账户的提示对话框,用于让用户选择要设置的同步账户 private void showSelectAccountAlertDialog() { + // 创建一个AlertDialog.Builder对象,用于构建提示对话框 AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + // 通过LayoutInflater加载一个自定义的标题视图(R.layout.account_dialog_title),用于设置对话框的标题部分 View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); + // 通过ID查找并获取标题TextView组件,设置其文本内容为选择账户相关的标题文本(从字符串资源中获取) TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); titleTextView.setText(getString(R.string.preferences_dialog_select_account_title)); + // 通过ID查找并获取副标题TextView组件,设置其文本内容为选择账户相关的提示文本(从字符串资源中获取) TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); subtitleTextView.setText(getString(R.string.preferences_dialog_select_account_tips)); + // 设置对话框的自定义标题视图 dialogBuilder.setCustomTitle(titleView); + // 设置对话框的确定按钮(这里先设置为null,后续可能根据具体需求再处理) dialogBuilder.setPositiveButton(null, null); + // 获取当前的谷歌账户列表 Account[] accounts = getGoogleAccounts(); + // 获取当前设置的同步账户名称(如果有的话) String defAccount = getSyncAccountName(this); + // 将当前账户列表赋值给mOriAccounts,用于后续对比等操作 mOriAccounts = accounts; + // 标记还没有添加新账户 mHasAddedAccount = false; + // 如果账户列表长度大于0,说明有可用账户,进行以下操作 if (accounts.length > 0) { + // 创建一个字符序列数组,用于存储账户名称,长度与账户列表长度相同 CharSequence[] items = new CharSequence[accounts.length]; + // 将items数组赋值给itemMapping,方便后续在点击事件中使用(这里可能是为了保持引用一致等原因) + final CharSequence[] itemMapping = items; + // 标记默认选中的账户索引,初始化为 -1,表示没有默认选中项 final CharSequence[] itemMapping = items; int checkedItem = -1; int index = 0; + // 遍历账户列表,设置每个账户名称到items数组中,并查找默认选中的账户(与当前同步账户名称相同的账户) for (Account account : accounts) { if (TextUtils.equals(account.name, defAccount)) { checkedItem = index; } items[index++] = account.name; } + // 设置对话框的单选列表项,传入账户名称数组、默认选中索引以及点击事件监听器 dialogBuilder.setSingleChoiceItems(items, checkedItem, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { + // 当用户点击某个账户项时,设置选择的账户为同步账户,关闭对话框,并更新界面显示 setSyncAccount(itemMapping[which].toString()); dialog.dismiss(); refreshUI(); @@ -237,10 +309,13 @@ public class NotesPreferenceActivity extends PreferenceActivity { }); } + // 通过LayoutInflater加载一个添加账户的视图(R.layout.add_account_text),并添加到对话框中 View addAccountView = LayoutInflater.from(this).inflate(R.layout.add_account_text, null); dialogBuilder.setView(addAccountView); + // 创建并显示对话框 final AlertDialog dialog = dialogBuilder.show(); + // 设置添加账户视图的点击事件,当用户点击时,标记添加了新账户,启动添加账户的系统设置界面,并关闭当前对话框 addAccountView.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { mHasAddedAccount = true; @@ -254,98 +329,138 @@ public class NotesPreferenceActivity extends PreferenceActivity { }); } + // 弹出确认更改账户的提示对话框,用于提示用户更改账户的相关操作和风险等信息 private void showChangeAccountConfirmAlertDialog() { + // 创建一个AlertDialog.Builder对象,用于构建提示对话框 AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + // 通过LayoutInflater加载一个自定义的标题视图(R.layout.account_dialog_title),用于设置对话框的标题部分 View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); + // 通过ID查找并获取标题TextView组件,设置其文本内容为更改账户相关的标题文本(包含当前同步账户名称,从字符串资源中获取并格式化) TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); titleTextView.setText(getString(R.string.preferences_dialog_change_account_title, getSyncAccountName(this))); + // 通过ID查找并获取副标题TextView组件,设置其文本内容为更改账户相关的警告提示文本(从字符串资源中获取) TextView subtitleTextView = (TextView) titleView.findViewById(R.id.account_dialog_subtitle); subtitleTextView.setText(getString(R.string.preferences_dialog_change_account_warn_msg)); + // 设置对话框的自定义标题视图 dialogBuilder.setCustomTitle(titleView); + // 创建一个字符序列数组,包含更改账户、移除账户、取消等菜单项文本(从字符串资源中获取) CharSequence[] menuItemArray = new CharSequence[] { getString(R.string.preferences_menu_change_account), getString(R.string.preferences_menu_remove_account), getString(R.string.preferences_menu_cancel) }; + // 设置对话框的菜单项和点击事件监听器,根据用户点击的不同菜单项执行相应操作 dialogBuilder.setItems(menuItemArray, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { if (which == 0) { + // 如果点击的是更改账户菜单项,弹出选择账户的提示对话框 showSelectAccountAlertDialog(); } else if (which == 1) { + // 如果点击的是移除账户菜单项,调用removeSyncAccount方法移除同步账户,并更新界面显示 removeSyncAccount(); refreshUI(); } } }); + // 显示对话框 dialogBuilder.show(); } - + // 获取当前设备上的谷歌账户列表,通过AccountManager获取指定类型("com.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); + // 获取偏好设置的编辑器,用于修改偏好设置的值 SharedPreferences.Editor editor = settings.edit(); if (account != null) { + // 如果账户名称不为null,将账户名称保存到偏好设置中(键为PREFERENCE_SYNC_ACCOUNT_NAME) + editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account); editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account); } else { + // 如果传入的账户名称为null,将同步账户名称设置为空字符串,存入偏好设置中 editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); } + // 提交对偏好设置的修改,使其生效 editor.commit(); // clean up last sync time + // 调用setLastSyncTime方法将上次同步时间设置为0,可能是为了在更改同步账户时重置同步相关的时间记录 + setLastSyncTime(this, 0); setLastSyncTime(this, 0); + // 清理本地与GTask相关的信息,通过开启一个新线程来执行更新操作 // clean up local gtask related info new Thread(new Runnable() { public void run() { + // 创建一个ContentValues对象,用于存储要更新的数据 ContentValues values = new ContentValues(); + // 将NoteColumns.GTASK_ID对应的值设置为空字符串,可能是清除之前关联的GTask ID信息 values.put(NoteColumns.GTASK_ID, ""); + // 将NoteColumns.SYNC_ID对应的值设置为0,可能是重置同步相关的ID等信息 values.put(NoteColumns.SYNC_ID, 0); + // 使用内容解析器(getContentResolver)更新Notes.CONTENT_NOTE_URI对应的内容,将上述设置的值进行更新,条件为null(可能是更新所有符合该URI的记录) getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); } }).start(); + // 弹出一个Toast提示信息,告知用户成功设置账户,提示内容通过字符串资源格式化传入账户名称来生成 Toast.makeText(NotesPreferenceActivity.this, getString(R.string.preferences_toast_success_set_accout, account), Toast.LENGTH_SHORT).show(); } } + // 移除同步账户相关的偏好设置信息以及清理本地与GTask相关的信息 private void removeSyncAccount() { - SharedPreferences settings = getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); + SharedPreferences settings = get + // 获取应用的共享偏好设置对象,用于操作偏好设置数据 + SharedPreferences.(PREFERENCE_NAME, Context.MODE_PRIVATE); + // 获取偏好设置的编辑器,用于修改偏好设置中的值 SharedPreferences.Editor editor = settings.edit(); + // 如果偏好设置中包含同步账户名称这个键(说明之前有设置过),则移除该键值对 if (settings.contains(PREFERENCE_SYNC_ACCOUNT_NAME)) { editor.remove(PREFERENCE_SYNC_ACCOUNT_NAME); } + // 如果偏好设置中包含上次同步时间这个键(说明之前有记录),则移除该键值对 if (settings.contains(PREFERENCE_LAST_SYNC_TIME)) { editor.remove(PREFERENCE_LAST_SYNC_TIME); } + // 提交对偏好设置的修改,使其生效 editor.commit(); + // 清理本地与GTask相关的信息,通过开启一个新线程来执行更新操作 // clean up local gtask related info new Thread(new Runnable() { public void run() { + // 创建一个ContentValues对象,用于存储要更新的数据 ContentValues values = new ContentValues(); + // 将NoteColumns.GTASK_ID对应的值设置为空字符串,可能是清除之前关联的GTask ID信息 values.put(NoteColumns.GTASK_ID, ""); + // 将NoteColumns.SYNC_ID对应的值设置为0,可能是重置同步相关的ID等信息 values.put(NoteColumns.SYNC_ID, 0); + // 使用内容解析器(getContentResolver)更新Notes.CONTENT_NOTE_URI对应的内容,将上述设置的值进行更新,条件为null(可能是更新所有符合该URI的记录) getContentResolver().update(Notes.CONTENT_NOTE_URI, values, null, null); } }).start(); } + // 获取当前设置的同步账户名称,从应用的共享偏好设置中读取对应键(PREFERENCE_SYNC_ACCOUNT_NAME)的值,如果不存在则返回空字符串 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,18 +469,24 @@ public class NotesPreferenceActivity extends PreferenceActivity { editor.commit(); } + // 获取上次同步时间,从应用的共享偏好设置中读取对应键(PREFERENCE_LAST_SYNC_TIME)的值,如果不存在则返回0 public static long getLastSyncTime(Context context) { SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE); return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0); } + // 内部类,继承自BroadcastReceiver,用于接收与GTask同步服务相关的广播消息,并进行相应的界面更新操作 private class GTaskReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { + // 接收到广播后,调用refreshUI方法更新界面显示,例如加载账户偏好设置、同步按钮状态等 refreshUI(); + // 判断广播中携带的是否正在同步的额外信息(键为GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING)是否为true,如果是则进行以下操作 + if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) { + // 通过ID查找并获取用于显示同步状态的TextView组件,ID为R.id.prefenerece_sync_status_textview TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview); syncStatus.setText(intent .getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG));