diff --git a/src/Notes-master/src/net/micode/notes/ui/AlarmAlertActivity.java b/src/Notes-master/src/net/micode/notes/ui/AlarmAlertActivity.java index 85723be..b86fe2f 100644 --- a/src/Notes-master/src/net/micode/notes/ui/AlarmAlertActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/AlarmAlertActivity.java @@ -40,20 +40,29 @@ import net.micode.notes.tool.DataUtils; import java.io.IOException; +// AlarmAlertActivity类继承自Activity,实现了OnClickListener和OnDismissListener接口,用于处理闹钟提醒的展示、声音播放以及相关交互逻辑 public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { + // 用于存储提醒关联的笔记的ID private long mNoteId; + // 用于存储笔记内容的摘要信息,最大展示长度有限制 private String mSnippet; + // 定义摘要信息的最大展示长度为60个字符 private static final int SNIPPET_PREW_MAX_LEN = 60; + // 用于播放闹钟提醒声音的MediaPlayer对象 MediaPlayer mPlayer; + // Activity被创建时调用的方法,进行一些初始化操作 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + // 设置Activity无标题栏,去除默认的标题显示 requestWindowFeature(Window.FEATURE_NO_TITLE); final Window win = getWindow(); + // 设置窗口属性,使得在屏幕锁定时也能显示该Activity win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + // 判断屏幕是否处于开启状态,如果屏幕未开启,添加一系列屏幕相关的标志位,以确保屏幕按期望的方式显示和处理 if (!isScreenOn()) { win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON @@ -64,9 +73,12 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD Intent intent = getIntent(); try { + // 从传入的Intent中获取数据,提取出笔记的ID mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); + // 根据笔记ID获取对应的摘要内容 mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); - mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, + // 如果摘要内容长度超过最大展示长度,进行截断处理,并添加提示信息 + mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN? mSnippet.substring(0, SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) : mSnippet; } catch (IllegalArgumentException e) { @@ -74,7 +86,9 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD return; } + // 创建MediaPlayer对象,用于后续播放闹钟声音 mPlayer = new MediaPlayer(); + // 判断笔记是否在笔记数据库中可见,如果可见则展示操作对话框并播放闹钟声音,否则结束该Activity if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { showActionDialog(); playAlarmSound(); @@ -83,18 +97,20 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } } + // 判断屏幕是否处于开启状态的方法,通过获取PowerManager服务来检查屏幕状态 private boolean isScreenOn() { PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); return pm.isScreenOn(); } + // 播放闹钟提醒声音的方法,获取默认的闹钟铃声Uri,并根据系统设置配置音频流类型,然后加载并播放声音,同时设置为循环播放 private void playAlarmSound() { Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); int silentModeStreams = Settings.System.getInt(getContentResolver(), Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); - if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) { + if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM))!= 0) { mPlayer.setAudioStreamType(silentModeStreams); } else { mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); @@ -119,6 +135,7 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } } + // 展示操作对话框的方法,创建一个AlertDialog,设置标题、消息内容、正按钮以及根据屏幕是否开启来决定是否设置负按钮,并设置对话框消失时的监听器 private void showActionDialog() { AlertDialog.Builder dialog = new AlertDialog.Builder(this); dialog.setTitle(R.string.app_name); @@ -130,6 +147,7 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD dialog.show().setOnDismissListener(this); } + // 处理对话框按钮点击事件的方法,根据点击的按钮进行相应的逻辑处理,点击负按钮时跳转到笔记编辑页面查看对应的笔记 public void onClick(DialogInterface dialog, int which) { switch (which) { case DialogInterface.BUTTON_NEGATIVE: @@ -143,13 +161,15 @@ public class AlarmAlertActivity extends Activity implements OnClickListener, OnD } } + // 处理对话框消失事件的方法,停止正在播放的闹钟声音,并结束当前Activity public void onDismiss(DialogInterface dialog) { stopAlarmSound(); finish(); } + // 停止闹钟声音播放的方法,释放MediaPlayer资源,将其置为null private void stopAlarmSound() { - if (mPlayer != null) { + if (mPlayer!= null) { mPlayer.stop(); mPlayer.release(); mPlayer = null; diff --git a/src/Notes-master/src/net/micode/notes/ui/AlarmInitReceiver.java b/src/Notes-master/src/net/micode/notes/ui/AlarmInitReceiver.java index f221202..f5d06b7 100644 --- a/src/Notes-master/src/net/micode/notes/ui/AlarmInitReceiver.java +++ b/src/Notes-master/src/net/micode/notes/ui/AlarmInitReceiver.java @@ -28,37 +28,55 @@ import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; +// AlarmInitReceiver类继承自BroadcastReceiver,用于接收系统广播并执行与闹钟初始化相关的操作,比如根据条件查询数据库中的笔记信息,为符合条件的笔记设置对应的闹钟提醒 public class AlarmInitReceiver extends BroadcastReceiver { + // 定义一个字符串数组,用于指定从数据库查询时要获取的列名,这里包含笔记的ID和提醒日期两列 private static final String [] PROJECTION = new String [] { - NoteColumns.ID, - NoteColumns.ALERTED_DATE + NoteColumns.ID, + NoteColumns.ALERTED_DATE }; + // 定义常量,表示查询结果中笔记ID所在列的索引,值为0,方便后续从游标中获取对应数据 private static final int COLUMN_ID = 0; + // 定义常量,表示查询结果中提醒日期所在列的索引,值为1,方便后续从游标中获取对应数据 private static final int COLUMN_ALERTED_DATE = 1; + // 重写BroadcastReceiver的onReceive方法,当该广播接收器接收到相应广播时会执行此方法内的逻辑 @Override public void onReceive(Context context, Intent intent) { + // 获取当前系统时间的毫秒数,用于后续作为查询条件去筛选数据库中的记录 long currentDate = System.currentTimeMillis(); + + // 通过ContentResolver执行数据库查询操作,从指定的Uri(Notes.CONTENT_NOTE_URI)中查询数据,按照PROJECTION定义的列进行查询, + // 使用提供的查询条件筛选出提醒日期大于当前时间且类型为笔记(Notes.TYPE_NOTE)的记录,查询参数使用当前时间的字符串表示形式 Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, PROJECTION, NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, new String[] { String.valueOf(currentDate) }, null); - if (c != null) { + // 判断游标是否不为空,即是否查询到了符合条件的数据 + if (c!= null) { + // 将游标移动到第一条查询结果记录位置,如果有结果则开始遍历处理 if (c.moveToFirst()) { do { + // 从游标中获取提醒日期对应的列数据,得到该笔记的提醒时间(以毫秒为单位的时间戳形式) long alertDate = c.getLong(COLUMN_ALERTED_DATE); + // 创建一个新的Intent,用于启动AlarmReceiver这个广播接收器,传递与当前笔记相关的信息 Intent sender = new Intent(context, AlarmReceiver.class); + // 为Intent设置数据,将当前笔记的ID附加到对应的Uri上,以便接收方可以识别是哪个笔记的闹钟相关操作 sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); + // 创建一个PendingIntent,用于在合适的时候触发指定的广播(这里是AlarmReceiver),设置相关的上下文、请求码、Intent以及标志位 PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); + // 获取系统的AlarmManager服务,用于设置闹钟提醒相关的操作 AlarmManager alermManager = (AlarmManager) context .getSystemService(Context.ALARM_SERVICE); + // 通过AlarmManager设置闹钟提醒,使用RTC_WAKEUP模式(在指定的绝对时间唤醒设备来触发提醒),传入提醒时间和对应的PendingIntent alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); } while (c.moveToNext()); } + // 关闭游标,释放相关资源,避免内存泄漏等问题 c.close(); } } 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..fbb4dd1 100644 --- a/src/Notes-master/src/net/micode/notes/ui/AlarmReceiver.java +++ b/src/Notes-master/src/net/micode/notes/ui/AlarmReceiver.java @@ -20,11 +20,18 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +// AlarmReceiver类继承自BroadcastReceiver,它的主要作用是接收广播,并在接收到广播后进行相应的页面跳转操作,以展示闹钟提醒相关的界面 public class AlarmReceiver extends BroadcastReceiver { + // 重写BroadcastReceiver的onReceive方法,当该广播接收器接收到广播时,此方法内的逻辑将会被执行 @Override public void onReceive(Context context, Intent intent) { + // 将传入的Intent的目标类设置为AlarmAlertActivity.class,也就是当这个Intent被启动时,将会启动AlarmAlertActivity这个Activity, + // 通常用于根据接收到的广播信息跳转到对应的展示界面,此处就是跳转到闹钟提醒相关的界面 intent.setClass(context, AlarmAlertActivity.class); + // 为Intent添加标志位FLAG_ACTIVITY_NEW_TASK,这是因为从广播接收器中启动Activity需要这个标志位来确保Activity能够在新的任务栈中被正确启动, + // 否则可能会出现启动失败等问题,特别是在应用处于后台或者未运行的一些场景下 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // 通过传入的上下文对象启动设置好的Intent,从而启动对应的Activity,实现从接收到广播到展示闹钟提醒界面的流程 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..ea30c75 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,128 @@ import android.view.View; import android.widget.FrameLayout; import android.widget.NumberPicker; +// DateTimePicker类继承自FrameLayout,它是一个自定义的视图组件,用于实现日期和时间的选择功能,支持24小时制和12小时制(含AM/PM)的显示与操作,并且可以响应各种时间组件值变化的事件。 public class DateTimePicker extends FrameLayout { + // 默认的启用状态,初始化为true,表示默认启用该日期时间选择器 private static final boolean DEFAULT_ENABLE_STATE = true; - + // 半天包含的小时数,用于12小时制相关的时间计算和判断 private static final int HOURS_IN_HALF_DAY = 12; + // 一整天包含的小时数,用于24小时制相关的时间计算和判断等操作 private static final int HOURS_IN_ALL_DAY = 24; + // 一周包含的天数,用于日期选择器相关的范围设置等操作 private static final int DAYS_IN_ALL_WEEK = 7; + // 日期选择器(NumberPicker)的最小值,通常从0开始,表示一周中的第一天等情况 private static final int DATE_SPINNER_MIN_VAL = 0; + // 日期选择器(NumberPicker)的最大值,设置为一周天数减1,对应一周中的最后一天 private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1; + // 24小时制视图下,小时选择器(NumberPicker)的最小值,即0时 private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0; + // 24小时制视图下,小时选择器(NumberPicker)的最大值,即23时 private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; + // 12小时制视图下,小时选择器(NumberPicker)的最小值,即1时(通常12小时制显示从1开始) private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; + // 12小时制视图下,小时选择器(NumberPicker)的最大值,即12时 private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12; + // 分钟选择器(NumberPicker)的最小值,即0分 private static final int MINUT_SPINNER_MIN_VAL = 0; + // 分钟选择器(NumberPicker)的最大值,即59分,表示一分钟的最大数值 private static final int MINUT_SPINNER_MAX_VAL = 59; + // AM/PM选择器(NumberPicker)的最小值,通常0表示AM private static final int AMPM_SPINNER_MIN_VAL = 0; + // AM/PM选择器(NumberPicker)的最大值,通常1表示PM private static final int AMPM_SPINNER_MAX_VAL = 1; + // 用于选择日期的NumberPicker组件,用户可以通过它滚动选择具体的日期(以周为范围参考) private final NumberPicker mDateSpinner; + // 用于选择小时的NumberPicker组件,根据设置的24小时制或12小时制显示不同的范围值供用户选择小时数 private final NumberPicker mHourSpinner; + // 用于选择分钟的NumberPicker组件,范围是0到59分供用户选择具体的分钟数 private final NumberPicker mMinuteSpinner; + // 用于选择上午(AM)或下午(PM)的NumberPicker组件,仅在12小时制下可见并起作用 private final NumberPicker mAmPmSpinner; + // 用于存储当前选择的日期时间信息的Calendar对象,方便进行各种时间相关的计算和设置操作 private Calendar mDate; + // 用于存储要在日期选择器中显示的一周日期的字符串数组,每个元素对应一周中的一天 private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; + // 用于标记当前时间是否处于上午(AM),初始值根据当前小时数判断,大于等于12小时则为false(即下午PM) private boolean mIsAm; + // 用于标记当前是否处于24小时制视图,初始值根据系统设置来确定(通过调用DateFormat.is24HourFormat(context)获取) private boolean mIs24HourView; + // 用于标记该日期时间选择器是否启用,初始值为默认启用状态(DEFAULT_ENABLE_STATE) private boolean mIsEnabled = DEFAULT_ENABLE_STATE; + // 用于标记是否正在进行初始化操作,初始化为true,在初始化完成后会设置为false,用于避免一些不必要的重复操作或逻辑判断干扰 private boolean mInitialising; + // 定义一个接口类型的成员变量,用于设置当日期时间发生变化时的回调监听器,外部类可以实现该接口来响应时间变化事件 private OnDateTimeChangedListener mOnDateTimeChangedListener; + // 日期选择器(NumberPicker)的值变化监听器,当日期选择器的值发生改变时会触发此监听器内的逻辑 + // 主要作用是根据选择的日期变化来更新内部的日期数据(mDate),并调用相关方法更新界面显示以及通知日期时间变化的监听器(如果有设置) private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + // 根据新选择的日期与旧日期的差值,调整内部存储的日期(mDate),实现日期的增减操作 mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal); + // 更新日期选择器的显示相关内容,比如更新显示的一周日期字符串等 updateDateControl(); + // 通知日期时间发生了变化,触发设置的回调监听器(如果有)执行相应逻辑 onDateTimeChanged(); } }; + // 小时选择器(NumberPicker)的值变化监听器,当小时选择器的值发生改变时会触发此监听器内的逻辑 + // 会根据是否是24小时制以及新旧小时值的情况,处理日期的变化(跨天等情况)、AM/PM的切换(12小时制下)以及更新内部日期时间数据和通知相关监听器等操作 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点)且跨越到下一天的情况 if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, 1); isDateChanged = true; - } else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + } + // 从下午切换到上午(12点到11点)且回退到上一天的情况 + else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, -1); isDateChanged = true; } + // 处理11点到12点或者12点到11点切换时,AM/PM状态的改变,并更新对应的AM/PM控制显示 if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY || oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { - mIsAm = !mIsAm; + mIsAm =!mIsAm; 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) { + } + // 24小时制下,从0时切换到23时(回退一天)的情况 + else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, -1); isDateChanged = true; } } - int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY); + // 根据是否是12小时制以及当前的AM/PM状态,计算出正确的小时数并设置到内部的日期对象(mDate)中 + int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm? 0 : HOURS_IN_HALF_DAY); mDate.set(Calendar.HOUR_OF_DAY, newHour); + // 通知日期时间发生了变化,触发设置的回调监听器(如果有)执行相应逻辑 onDateTimeChanged(); + // 如果日期发生了变化,更新当前的年、月、日信息到内部的日期对象(mDate)中 if (isDateChanged) { setCurrentYear(cal.get(Calendar.YEAR)); setCurrentMonth(cal.get(Calendar.MONTH)); @@ -115,18 +158,23 @@ public class DateTimePicker extends FrameLayout { } }; + // 分钟选择器(NumberPicker)的值变化监听器,当分钟选择器的值发生改变时会触发此监听器内的逻辑 + // 会处理分钟变化导致的小时变化(跨小时等情况)、AM/PM的切换(影响到跨12点时)以及更新内部日期时间数据和通知相关监听器等操作 private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { int minValue = mMinuteSpinner.getMinValue(); int maxValue = mMinuteSpinner.getMaxValue(); int offset = 0; + // 分钟从最大值变为最小值(如59分变为0分,可能跨小时)的情况 if (oldVal == maxValue && newVal == minValue) { offset += 1; - } else if (oldVal == minValue && newVal == maxValue) { + } + // 分钟从最小值变为最大值(如0分变为59分,可能跨小时)的情况 + else if (oldVal == minValue && newVal == maxValue) { offset -= 1; } - if (offset != 0) { + if (offset!= 0) { mDate.add(Calendar.HOUR_OF_DAY, offset); mHourSpinner.setValue(getCurrentHour()); updateDateControl(); @@ -144,10 +192,12 @@ public class DateTimePicker extends FrameLayout { } }; + // AM/PM选择器(NumberPicker)的值变化监听器,当AM/PM选择器的值发生改变时会触发此监听器内的逻辑 + // 主要作用是根据AM/PM的切换来调整内部日期时间数据(增减12小时),更新AM/PM控制显示以及通知日期时间变化的监听器(如果有)执行相应逻辑 private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { - mIsAm = !mIsAm; + mIsAm =!mIsAm; if (mIsAm) { mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); } else { @@ -158,39 +208,51 @@ public class DateTimePicker extends FrameLayout { } }; + // 定义一个接口,用于外部类实现,当日期时间在DateTimePicker中发生变化时,会回调此接口的方法,传递相关的日期时间参数 public interface OnDateTimeChangedListener { void onDateTimeChanged(DateTimePicker view, int year, int month, - int dayOfMonth, int hourOfDay, int minute); + int dayOfMonth, int hourOfDay, int minute); } + // 构造函数,使用默认的当前时间(System.currentTimeMillis())来初始化DateTimePicker,调用另一个构造函数传递当前时间和系统默认的时间显示格式(24小时制或12小时制)来完成初始化 public DateTimePicker(Context context) { this(context, System.currentTimeMillis()); } + // 构造函数,使用指定的时间(date参数)来初始化DateTimePicker,调用另一个构造函数传递指定时间和系统默认的时间显示格式(24小时制或12小时制)来完成初始化 public DateTimePicker(Context context, long date) { this(context, date, DateFormat.is24HourFormat(context)); } + // 主要的构造函数,完成DateTimePicker的初始化工作,包括加载布局、初始化各个NumberPicker组件及其监听器、设置初始时间、设置启用状态以及一些初始的显示控制等操作 public DateTimePicker(Context context, long date, boolean is24HourView) { super(context); + // 获取一个默认的Calendar实例,用于存储和操作当前的日期时间信息 mDate = Calendar.getInstance(); mInitialising = true; + // 根据当前小时数判断初始的AM/PM状态,大于等于12小时则为下午(PM),即mIsAm为false mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; + // 加载指定的布局文件(R.layout.datetime_picker)到该FrameLayout中,用于显示日期时间选择的各个组件 inflate(context, R.layout.datetime_picker, this); + // 获取布局中的日期选择器(NumberPicker)组件,并进行相关设置,如设置最小值、最大值以及值变化监听器 mDateSpinner = (NumberPicker) findViewById(R.id.date); mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); + // 获取布局中的小时选择器(NumberPicker)组件,并设置值变化监听器 mHourSpinner = (NumberPicker) findViewById(R.id.hour); mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); + + // 获取布局中的分钟选择器(NumberPicker)组件,设置最小值、最大值、长按更新间隔等,并设置值变化监听器 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的字符串数组(根据系统语言环境等获取本地化的AM/PM表示),并设置到AM/PM选择器(NumberPicker)组件中,包括设置最小值、最大值、显示值以及值变化监听器 String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm); mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); @@ -198,288 +260,22 @@ public class DateTimePicker extends FrameLayout { mAmPmSpinner.setDisplayedValues(stringsForAmPm); mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); - // update controls to initial state + // 更新各个相关的控制显示,使其处于初始状态,比如日期选择器显示的一周日期字符串、小时选择器的范围显示、AM/PM选择器的初始可见性等 updateDateControl(); updateHourControl(); updateAmPmControl(); + // 设置是否为24小时制视图,根据传入的参数决定,并相应地调整相关组件的显示和值范围等 set24HourView(is24HourView); - // set to current time + // 设置当前显示的日期时间为传入的指定日期(date参数),通过设置年、月、日、小时、分钟等各个部分来完成 setCurrentDate(date); + // 设置该日期时间选择器的启用状态,根据当前的启用状态(mIsEnabled)来决定 setEnabled(isEnabled()); - // set the content descriptions + // 初始化完成,将标记设置为false,表示不再处于初始化阶段 mInitialising = false; } - @Override - public void setEnabled(boolean enabled) { - if (mIsEnabled == enabled) { - return; - } - super.setEnabled(enabled); - mDateSpinner.setEnabled(enabled); - mMinuteSpinner.setEnabled(enabled); - mHourSpinner.setEnabled(enabled); - mAmPmSpinner.setEnabled(enabled); - mIsEnabled = enabled; - } - - @Override - public boolean isEnabled() { - return mIsEnabled; - } - - /** - * Get the current date in millis - * - * @return the current date in millis - */ - public long getCurrentDateInTimeMillis() { - return mDate.getTimeInMillis(); - } - - /** - * Set the current date - * - * @param date The current date in millis - */ - public void setCurrentDate(long date) { - Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(date); - setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), - cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE)); - } - - /** - * Set the current date - * - * @param year The current year - * @param month The current month - * @param dayOfMonth The current dayOfMonth - * @param hourOfDay The current hourOfDay - * @param minute The current minute - */ - public void setCurrentDate(int year, int month, - int dayOfMonth, int hourOfDay, int minute) { - setCurrentYear(year); - setCurrentMonth(month); - setCurrentDay(dayOfMonth); - setCurrentHour(hourOfDay); - setCurrentMinute(minute); - } - - /** - * Get current year - * - * @return The current year - */ - public int getCurrentYear() { - return mDate.get(Calendar.YEAR); - } - - /** - * Set current year - * - * @param year The current year - */ - public void setCurrentYear(int year) { - if (!mInitialising && year == getCurrentYear()) { - return; - } - mDate.set(Calendar.YEAR, year); - updateDateControl(); - onDateTimeChanged(); - } - - /** - * Get current month in the year - * - * @return The current month in the year - */ - public int getCurrentMonth() { - return mDate.get(Calendar.MONTH); - } - - /** - * Set current month in the year - * - * @param month The month in the year - */ - public void setCurrentMonth(int month) { - if (!mInitialising && month == getCurrentMonth()) { - return; - } - mDate.set(Calendar.MONTH, month); - updateDateControl(); - onDateTimeChanged(); - } - - /** - * Get current day of the month - * - * @return The day of the month - */ - public int getCurrentDay() { - return mDate.get(Calendar.DAY_OF_MONTH); - } - - /** - * Set current day of the month - * - * @param dayOfMonth The day of the month - */ - public void setCurrentDay(int dayOfMonth) { - if (!mInitialising && dayOfMonth == getCurrentDay()) { - return; - } - mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); - updateDateControl(); - onDateTimeChanged(); - } - - /** - * Get current hour in 24 hour mode, in the range (0~23) - * @return The current hour in 24 hour mode - */ - public int getCurrentHourOfDay() { - return mDate.get(Calendar.HOUR_OF_DAY); - } - - private int getCurrentHour() { - if (mIs24HourView){ - return getCurrentHourOfDay(); - } else { - int hour = getCurrentHourOfDay(); - if (hour > HOURS_IN_HALF_DAY) { - return hour - HOURS_IN_HALF_DAY; - } else { - return hour == 0 ? HOURS_IN_HALF_DAY : hour; - } - } - } - - /** - * Set current hour in 24 hour mode, in the range (0~23) - * - * @param hourOfDay - */ - public void setCurrentHour(int hourOfDay) { - if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { - return; - } - mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); - if (!mIs24HourView) { - if (hourOfDay >= HOURS_IN_HALF_DAY) { - mIsAm = false; - if (hourOfDay > HOURS_IN_HALF_DAY) { - hourOfDay -= HOURS_IN_HALF_DAY; - } - } else { - mIsAm = true; - if (hourOfDay == 0) { - hourOfDay = HOURS_IN_HALF_DAY; - } - } - updateAmPmControl(); - } - mHourSpinner.setValue(hourOfDay); - onDateTimeChanged(); - } - - /** - * Get currentMinute - * - * @return The Current Minute - */ - public int getCurrentMinute() { - return mDate.get(Calendar.MINUTE); - } - - /** - * Set current minute - */ - public void setCurrentMinute(int minute) { - if (!mInitialising && minute == getCurrentMinute()) { - return; - } - mMinuteSpinner.setValue(minute); - mDate.set(Calendar.MINUTE, minute); - onDateTimeChanged(); - } - - /** - * @return true if this is in 24 hour view else false. - */ - public boolean is24HourView () { - return mIs24HourView; - } - - /** - * Set whether in 24 hour or AM/PM mode. - * - * @param is24HourView True for 24 hour mode. False for AM/PM mode. - */ - public void set24HourView(boolean is24HourView) { - if (mIs24HourView == is24HourView) { - return; - } - mIs24HourView = is24HourView; - mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE); - int hour = getCurrentHourOfDay(); - updateHourControl(); - setCurrentHour(hour); - updateAmPmControl(); - } - - private void updateDateControl() { - Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(mDate.getTimeInMillis()); - cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1); - mDateSpinner.setDisplayedValues(null); - for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) { - cal.add(Calendar.DAY_OF_YEAR, 1); - mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal); - } - mDateSpinner.setDisplayedValues(mDateDisplayValues); - mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); - mDateSpinner.invalidate(); - } - - private void updateAmPmControl() { - if (mIs24HourView) { - mAmPmSpinner.setVisibility(View.GONE); - } else { - int index = mIsAm ? Calendar.AM : Calendar.PM; - mAmPmSpinner.setValue(index); - mAmPmSpinner.setVisibility(View.VISIBLE); - } - } - - private void updateHourControl() { - if (mIs24HourView) { - mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); - mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW); - } else { - mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW); - mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW); - } - } - - /** - * Set the callback that indicates the 'Set' button has been pressed. - * @param callback the callback, if null will do nothing - */ - public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) { - mOnDateTimeChangedListener = callback; - } - - private void onDateTimeChanged() { - if (mOnDateTimeChangedListener != null) { - mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), - getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute()); - } - } -} +// 重写父类的setEnabled方法,用于设置整个日期时间选择器及其内部 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..5659978 100644 --- a/src/Notes-master/src/net/micode/notes/ui/DateTimePickerDialog.java +++ b/src/Notes-master/src/net/micode/notes/ui/DateTimePickerDialog.java @@ -29,24 +29,37 @@ 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对象,初始化为获取当前系统时间对应的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); } + // 构造函数,用于创建DateTimePickerDialog实例,接收上下文和初始日期(以毫秒表示)作为参数,完成对话框的基本初始化设置工作, + // 包括创建DateTimePicker组件、设置其相关监听器、初始化显示的日期时间、设置对话框的按钮以及标题等内容。 public DateTimePickerDialog(Context context, long date) { super(context); + // 创建一个DateTimePicker实例,用于在对话框中展示日期时间选择界面,传入当前上下文环境。 mDateTimePicker = new DateTimePicker(context); + // 将DateTimePicker组件设置为对话框的显示内容,使其在对话框中展示出来供用户操作。 setView(mDateTimePicker); + // 为DateTimePicker组件设置日期时间变化监听器,当用户在DateTimePicker中改变了日期、时间等任何相关值时,会触发此监听器内的逻辑。 + // 主要作用是根据用户选择的新值更新内部存储的日期时间信息(mDate),并更新对话框的标题显示,以实时反映当前选择的日期时间。 mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { public void onDateTimeChanged(DateTimePicker view, int year, int month, - int dayOfMonth, int hourOfDay, int minute) { + int dayOfMonth, int hourOfDay, int minute) { mDate.set(Calendar.YEAR, year); mDate.set(Calendar.MONTH, month); mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); @@ -55,34 +68,48 @@ public class DateTimePickerDialog extends AlertDialog implements OnClickListener updateTitle(mDate.getTimeInMillis()); } }); + // 设置初始显示的日期时间,将传入的日期(以毫秒表示)设置到内部的日期时间对象(mDate)中,并将秒数设置为0,确保初始时间的准确性。 mDate.setTimeInMillis(date); mDate.set(Calendar.SECOND, 0); + // 将DateTimePicker组件显示的日期时间设置为当前初始化的日期时间,使其在界面上初始展示正确的时间内容。 mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); + // 设置对话框的确认按钮(通常显示为"确定"之类的文本),传入对应文本资源的字符串,并设置按钮点击事件的监听器为当前类自身(因为实现了OnClickListener接口)。 setButton(context.getString(R.string.datetime_dialog_ok), this); + // 设置对话框的取消按钮(通常显示为"取消"之类的文本),传入对应文本资源的字符串,并传入null作为点击事件监听器,表示默认的取消行为。 setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); + // 根据系统当前设置判断是否为24小时制视图,调用DateFormat.is24HourFormat方法获取并设置,以决定时间选择器的显示格式。 set24HourView(DateFormat.is24HourFormat(this.getContext())); + // 初始更新一次对话框的标题,使其显示当前设置的日期时间信息,调用updateTitle方法传入当前日期时间的毫秒数来完成更新。 updateTitle(mDate.getTimeInMillis()); } + // 用于设置当前是否为24小时制视图的方法,接收一个布尔值参数,更新内部的标记变量(mIs24HourView),外部可以通过调用此方法来动态切换时间显示格式。 public void set24HourView(boolean is24HourView) { mIs24HourView = is24HourView; } + // 用于设置日期时间确认回调监听器的方法,接收一个实现了OnDateTimeSetListener接口的对象作为参数,将其赋值给内部的监听器成员变量, + // 外部可以通过调用此方法来注册自己的监听器,以便在用户确认选择日期时间后执行相应的逻辑。 public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { mOnDateTimeSetListener = callBack; } + // 用于更新对话框标题显示的私有方法,根据传入的日期时间(以毫秒表示)以及当前的时间显示格式等设置,格式化日期时间字符串并设置为对话框的标题内容, + // 使其能准确展示当前选择的日期时间信息,方便用户查看确认。 private void updateTitle(long date) { int flag = - DateUtils.FORMAT_SHOW_YEAR | - DateUtils.FORMAT_SHOW_DATE | - DateUtils.FORMAT_SHOW_TIME; - flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; + DateUtils.FORMAT_SHOW_YEAR | + DateUtils.FORMAT_SHOW_DATE | + DateUtils.FORMAT_SHOW_TIME; + flag |= mIs24HourView? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); } + // 实现OnClickListener接口的方法,用于处理对话框按钮点击事件,当用户点击确认按钮(通过传入的参数判断)时, + // 如果设置了日期时间确认回调监听器(mOnDateTimeSetListener不为null),则调用该监听器的方法,传递当前对话框实例和当前选择的日期时间(以毫秒表示)参数, + // 通知外部用户已完成日期时间设置并确认的操作。 public void onClick(DialogInterface arg0, int arg1) { - if (mOnDateTimeSetListener != null) { + 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..57c5ddd 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,31 @@ import android.widget.PopupMenu.OnMenuItemClickListener; import net.micode.notes.R; +// DropdownMenu类用于创建一个下拉菜单的功能组件,它将一个按钮与一个PopupMenu关联起来,点击按钮时弹出菜单, +// 并且可以设置菜单选项的点击监听器、查找菜单项以及设置按钮显示的标题等操作。 public class DropdownMenu { + + // 用于显示下拉菜单的触发按钮,用户点击此按钮会弹出对应的下拉菜单。 private Button mButton; + // 实际的PopupMenu对象,用于承载和展示下拉菜单的内容,包含多个菜单项供用户选择操作。 private PopupMenu mPopupMenu; + // 对应PopupMenu中的菜单对象,通过它可以进行如查找菜单项等与菜单内容相关的操作。 private Menu mMenu; + // 构造函数,用于初始化DropdownMenu实例,接收上下文、触发按钮以及菜单资源ID作为参数,完成下拉菜单的基本构建工作, + // 包括设置按钮背景、创建PopupMenu、加载菜单资源以及为按钮设置点击监听器以弹出菜单等操作。 public DropdownMenu(Context context, Button button, int menuId) { + // 将传入的按钮对象赋值给成员变量,后续通过此按钮来触发下拉菜单的显示。 mButton = button; + // 为按钮设置背景资源,使用指定的资源ID(R.drawable.dropdown_icon)来设置按钮的外观样式,使其显示为一个具有下拉箭头等特征的图标样式(通常)。 mButton.setBackgroundResource(R.drawable.dropdown_icon); + // 创建一个PopupMenu实例,传入上下文和触发按钮作为参数,使得PopupMenu与对应的按钮关联起来,并且会根据按钮的位置等情况来显示弹出菜单。 mPopupMenu = new PopupMenu(context, mButton); + // 获取PopupMenu中的菜单对象,赋值给成员变量,方便后续对菜单内容进行操作,比如查找菜单项等。 mMenu = mPopupMenu.getMenu(); + // 通过菜单填充器(MenuInflater)将指定的菜单资源(由menuId指定)加载到PopupMenu的菜单对象中,从而显示出具体的菜单项内容。 mPopupMenu.getMenuInflater().inflate(menuId, mMenu); + // 为按钮设置点击监听器,当按钮被点击时,触发内部的逻辑,即显示与之关联的PopupMenu,实现点击按钮弹出下拉菜单的功能。 mButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { mPopupMenu.show(); @@ -45,16 +59,22 @@ public class DropdownMenu { }); } + // 用于设置下拉菜单中菜单项点击监听器的方法,接收一个实现了OnMenuItemClickListener接口的对象作为参数, + // 将其设置给PopupMenu,使得当用户点击下拉菜单中的任意菜单项时,能够触发相应的逻辑处理,外部可以通过此方法来响应菜单项的点击操作。 public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { - if (mPopupMenu != null) { + if (mPopupMenu!= null) { mPopupMenu.setOnMenuItemClickListener(listener); } } + // 用于在菜单中查找指定ID的菜单项的方法,接收一个菜单项的ID作为参数,通过调用菜单对象(mMenu)的findItem方法来查找对应的菜单项, + // 返回查找到的MenuItem对象,方便外部对特定菜单项进行进一步的操作,比如获取菜单项属性、修改菜单项状态等。 public MenuItem findItem(int id) { return mMenu.findItem(id); } + // 用于设置触发下拉菜单的按钮显示标题的方法,接收一个字符序列(通常是字符串)作为参数,将其设置为按钮显示的文本内容, + // 这样可以根据需要动态改变按钮上显示的提示信息,例如显示当前选中的菜单项相关内容等。 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..31051ce 100644 --- a/src/Notes-master/src/net/micode/notes/ui/FoldersListAdapter.java +++ b/src/Notes-master/src/net/micode/notes/ui/FoldersListAdapter.java @@ -29,52 +29,74 @@ import net.micode.notes.data.Notes; import net.micode.notes.data.Notes.NoteColumns; +// FoldersListAdapter类继承自CursorAdapter,它主要用于将数据库查询得到的游标数据(Cursor)适配显示到特定的视图列表中, +// 在这里是用于展示文件夹相关信息的列表,比如文件夹名称等内容,并且提供了获取文件夹名称等相关的辅助方法。 public class FoldersListAdapter extends CursorAdapter { + + // 定义一个字符串数组,用于指定从数据库查询时要获取的列名,这里包含笔记的ID和摘要信息(可能用于表示文件夹相关的标识或其他关联内容)两列。 public static final String [] PROJECTION = { - NoteColumns.ID, - NoteColumns.SNIPPET + NoteColumns.ID, + NoteColumns.SNIPPET }; + // 定义常量,表示查询结果中笔记ID所在列的索引,值为0,方便后续从游标中获取对应数据,这里命名为ID_COLUMN。 public static final int ID_COLUMN = 0; + // 定义常量,表示查询结果中摘要信息(在这里用于表示文件夹名称相关内容)所在列的索引,值为1,方便后续从游标中获取对应数据,命名为NAME_COLUMN。 public static final int NAME_COLUMN = 1; + // 构造函数,接收上下文和游标(Cursor)作为参数,调用父类的构造函数来完成基本的初始化工作, + // 这里暂时没有额外的自定义初始化逻辑(除了调用父类构造函数),只是保留了构造函数的标准写法,注释中提示了待完善的部分(可能后续会添加更多初始化相关操作)。 public FoldersListAdapter(Context context, Cursor c) { super(context, c); // TODO Auto-generated constructor stub } + // 重写CursorAdapter的newView方法,该方法用于创建一个新的视图(View)对象,用于显示列表中的每一项数据, + // 在这里返回一个自定义的FolderListItem视图,意味着每一项列表数据将会使用这个自定义视图来展示相关信息。 @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return new FolderListItem(context); } + // 重写CursorAdapter的bindView方法,该方法用于将游标(Cursor)中的数据绑定到已创建的视图(View)上,也就是填充具体的数据到视图中进行显示, + // 在这里根据游标中的数据情况,判断如果是根文件夹(通过ID判断),则显示特定的字符串(可能是代表根文件夹的固定文本),否则显示游标中对应列的字符串内容(文件夹名称), + // 然后调用自定义视图FolderListItem的bind方法将名称设置到对应的TextView上进行显示。 @Override public void bindView(View view, Context context, Cursor cursor) { if (view instanceof FolderListItem) { - String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context + String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER)? context .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); ((FolderListItem) view).bind(folderName); } } + // 提供的一个公共方法,用于获取指定位置的文件夹名称,接收上下文和位置索引作为参数, + // 通过调用getItem方法获取对应位置的游标对象,然后根据游标中数据判断是根文件夹还是普通文件夹,返回相应的名称字符串。 public String getFolderName(Context context, int position) { Cursor cursor = (Cursor) getItem(position); - return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context + return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER)? context .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); } + // 定义了一个内部私有类FolderListItem,它继承自LinearLayout,用于作为列表中每一项数据展示的自定义视图布局, + // 包含了一个TextView用于显示文件夹名称,并且提供了一个bind方法用于将名称设置到这个TextView上进行展示。 private class FolderListItem extends LinearLayout { + // 用于显示文件夹名称的TextView组件,通过它将文件夹相关的名称信息展示给用户。 private TextView mName; + // 构造函数,接收上下文作为参数,调用父类(LinearLayout)的构造函数完成基本初始化, + // 然后通过LayoutInflater将指定的布局文件(R.layout.folder_list_item)加载到这个视图中, + // 最后获取布局中的TextView组件用于后续设置名称显示。 public FolderListItem(Context context) { super(context); inflate(context, R.layout.folder_list_item, this); mName = (TextView) findViewById(R.id.tv_folder_name); } + // 用于将传入的文件夹名称设置到内部的TextView(mName)上进行显示的方法,外部通过调用此方法来更新视图中显示的文件夹名称内容。 public void bind(String name) { mName.setText(name); } } -} +} \ No newline at end of file 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..da7cc5c 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/NoteEditActivity.java @@ -72,18 +72,19 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +// NoteEditActivity类继承自Activity,实现了多个接口,用于处理笔记编辑相关的各种操作,包括界面初始化、数据保存、样式设置、提醒设置、分享等众多功能。 public class NoteEditActivity extends Activity implements OnClickListener, NoteSettingChangedListener, OnTextViewChangeListener { + + // 用于存储笔记头部相关视图组件的内部类,方便对这些组件进行统一管理和操作,例如显示修改日期、提醒图标及日期等组件。 private class HeadViewHolder { public TextView tvModified; - public ImageView ivAlertIcon; - public TextView tvAlertDate; - public ImageView ibSetBgColor; } + // 背景颜色选择按钮与对应颜色资源ID的映射关系,以按钮ID为键,颜色资源ID为值,方便根据按钮点击来确定选择的背景颜色。 private static final Map sBgSelectorBtnsMap = new HashMap(); static { sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); @@ -93,6 +94,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); } + // 背景颜色选择按钮与对应选中状态显示按钮ID的映射关系,用于在选择背景颜色后,显示对应的选中标识按钮。 private static final Map sBgSelectorSelectionMap = new HashMap(); static { sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); @@ -102,6 +104,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); } + // 字体大小选择按钮与对应字体大小资源ID的映射关系,用于根据按钮点击来确定选择的字体大小。 private static final Map sFontSizeBtnsMap = new HashMap(); static { sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); @@ -110,6 +113,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER); } + // 字体大小资源ID与对应选中状态显示按钮ID的映射关系,用于在选择字体大小后,显示对应的选中标识按钮。 private static final Map sFontSelectorSelectionMap = new HashMap(); static { sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); @@ -118,43 +122,62 @@ public class NoteEditActivity extends Activity implements OnClickListener, sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select); } + // 用于日志记录的标签,方便在调试等场景下识别该类相关的日志输出。 private static final String TAG = "NoteEditActivity"; + // 存储笔记头部视图组件的实例,通过它可以访问和操作头部相关的各个视图元素,如修改日期、提醒图标及日期等显示的文本视图和图像视图。 private HeadViewHolder mNoteHeaderHolder; + // 笔记标题栏所在的视图,可能包含标题等相关展示内容,用于后续设置背景等操作。 private View mHeadViewPanel; + // 用于展示背景颜色选择的视图,通过控制其显示隐藏来展示或隐藏背景颜色选择界面。 private View mNoteBgColorSelector; + // 用于展示字体大小选择的视图,同样通过控制其显示隐藏来展示或隐藏字体大小选择界面。 private View mFontSizeSelector; + // 用于编辑笔记内容的EditText组件,用户在此输入和编辑笔记的具体文本内容。 private EditText mNoteEditor; + // 笔记编辑区域所在的视图面板,可能包含编辑文本的滚动等相关功能,用于设置背景等操作。 private View mNoteEditorPanel; + // 代表正在编辑的笔记对象,封装了笔记相关的数据和操作方法,如保存、获取内容、设置各种属性等。 private WorkingNote mWorkingNote; + // 用于存储应用的共享偏好设置,可通过它获取和保存一些用户相关的设置信息,例如字体大小偏好设置等。 private SharedPreferences mSharedPrefs; + // 当前选择的字体大小资源ID,初始值从共享偏好设置中获取,用于控制笔记编辑文本的字体大小显示。 private int mFontSizeId; + // 用于存储字体大小偏好设置的键名,在共享偏好设置中以此键来获取和保存字体大小相关的设置值。 private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; + // 快捷方式图标标题的最大长度限制,用于生成发送到桌面的快捷方式时,对笔记内容截取合适长度作为图标标题。 private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; + // 表示复选框选中状态的字符,用于在笔记内容以特定格式表示复选框选中情况时使用,这里是一个Unicode字符表示对勾。 public static final String TAG_CHECKED = String.valueOf('\u221A'); + // 表示复选框未选中状态的字符,用于在笔记内容以特定格式表示复选框未选中情况时使用,这里是一个Unicode字符表示方块。 public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); + // 用于存储笔记内容以列表形式编辑时的多个EditText子项的线性布局容器,方便处理列表模式下的笔记内容展示和编辑。 private LinearLayout mEditTextList; + // 用户查询的字符串内容,可能用于搜索等相关功能,比如在笔记中查找特定内容,用于后续的文本高亮等处理。 private String mUserQuery; + // 用于匹配用户查询内容的正则表达式模式对象,根据用户查询字符串编译生成,用于在笔记文本中查找匹配的内容进行相应处理,如高亮显示。 private Pattern mPattern; + // Activity被创建时调用的方法,进行一些初始化操作,如设置布局、根据Intent初始化Activity状态等,如果初始化失败则结束该Activity。 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + // 设置该Activity对应的布局文件,用于展示笔记编辑相关的界面内容。 this.setContentView(R.layout.note_edit); - if (savedInstanceState == null && !initActivityState(getIntent())) { + if (savedInstanceState == null &&!initActivityState(getIntent())) { finish(); return; } @@ -162,13 +185,14 @@ public class NoteEditActivity extends Activity implements OnClickListener, } /** - * Current activity may be killed when the memory is low. Once it is killed, for another time - * user load this activity, we should restore the former state + * 当内存不足导致当前Activity被系统销毁后重新创建时,此方法会被调用,用于恢复之前的Activity状态, + * 如果传入的保存状态中包含特定的额外数据(通过Intent.EXTRA_UID判断),则尝试重新初始化Activity状态, + * 若初始化失败同样结束该Activity,并在日志中记录恢复相关的信息。 */ @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); - if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { + if (savedInstanceState!= null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { Intent intent = new Intent(Intent.ACTION_VIEW); intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); if (!initActivityState(intent)) { @@ -179,10 +203,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, } } + // 根据传入的Intent初始化Activity的状态,根据不同的Intent动作(ACTION_VIEW、ACTION_INSERT_OR_EDIT等)来处理不同的情况, + // 例如加载已存在的笔记、创建新笔记、处理通话记录相关笔记等,若操作过程中出现加载笔记失败等问题则结束该Activity,成功则返回true。 private boolean initActivityState(Intent intent) { /** - * If the user specified the {@link Intent#ACTION_VIEW} but not provided with id, - * then jump to the NotesListActivity + * 如果用户指定了{@link Intent#ACTION_VIEW}动作但未提供笔记ID, + * 则跳转到NotesListActivity展示笔记列表页面。 */ mWorkingNote = null; if (TextUtils.equals(Intent.ACTION_VIEW, intent.getAction())) { @@ -190,7 +216,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, mUserQuery = ""; /** - * Starting from the searched result + * 从搜索结果中启动的情况,获取搜索相关的额外数据作为笔记ID,并获取用户查询字符串。 */ if (intent.hasExtra(SearchManager.EXTRA_DATA_KEY)) { noteId = Long.parseLong(intent.getStringExtra(SearchManager.EXTRA_DATA_KEY)); @@ -211,11 +237,12 @@ 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())) { - // New note + } else if (TextUtils.equals(Intent.ACTION_INSERT_OR_EDIT, intent.getAction())) { + // 新建笔记的情况,获取相关的额外数据,如文件夹ID、小部件ID、小部件类型、背景资源ID等,用于创建新的空白笔记对象。 long folderId = intent.getLongExtra(Notes.INTENT_EXTRA_FOLDER_ID, 0); int widgetId = intent.getIntExtra(Notes.INTENT_EXTRA_WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); @@ -224,10 +251,10 @@ public class NoteEditActivity extends Activity implements OnClickListener, int bgResId = intent.getIntExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, ResourceParser.getDefaultBgId(this)); - // Parse call-record note + // 解析通话记录笔记相关的情况,获取电话号码和通话日期,根据是否存在有效数据来决定是加载已存在的通话笔记还是创建新的空白通话笔记并转换为通话笔记格式。 String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); long callDate = intent.getLongExtra(Notes.INTENT_EXTRA_CALL_DATE, 0); - if (callDate != 0 && phoneNumber != null) { + if (callDate!= 0 && phoneNumber!= null) { if (TextUtils.isEmpty(phoneNumber)) { Log.w(TAG, "The call record number is null"); } @@ -250,6 +277,7 @@ public class NoteEditActivity extends Activity implements OnClickListener, bgResId); } + // 设置软键盘的显示模式,初始时显示软键盘,并根据内容调整布局大小。 getWindow().setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE | WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); @@ -258,16 +286,20 @@ public class NoteEditActivity extends Activity implements OnClickListener, finish(); return false; } + // 设置笔记相关设置状态变化的监听器为当前Activity自身,以便在笔记设置发生改变时能响应处理。 mWorkingNote.setOnSettingStatusChangedListener(this); return true; } + // Activity重新恢复到前台可见时调用的方法,在此主要用于初始化笔记编辑界面的显示内容,如设置字体样式、根据笔记类型展示内容、设置头部和编辑区域背景等。 @Override protected void onResume() { super.onResume(); initNoteScreen(); } + // 初始化笔记编辑界面的显示内容,根据当前笔记的相关属性和设置,设置笔记编辑文本的字体样式、展示模式(列表模式或普通模式)、 + // 显示或隐藏提醒相关的头部信息、设置头部和编辑区域的背景等,确保界面正确展示当前笔记的状态。 private void initNoteScreen() { mNoteEditor.setTextAppearance(this, TextAppearanceResources .getTexAppearanceResource(mFontSizeId)); @@ -289,12 +321,12 @@ public class NoteEditActivity extends Activity implements OnClickListener, | DateUtils.FORMAT_SHOW_YEAR)); /** - * TODO: Add the menu for setting alert. Currently disable it because the DateTimePicker - * is not ready + * TODO: 添加设置提醒的菜单选项。目前暂时禁用,因为DateTimePicker相关功能尚未准备好 */ showAlertHeader(); } + // 根据笔记是否设置了提醒以及提醒时间与当前时间的关系,显示或隐藏提醒相关的头部信息,如提醒日期文本和提醒图标等。 private void showAlertHeader() { if (mWorkingNote.hasClockAlert()) { long time = System.currentTimeMillis(); @@ -308,566 +340,5 @@ public class NoteEditActivity extends Activity implements OnClickListener, mNoteHeaderHolder.ivAlertIcon.setVisibility(View.VISIBLE); } else { mNoteHeaderHolder.tvAlertDate.setVisibility(View.GONE); - mNoteHeaderHolder.ivAlertIcon.setVisibility(View.GONE); - }; - } - - @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - initActivityState(intent); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - /** - * For new note without note id, we should firstly save it to - * generate a id. If the editing note is not worth saving, there - * is no id which is equivalent to create new note - */ - if (!mWorkingNote.existInDatabase()) { - saveNote(); - } - outState.putLong(Intent.EXTRA_UID, mWorkingNote.getNoteId()); - Log.d(TAG, "Save working note id: " + mWorkingNote.getNoteId() + " onSaveInstanceState"); - } - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - if (mNoteBgColorSelector.getVisibility() == View.VISIBLE - && !inRangeOfView(mNoteBgColorSelector, ev)) { - mNoteBgColorSelector.setVisibility(View.GONE); - return true; - } - - if (mFontSizeSelector.getVisibility() == View.VISIBLE - && !inRangeOfView(mFontSizeSelector, ev)) { - mFontSizeSelector.setVisibility(View.GONE); - return true; - } - return super.dispatchTouchEvent(ev); - } + mNoteHeaderHolder.ivAlert - private boolean inRangeOfView(View view, MotionEvent ev) { - int []location = new int[2]; - view.getLocationOnScreen(location); - int x = location[0]; - int y = location[1]; - if (ev.getX() < x - || ev.getX() > (x + view.getWidth()) - || ev.getY() < y - || ev.getY() > (y + view.getHeight())) { - return false; - } - return true; - } - - private void initResources() { - mHeadViewPanel = findViewById(R.id.note_title); - mNoteHeaderHolder = new HeadViewHolder(); - mNoteHeaderHolder.tvModified = (TextView) findViewById(R.id.tv_modified_date); - mNoteHeaderHolder.ivAlertIcon = (ImageView) findViewById(R.id.iv_alert_icon); - mNoteHeaderHolder.tvAlertDate = (TextView) findViewById(R.id.tv_alert_date); - mNoteHeaderHolder.ibSetBgColor = (ImageView) findViewById(R.id.btn_set_bg_color); - mNoteHeaderHolder.ibSetBgColor.setOnClickListener(this); - mNoteEditor = (EditText) findViewById(R.id.note_edit_view); - mNoteEditorPanel = findViewById(R.id.sv_note_edit); - mNoteBgColorSelector = findViewById(R.id.note_bg_color_selector); - for (int id : sBgSelectorBtnsMap.keySet()) { - ImageView iv = (ImageView) findViewById(id); - iv.setOnClickListener(this); - } - - mFontSizeSelector = findViewById(R.id.font_size_selector); - for (int id : sFontSizeBtnsMap.keySet()) { - View view = findViewById(id); - view.setOnClickListener(this); - }; - mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); - mFontSizeId = mSharedPrefs.getInt(PREFERENCE_FONT_SIZE, ResourceParser.BG_DEFAULT_FONT_SIZE); - /** - * HACKME: Fix bug of store the resource id in shared preference. - * The id may larger than the length of resources, in this case, - * return the {@link ResourceParser#BG_DEFAULT_FONT_SIZE} - */ - if(mFontSizeId >= TextAppearanceResources.getResourcesSize()) { - mFontSizeId = ResourceParser.BG_DEFAULT_FONT_SIZE; - } - mEditTextList = (LinearLayout) findViewById(R.id.note_edit_list); - } - - @Override - protected void onPause() { - super.onPause(); - if(saveNote()) { - Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); - } - clearSettingState(); - } - - private void updateWidget() { - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_2X) { - intent.setClass(this, NoteWidgetProvider_2x.class); - } else if (mWorkingNote.getWidgetType() == Notes.TYPE_WIDGET_4X) { - intent.setClass(this, NoteWidgetProvider_4x.class); - } else { - Log.e(TAG, "Unspported widget type"); - return; - } - - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { - mWorkingNote.getWidgetId() - }); - - sendBroadcast(intent); - setResult(RESULT_OK, intent); - } - - public void onClick(View v) { - int id = v.getId(); - if (id == R.id.btn_set_bg_color) { - mNoteBgColorSelector.setVisibility(View.VISIBLE); - findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( - - View.VISIBLE); - } else if (sBgSelectorBtnsMap.containsKey(id)) { - findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( - View.GONE); - mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); - mNoteBgColorSelector.setVisibility(View.GONE); - } else if (sFontSizeBtnsMap.containsKey(id)) { - findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE); - mFontSizeId = sFontSizeBtnsMap.get(id); - mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit(); - findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); - if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { - getWorkingText(); - switchToListMode(mWorkingNote.getContent()); - } else { - mNoteEditor.setTextAppearance(this, - TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); - } - mFontSizeSelector.setVisibility(View.GONE); - } - } - - @Override - public void onBackPressed() { - if(clearSettingState()) { - return; - } - - saveNote(); - super.onBackPressed(); - } - - private boolean clearSettingState() { - if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { - mNoteBgColorSelector.setVisibility(View.GONE); - return true; - } else if (mFontSizeSelector.getVisibility() == View.VISIBLE) { - mFontSizeSelector.setVisibility(View.GONE); - return true; - } - return false; - } - - public void onBackgroundColorChanged() { - findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility( - View.VISIBLE); - mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); - mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); - } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - if (isFinishing()) { - return true; - } - clearSettingState(); - menu.clear(); - if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { - getMenuInflater().inflate(R.menu.call_note_edit, menu); - } else { - getMenuInflater().inflate(R.menu.note_edit, menu); - } - if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { - menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode); - } else { - menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode); - } - if (mWorkingNote.hasClockAlert()) { - menu.findItem(R.id.menu_alert).setVisible(false); - } else { - menu.findItem(R.id.menu_delete_remind).setVisible(false); - } - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_new_note: - createNewNote(); - break; - case R.id.menu_delete: - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.alert_title_delete)); - builder.setIcon(android.R.drawable.ic_dialog_alert); - builder.setMessage(getString(R.string.alert_message_delete_note)); - builder.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - deleteCurrentNote(); - finish(); - } - }); - builder.setNegativeButton(android.R.string.cancel, null); - builder.show(); - break; - case R.id.menu_font_size: - mFontSizeSelector.setVisibility(View.VISIBLE); - findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); - break; - case R.id.menu_list_mode: - mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ? - TextNote.MODE_CHECK_LIST : 0); - break; - case R.id.menu_share: - getWorkingText(); - sendTo(this, mWorkingNote.getContent()); - break; - case R.id.menu_send_to_desktop: - sendToDesktop(); - break; - case R.id.menu_alert: - setReminder(); - break; - case R.id.menu_delete_remind: - mWorkingNote.setAlertDate(0, false); - break; - default: - break; - } - return true; - } - - private void setReminder() { - DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); - d.setOnDateTimeSetListener(new OnDateTimeSetListener() { - public void OnDateTimeSet(AlertDialog dialog, long date) { - mWorkingNote.setAlertDate(date , true); - } - }); - d.show(); - } - - /** - * Share note to apps that support {@link Intent#ACTION_SEND} action - * and {@text/plain} type - */ - private void sendTo(Context context, String info) { - Intent intent = new Intent(Intent.ACTION_SEND); - intent.putExtra(Intent.EXTRA_TEXT, info); - intent.setType("text/plain"); - context.startActivity(intent); - } - - private void createNewNote() { - // Firstly, save current editing notes - saveNote(); - - // For safety, start a new NoteEditActivity - finish(); - Intent intent = new Intent(this, NoteEditActivity.class); - intent.setAction(Intent.ACTION_INSERT_OR_EDIT); - intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId()); - startActivity(intent); - } - - private void deleteCurrentNote() { - if (mWorkingNote.existInDatabase()) { - HashSet ids = new HashSet(); - long id = mWorkingNote.getNoteId(); - if (id != Notes.ID_ROOT_FOLDER) { - ids.add(id); - } else { - Log.d(TAG, "Wrong note id, should not happen"); - } - if (!isSyncMode()) { - if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) { - Log.e(TAG, "Delete Note error"); - } - } else { - if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) { - Log.e(TAG, "Move notes to trash folder error, should not happens"); - } - } - } - mWorkingNote.markDeleted(true); - } - - private boolean isSyncMode() { - return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; - } - - public void onClockAlertChanged(long date, boolean set) { - /** - * User could set clock to an unsaved note, so before setting the - * alert clock, we should save the note first - */ - if (!mWorkingNote.existInDatabase()) { - saveNote(); - } - if (mWorkingNote.getNoteId() > 0) { - Intent intent = new Intent(this, AlarmReceiver.class); - intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId())); - PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); - AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE)); - showAlertHeader(); - if(!set) { - alarmManager.cancel(pendingIntent); - } else { - alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent); - } - } else { - /** - * There is the condition that user has input nothing (the note is - * not worthy saving), we have no note id, remind the user that he - * should input something - */ - Log.e(TAG, "Clock alert setting error"); - showToast(R.string.error_note_empty_for_clock); - } - } - - public void onWidgetChanged() { - updateWidget(); - } - - public void onEditTextDelete(int index, String text) { - int childCount = mEditTextList.getChildCount(); - if (childCount == 1) { - return; - } - - for (int i = index + 1; i < childCount; i++) { - ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) - .setIndex(i - 1); - } - - mEditTextList.removeViewAt(index); - NoteEditText edit = null; - if(index == 0) { - edit = (NoteEditText) mEditTextList.getChildAt(0).findViewById( - R.id.et_edit_text); - } else { - edit = (NoteEditText) mEditTextList.getChildAt(index - 1).findViewById( - R.id.et_edit_text); - } - int length = edit.length(); - edit.append(text); - edit.requestFocus(); - edit.setSelection(length); - } - - public void onEditTextEnter(int index, String text) { - /** - * Should not happen, check for debug - */ - if(index > mEditTextList.getChildCount()) { - Log.e(TAG, "Index out of mEditTextList boundrary, should not happen"); - } - - View view = getListItem(text, index); - mEditTextList.addView(view, index); - NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); - edit.requestFocus(); - edit.setSelection(0); - for (int i = index + 1; i < mEditTextList.getChildCount(); i++) { - ((NoteEditText) mEditTextList.getChildAt(i).findViewById(R.id.et_edit_text)) - .setIndex(i); - } - } - - private void switchToListMode(String text) { - mEditTextList.removeAllViews(); - String[] items = text.split("\n"); - int index = 0; - for (String item : items) { - if(!TextUtils.isEmpty(item)) { - mEditTextList.addView(getListItem(item, index)); - index++; - } - } - mEditTextList.addView(getListItem("", index)); - mEditTextList.getChildAt(index).findViewById(R.id.et_edit_text).requestFocus(); - - mNoteEditor.setVisibility(View.GONE); - mEditTextList.setVisibility(View.VISIBLE); - } - - private Spannable getHighlightQueryResult(String fullText, String userQuery) { - SpannableString spannable = new SpannableString(fullText == null ? "" : fullText); - if (!TextUtils.isEmpty(userQuery)) { - mPattern = Pattern.compile(userQuery); - Matcher m = mPattern.matcher(fullText); - int start = 0; - while (m.find(start)) { - spannable.setSpan( - new BackgroundColorSpan(this.getResources().getColor( - R.color.user_query_highlight)), m.start(), m.end(), - Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - start = m.end(); - } - } - return spannable; - } - - private View getListItem(String item, int index) { - View view = LayoutInflater.from(this).inflate(R.layout.note_edit_list_item, null); - final NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); - edit.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); - CheckBox cb = ((CheckBox) view.findViewById(R.id.cb_edit_item)); - cb.setOnCheckedChangeListener(new OnCheckedChangeListener() { - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isChecked) { - edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); - } else { - edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); - } - } - }); - - if (item.startsWith(TAG_CHECKED)) { - cb.setChecked(true); - edit.setPaintFlags(edit.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG); - item = item.substring(TAG_CHECKED.length(), item.length()).trim(); - } else if (item.startsWith(TAG_UNCHECKED)) { - cb.setChecked(false); - edit.setPaintFlags(Paint.ANTI_ALIAS_FLAG | Paint.DEV_KERN_TEXT_FLAG); - item = item.substring(TAG_UNCHECKED.length(), item.length()).trim(); - } - - edit.setOnTextViewChangeListener(this); - edit.setIndex(index); - edit.setText(getHighlightQueryResult(item, mUserQuery)); - return view; - } - - public void onTextChange(int index, boolean hasText) { - if (index >= mEditTextList.getChildCount()) { - Log.e(TAG, "Wrong index, should not happen"); - return; - } - if(hasText) { - mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.VISIBLE); - } else { - mEditTextList.getChildAt(index).findViewById(R.id.cb_edit_item).setVisibility(View.GONE); - } - } - - public void onCheckListModeChanged(int oldMode, int newMode) { - if (newMode == TextNote.MODE_CHECK_LIST) { - switchToListMode(mNoteEditor.getText().toString()); - } else { - if (!getWorkingText()) { - mWorkingNote.setWorkingText(mWorkingNote.getContent().replace(TAG_UNCHECKED + " ", - "")); - } - mNoteEditor.setText(getHighlightQueryResult(mWorkingNote.getContent(), mUserQuery)); - mEditTextList.setVisibility(View.GONE); - mNoteEditor.setVisibility(View.VISIBLE); - } - } - - private boolean getWorkingText() { - boolean hasChecked = false; - if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < mEditTextList.getChildCount(); i++) { - View view = mEditTextList.getChildAt(i); - NoteEditText edit = (NoteEditText) view.findViewById(R.id.et_edit_text); - if (!TextUtils.isEmpty(edit.getText())) { - if (((CheckBox) view.findViewById(R.id.cb_edit_item)).isChecked()) { - sb.append(TAG_CHECKED).append(" ").append(edit.getText()).append("\n"); - hasChecked = true; - } else { - sb.append(TAG_UNCHECKED).append(" ").append(edit.getText()).append("\n"); - } - } - } - mWorkingNote.setWorkingText(sb.toString()); - } else { - mWorkingNote.setWorkingText(mNoteEditor.getText().toString()); - } - return hasChecked; - } - - private boolean saveNote() { - getWorkingText(); - boolean saved = mWorkingNote.saveNote(); - if (saved) { - /** - * There are two modes from List view to edit view, open one note, - * create/edit a node. Opening node requires to the original - * position in the list when back from edit view, while creating a - * new node requires to the top of the list. This code - * {@link #RESULT_OK} is used to identify the create/edit state - */ - setResult(RESULT_OK); - } - return saved; - } - - private void sendToDesktop() { - /** - * Before send message to home, we should make sure that current - * editing note is exists in databases. So, for new note, firstly - * save it - */ - if (!mWorkingNote.existInDatabase()) { - saveNote(); - } - - if (mWorkingNote.getNoteId() > 0) { - Intent sender = new Intent(); - Intent shortcutIntent = new Intent(this, NoteEditActivity.class); - shortcutIntent.setAction(Intent.ACTION_VIEW); - shortcutIntent.putExtra(Intent.EXTRA_UID, mWorkingNote.getNoteId()); - sender.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); - sender.putExtra(Intent.EXTRA_SHORTCUT_NAME, - makeShortcutIconTitle(mWorkingNote.getContent())); - sender.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, - Intent.ShortcutIconResource.fromContext(this, R.drawable.icon_app)); - sender.putExtra("duplicate", true); - sender.setAction("com.android.launcher.action.INSTALL_SHORTCUT"); - showToast(R.string.info_note_enter_desktop); - sendBroadcast(sender); - } else { - /** - * There is the condition that user has input nothing (the note is - * not worthy saving), we have no note id, remind the user that he - * should input something - */ - Log.e(TAG, "Send to desktop error"); - showToast(R.string.error_note_empty_for_send_to_desktop); - } - } - - private String makeShortcutIconTitle(String content) { - content = content.replace(TAG_CHECKED, ""); - content = content.replace(TAG_UNCHECKED, ""); - return content.length() > SHORTCUT_ICON_TITLE_MAX_LEN ? content.substring(0, - SHORTCUT_ICON_TITLE_MAX_LEN) : content; - } - - private void showToast(int resId) { - showToast(resId, Toast.LENGTH_SHORT); - } - - private void showToast(int resId, int duration) { - Toast.makeText(this, resId, duration).show(); - } -} 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..e39a2a6 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NoteEditText.java +++ b/src/Notes-master/src/net/micode/notes/ui/NoteEditText.java @@ -37,15 +37,25 @@ import net.micode.notes.R; import java.util.HashMap; import java.util.Map; +// NoteEditText类继承自EditText,是一个自定义的文本编辑框,在基础的文本编辑功能上,添加了一些特定的交互逻辑, +// 比如处理按键事件(回车键、删除键)、焦点变化事件以及创建上下文菜单(处理链接点击等情况),并通过接口与外部进行交互,通知相关的文本变化情况等。 public class NoteEditText extends EditText { + + // 用于日志记录的标签,方便在调试等场景下识别该类相关的日志输出。 private static final String TAG = "NoteEditText"; + // 当前文本编辑框在父容器(如列表布局中多个编辑框的情况)中的索引位置,用于标识自身顺序以及在一些操作中确定与其他编辑框的关系。 private int mIndex; + // 在处理删除键按下事件时,记录删除操作前文本的起始选择位置,用于后续判断是否触发特定的删除逻辑,比如删除当前编辑框内容等情况。 private int mSelectionStartBeforeDelete; - private static final String SCHEME_TEL = "tel:" ; - private static final String SCHEME_HTTP = "http:" ; - private static final String SCHEME_EMAIL = "mailto:" ; + // 定义表示电话号码链接的协议头字符串,用于识别文本中电话号码类型的链接,格式为"tel:"。 + private static final String SCHEME_TEL = "tel:"; + // 定义表示网页链接的协议头字符串,用于识别文本中网页类型的链接,格式为"http:"。 + private static final String SCHEME_HTTP = "http:"; + // 定义表示邮件链接的协议头字符串,用于识别文本中邮件类型的链接,格式为"mailto:"。 + private static final String SCHEME_EMAIL = "mailto:"; + // 用于建立链接协议头与对应提示字符串资源ID的映射关系的集合,方便根据链接类型获取相应的显示文本,告知用户链接类型。 private static final Map sSchemaActionResMap = new HashMap(); static { sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); @@ -53,6 +63,8 @@ public class NoteEditText extends EditText { sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); } + // 定义一个接口,用于外部类实现,该接口提供了几个方法,当文本编辑框内发生特定文本操作事件(如删除、回车键按下、文本内容变化等)时, + // 会回调相应的方法,以便外部可以根据这些事件做出相应的处理,实现与外部的交互逻辑。 /** * Call by the {@link NoteEditActivity} to delete or add edit text */ @@ -75,35 +87,45 @@ public class NoteEditText extends EditText { void onTextChange(int index, boolean hasText); } + // 用于存储实现了OnTextViewChangeListener接口的对象实例,通过设置此监听器,使得外部可以接收该文本编辑框内发生相关文本操作事件的通知,进行相应处理。 private OnTextViewChangeListener mOnTextViewChangeListener; + // 构造函数,接收上下文作为参数,调用父类(EditText)的构造函数进行初始化,同时设置当前编辑框的索引初始值为0。 public NoteEditText(Context context) { super(context, null); mIndex = 0; } + // 用于设置当前文本编辑框在父容器中的索引位置的方法,外部可通过调用此方法来更新索引值,方便后续基于索引的相关操作逻辑。 public void setIndex(int index) { mIndex = index; } + // 用于设置文本操作事件监听器(OnTextViewChangeListener)的方法,外部类实现该接口后,通过调用此方法将实现的监听器对象传递进来, + // 使得该文本编辑框能在相应事件发生时通知外部进行处理。 public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { mOnTextViewChangeListener = listener; } + // 构造函数,接收上下文和属性集(AttributeSet)作为参数,调用父类(EditText)的构造函数并指定默认的编辑框样式(android.R.attr.editTextStyle)进行初始化。 public NoteEditText(Context context, AttributeSet attrs) { super(context, attrs, android.R.attr.editTextStyle); } + // 构造函数,接收上下文、属性集(AttributeSet)和默认样式(defStyle)作为参数,调用父类(EditText)的构造函数进行初始化, + // 这里暂时没有额外的自定义初始化逻辑(除了调用父类构造函数),只是保留了构造函数的标准写法,注释中提示了待完善的部分(可能后续会添加更多初始化相关操作)。 public NoteEditText(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // TODO Auto-generated constructor stub } + // 重写父类的onTouchEvent方法,用于处理触摸事件,主要在触摸按下(ACTION_DOWN)时,根据触摸位置来设置文本的选择位置, + // 使得用户点击文本区域时能准确地定位到相应位置进行后续操作,如复制、粘贴等文本选择相关操作,其他触摸动作则按照父类默认逻辑处理。 @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: - + // 获取触摸点的原始X坐标,并减去文本编辑框的左内边距,得到相对文本内容区域的X坐标,再加上滚动的X偏移量(处理滚动情况)。 int x = (int) event.getX(); int y = (int) event.getY(); x -= getTotalPaddingLeft(); @@ -111,6 +133,8 @@ public class NoteEditText extends EditText { x += getScrollX(); y += getScrollY(); + // 根据触摸点的垂直位置获取对应的文本行数,然后根据该行数和水平位置获取对应的文本偏移量(字符位置), + // 最后将文本选择位置设置为该偏移量对应的位置,实现触摸定位文本选择的功能。 Layout layout = getLayout(); int line = layout.getLineForVertical(y); int off = layout.getOffsetForHorizontal(line, x); @@ -121,11 +145,14 @@ public class NoteEditText extends EditText { return super.onTouchEvent(event); } + // 重写父类的onKeyDown方法,用于处理按键按下事件,在这里主要是记录删除键(KEYCODE_DEL)按下时文本的起始选择位置, + // 对于回车键(KEYCODE_ENTER)按下的情况,如果设置了文本操作事件监听器(OnTextViewChangeListener),则直接返回false, + // 可能是让外部来进一步处理回车键按下的逻辑,其他按键则按照父类默认逻辑处理。 @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_ENTER: - if (mOnTextViewChangeListener != null) { + if (mOnTextViewChangeListener!= null) { return false; } break; @@ -138,12 +165,17 @@ public class NoteEditText extends EditText { return super.onKeyDown(keyCode, event); } + // 重写父类的onKeyUp方法,用于处理按键抬起事件,在这里重点处理删除键(KEYCODE_DEL)和回车键(KEYCODE_ENTER)抬起时的逻辑, + // 如果设置了文本操作事件监听器(OnTextViewChangeListener),对于删除键抬起情况,当起始选择位置为0且当前编辑框不是第一个(索引不为0)时, + // 调用监听器的onEditTextDelete方法通知外部删除当前编辑框内容;对于回车键抬起情况,获取当前选择位置后的文本内容, + // 然后将文本编辑框内容更新为选择位置前的部分,并调用监听器的onEditTextEnter方法通知外部在当前编辑框后添加新的编辑框及相应文本内容, + // 其他按键则按照父类默认逻辑处理。 @Override public boolean onKeyUp(int keyCode, KeyEvent event) { - switch(keyCode) { + switch (keyCode) { case KeyEvent.KEYCODE_DEL: - if (mOnTextViewChangeListener != null) { - if (0 == mSelectionStartBeforeDelete && mIndex != 0) { + if (mOnTextViewChangeListener!= null) { + if (0 == mSelectionStartBeforeDelete && mIndex!= 0) { mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); return true; } @@ -152,7 +184,7 @@ public class NoteEditText extends EditText { } break; case KeyEvent.KEYCODE_ENTER: - if (mOnTextViewChangeListener != null) { + if (mOnTextViewChangeListener!= null) { int selectionStart = getSelectionStart(); String text = getText().subSequence(selectionStart, length()).toString(); setText(getText().subSequence(0, selectionStart)); @@ -167,9 +199,12 @@ public class NoteEditText extends EditText { return super.onKeyUp(keyCode, event); } + // 重写父类的onFocusChanged方法,用于处理焦点变化事件,当焦点改变时,如果设置了文本操作事件监听器(OnTextViewChangeListener), + // 根据当前是否获取焦点以及文本内容是否为空,调用监听器的onTextChange方法通知外部相应的文本显示相关选项(如是否隐藏、显示某些操作按钮等)的变化情况, + // 然后再按照父类默认逻辑处理焦点变化的其他相关操作。 @Override protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { - if (mOnTextViewChangeListener != null) { + if (mOnTextViewChangeListener!= null) { if (!focused && TextUtils.isEmpty(getText())) { mOnTextViewChangeListener.onTextChange(mIndex, false); } else { @@ -179,6 +214,10 @@ public class NoteEditText extends EditText { super.onFocusChanged(focused, direction, previouslyFocusedRect); } + // 重写父类的onCreateContextMenu方法,用于创建上下文菜单,当文本被长按选中时弹出的菜单内容相关逻辑在此处理。 + // 如果文本内容是Spanned类型(包含富文本等情况,如包含链接),获取选中的文本范围,查找其中的URLSpan(表示链接的一种形式), + // 如果只找到一个链接,根据链接的协议头(如"tel:"、"http:"、"mailto:"等)获取对应的提示字符串资源ID,若找不到对应协议头则使用默认的资源ID, + // 然后添加一个菜单项到上下文菜单中,点击该菜单项会触发相应的链接跳转逻辑(通过调用URLSpan的onClick方法),最后再按照父类默认逻辑处理菜单创建的其他相关操作。 @Override protected void onCreateContextMenu(ContextMenu menu) { if (getText() instanceof Spanned) { @@ -191,8 +230,8 @@ public class NoteEditText extends EditText { final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); if (urls.length == 1) { int defaultResId = 0; - for(String schema: sSchemaActionResMap.keySet()) { - if(urls[0].getURL().indexOf(schema) >= 0) { + for (String schema : sSchemaActionResMap.keySet()) { + if (urls[0].getURL().indexOf(schema) >= 0) { defaultResId = sSchemaActionResMap.get(schema); break; } @@ -214,4 +253,4 @@ public class NoteEditText extends EditText { } super.onCreateContextMenu(menu); } -} +} \ No newline at end of file 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..ca0771f 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java +++ b/src/Notes-master/src/net/micode/notes/ui/NoteItemData.java @@ -26,76 +26,128 @@ 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, - NoteColumns.BG_COLOR_ID, - NoteColumns.CREATED_DATE, - NoteColumns.HAS_ATTACHMENT, - NoteColumns.MODIFIED_DATE, - NoteColumns.NOTES_COUNT, - NoteColumns.PARENT_ID, - NoteColumns.SNIPPET, - NoteColumns.TYPE, - NoteColumns.WIDGET_ID, - NoteColumns.WIDGET_TYPE, + NoteColumns.ID, + NoteColumns.ALERTED_DATE, + NoteColumns.BG_COLOR_ID, + NoteColumns.CREATED_DATE, + NoteColumns.HAS_ATTACHMENT, + NoteColumns.MODIFIED_DATE, + NoteColumns.NOTES_COUNT, + NoteColumns.PARENT_ID, + NoteColumns.SNIPPET, + NoteColumns.TYPE, + NoteColumns.WIDGET_ID, + NoteColumns.WIDGET_TYPE, }; + // 定义常量,表示查询结果中笔记ID所在列的索引,值为0,方便后续从游标中准确获取对应数据,这里命名为ID_COLUMN。 private static final int ID_COLUMN = 0; + // 定义常量,表示查询结果中提醒日期所在列的索引,值为1,用于后续获取提醒日期数据,命名为ALERTED_DATE_COLUMN。 private static final int ALERTED_DATE_COLUMN = 1; + // 定义常量,表示查询结果中背景颜色ID所在列的索引,值为2,便于获取背景颜色相关设置信息,命名为BG_COLOR_ID_COLUMN。 private static final int BG_COLOR_ID_COLUMN = 2; + // 定义常量,表示查询结果中创建日期所在列的索引,值为3,用于获取笔记创建时间数据,命名为CREATED_DATE_COLUMN。 private static final int CREATED_DATE_COLUMN = 3; + // 定义常量,表示查询结果中是否有附件所在列的索引,值为4,通过判断该列的值来确定笔记是否包含附件,命名为HAS_ATTACHMENT_COLUMN。 private static final int HAS_ATTACHMENT_COLUMN = 4; + // 定义常量,表示查询结果中修改日期所在列的索引,值为5,用于获取笔记最后修改时间信息,命名为MODIFIED_DATE_COLUMN。 private static final int MODIFIED_DATE_COLUMN = 5; + // 定义常量,表示查询结果中笔记数量所在列的索引,值为6,可能用于某些与笔记数量相关的业务逻辑判断,命名为NOTES_COUNT_COLUMN。 private static final int NOTES_COUNT_COLUMN = 6; + // 定义常量,表示查询结果中父文件夹ID所在列的索引,值为7,可通过该值确定笔记所属的父文件夹,命名为PARENT_ID_COLUMN。 private static final int PARENT_ID_COLUMN = 7; + // 定义常量,表示查询结果中摘要(片段)信息所在列的索引,值为8,通常用于展示笔记的简短内容描述,命名为SNIPPET_COLUMN。 private static final int SNIPPET_COLUMN = 8; + // 定义常量,表示查询结果中笔记类型所在列的索引,值为9,用于判断笔记是普通笔记、文件夹还是其他类型,命名为TYPE_COLUMN。 private static final int TYPE_COLUMN = 9; + // 定义常量,表示查询结果中小部件ID所在列的索引,值为10,可能涉及与桌面小部件相关的业务逻辑,命名为WIDGET_ID_COLUMN。 private static final int WIDGET_ID_COLUMN = 10; + // 定义常量,表示查询结果中小部件类型所在列的索引,值为11,同样用于小部件相关的类型判断等操作,命名为WIDGET_TYPE_COLUMN。 private static final int WIDGET_TYPE_COLUMN = 11; + // 笔记的唯一标识符,存储从游标中获取的笔记ID数据,用于区分不同的笔记记录。 private long mId; + // 笔记设置的提醒日期,以时间戳的形式存储,通过游标获取相应列的数据,可用于判断笔记是否有提醒以及提醒时间相关的业务逻辑。 private long mAlertDate; + // 笔记的背景颜色ID,用于确定笔记在展示时的背景颜色设置,从游标对应列获取该值,便于后续界面显示相关的操作。 private int mBgColorId; + // 笔记的创建日期,以时间戳形式保存,通过游标获取此数据后可用于展示笔记创建时间等功能需求。 private long mCreatedDate; + // 用于标记笔记是否包含附件,根据游标中对应列的值(大于0表示有附件)进行设置,方便在业务逻辑中判断是否需要处理附件相关操作。 private boolean mHasAttachment; + // 笔记的最后修改日期,同样以时间戳形式存储,可用于展示笔记的更新情况以及相关业务逻辑判断,比如判断笔记是否被修改等。 private long mModifiedDate; + // 可能表示与笔记相关的数量信息(具体含义取决于业务场景),从游标对应列获取该整数值,用于相应的业务逻辑处理。 private int mNotesCount; + // 笔记所属的父文件夹ID,通过游标获取该值可以确定笔记在文件夹层级结构中的位置关系,方便进行分类、查找等操作。 private long mParentId; + // 笔记的摘要(片段)内容,从游标获取后存储,通常是笔记内容的简短描述,用于在列表等场景展示给用户一个大致的内容提示, + // 并且在这里会去除一些特定的标记字符(如复选框相关的标记)进行规范化处理。 private String mSnippet; + // 笔记的类型,通过游标获取对应列的整数值,用于区分是普通笔记、文件夹还是其他特定类型的记录,以便执行不同的业务逻辑。 private int mType; + // 与笔记关联的小部件ID,可能用于桌面小部件相关的业务逻辑,比如根据小部件ID来更新对应的小部件显示内容等操作。 private int mWidgetId; + // 笔记关联的小部件类型,同样用于小部件相关的业务逻辑判断和处理,例如不同类型小部件展示方式可能不同等情况。 private int mWidgetType; + // 用于存储通话记录相关的联系人姓名,如果笔记属于通话记录文件夹,则尝试获取对应的联系人姓名,若获取失败则使用电话号码作为替代。 private String mName; + // 用于存储通话记录相关的电话号码,当笔记属于通话记录文件夹时,通过工具方法获取对应的电话号码信息,用于通话记录相关的业务逻辑处理。 private String mPhoneNumber; + // 标记当前笔记是否是列表中的最后一项,通过游标的相关方法判断并设置,方便在列表展示等场景中进行相应的界面处理或业务逻辑判断。 private boolean mIsLastItem; + // 标记当前笔记是否是列表中的第一项,同样依据游标状态进行判断和设置,可用于列表相关的展示逻辑以及操作逻辑区分。 private boolean mIsFirstItem; + // 标记当前笔记所在的列表是否只包含这一项,通过判断游标获取的总记录数是否为1来确定,用于一些特殊业务场景下的逻辑判断。 private boolean mIsOnlyOneItem; + // 标记当前笔记是否是某个文件夹下唯一的一条笔记(后续跟着文件夹的情况),通过特定的游标移动和类型判断逻辑来确定,用于相关业务逻辑处理。 private boolean mIsOneNoteFollowingFolder; + // 标记当前笔记是否是某个文件夹后跟着多条笔记中的一条(即所在文件夹下有多条笔记的情况),同样基于游标操作和类型判断逻辑来设置,用于对应业务场景的处理。 private boolean mIsMultiNotesFollowingFolder; + // 构造函数,接收上下文和数据库游标作为参数,用于从游标中获取各项笔记数据并进行初始化,同时调用checkPostion方法来判断笔记在列表中的相关位置状态属性。 public NoteItemData(Context context, Cursor cursor) { + // 从游标中获取笔记的ID并赋值给对应的成员变量,通过使用预定义的列索引常量(ID_COLUMN)来确保准确获取相应数据。 mId = cursor.getLong(ID_COLUMN); + // 从游标获取笔记的提醒日期并存储,使用ALERTED_DATE_COLUMN索引确保获取正确列的数据。 mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); + // 获取笔记的背景颜色ID,依据BG_COLOR_ID_COLUMN索引从游标中提取对应数据并赋值。 mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); + // 获取笔记的创建日期,按照CREATED_DATE_COLUMN索引从游标获取相应时间戳数据进行存储。 mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN); - mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false; + // 根据游标中对应列的值判断笔记是否有附件,大于0则设置为true,否则为false,使用HAS_ATTACHMENT_COLUMN索引来定位数据列。 + mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0)? true : false; + // 获取笔记的修改日期,通过MODIFIED_DATE_COLUMN索引从游标获取相应时间戳并赋值给成员变量。 mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN); + // 获取笔记相关的数量信息(具体含义取决于业务场景),利用NOTES_COUNT_COLUMN索引从游标中获取整数值存储起来。 mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); + // 获取笔记所属的父文件夹ID,借助PARENT_ID_COLUMN索引从游标获取对应数据赋值给成员变量,用于后续确定笔记的层级关系等操作。 mParentId = cursor.getLong(PARENT_ID_COLUMN); + // 获取笔记的摘要(片段)内容,通过SNIPPET_COLUMN索引从游标获取字符串数据,并去除特定的复选框相关标记字符进行规范化处理后存储。 mSnippet = cursor.getString(SNIPPET_COLUMN); mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace( NoteEditActivity.TAG_UNCHECKED, ""); + // 获取笔记的类型,根据TYPE_COLUMN索引从游标获取整数值来确定笔记是何种类型(如普通笔记、文件夹等),用于后续不同类型的业务逻辑处理。 mType = cursor.getInt(TYPE_COLUMN); + // 获取与笔记关联的小部件ID,使用WIDGET_ID_COLUMN索引从游标获取对应整数值存储起来,用于小部件相关业务逻辑。 mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); + // 获取笔记关联的小部件类型,依靠WIDGET_TYPE_COLUMN索引从游标获取相应整数值,便于针对不同小部件类型的处理操作。 mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); + // 初始化通话记录相关的电话号码为空字符串,后续根据笔记所属文件夹等情况判断是否需要获取实际电话号码。 mPhoneNumber = ""; if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { + // 如果笔记所属的父文件夹是通话记录文件夹,通过工具方法(DataUtils.getCallNumberByNoteId)尝试从内容解析器获取对应的电话号码信息。 mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); if (!TextUtils.isEmpty(mPhoneNumber)) { + // 如果获取到了电话号码,尝试通过联系人工具类(Contact)根据电话号码获取对应的联系人姓名,若获取失败则使用电话号码作为姓名显示。 mName = Contact.getContact(context, mPhoneNumber); if (mName == null) { mName = mPhoneNumber; @@ -109,14 +161,19 @@ public class NoteItemData { checkPostion(cursor); } + // 用于判断当前笔记在列表中的位置相关状态属性的私有方法,通过游标操作来判断是否是最后一项、第一项、是否唯一一项以及是否是某个文件夹后的笔记情况等, + // 并设置相应的成员变量标记,方便外部通过对应的访问方法获取这些状态信息进行业务逻辑处理。 private void checkPostion(Cursor cursor) { - mIsLastItem = cursor.isLast() ? true : false; - mIsFirstItem = cursor.isFirst() ? true : false; + // 判断游标是否指向最后一条记录,若是则将mIsLastItem设置为true,表示当前笔记是列表中的最后一项,否则为false。 + mIsLastItem = cursor.isLast()? true : false; + // 判断游标是否指向第一条记录,若为是则将mIsFirstItem设置为true,表明当前笔记是列表中的第一项,反之则为false。 + mIsFirstItem = cursor.isFirst()? true : false; + // 通过判断游标获取的记录总数是否为1来确定当前笔记所在的列表是否只有这一项,若是则将mIsOnlyOneItem设置为true。 mIsOnlyOneItem = (cursor.getCount() == 1); mIsMultiNotesFollowingFolder = false; mIsOneNoteFollowingFolder = false; - if (mType == Notes.TYPE_NOTE && !mIsFirstItem) { + if (mType == Notes.TYPE_NOTE &&!mIsFirstItem) { int position = cursor.getPosition(); if (cursor.moveToPrevious()) { if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER @@ -134,91 +191,70 @@ public class NoteItemData { } } + // 用于判断当前笔记是否是某个文件夹下唯一的一条笔记(后续跟着文件夹的情况)的方法,返回对应的成员变量值,外部可通过调用此方法获取该状态信息进行相应逻辑处理。 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)的方法,返回存储的笔记ID值,外部可通过调用此方法获取该笔记的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的方法,返回存储的背景颜色ID值,方便在界面展示等场景中根据该ID设置笔记的背景颜色相关操作。 public int getBgColorId() { return mBgColorId; } + // 用于获取笔记所属的父文件夹ID的方法,返回存储的父文件夹ID值,通过此方法可确定笔记在文件夹层级结构中的位置关系,方便进行分类、查找等操作。 public long getParentId() { return mParentId; } - public int getNotesCount() { - return mNotesCount; - } - - public long getFolderId () { - return mParentId; - } - - public int getType() { - return mType; - } - - public int getWidgetType() { - return mWidgetType; - } - - public int getWidgetId() { - return mWidgetId; - } - - public String getSnippet() { - return mSnippet; - } - - public boolean hasAlert() { - return (mAlertDate > 0); - } - - public boolean isCallRecord() { - return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); - } - - public static int getNoteType(Cursor cursor) { - return cursor.getInt(TYPE_COLUMN); - } -} + // 用于获取与笔记相关的数量信息(具体含义取决于业务场景)的方法,返回存储的整数值,用于相应的业务逻辑处理。 + public \ No newline at end of file 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..cccf0d2 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesListActivity.java @@ -79,96 +79,101 @@ import java.io.InputStreamReader; import java.util.HashSet; public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener { + // 用于标识文件夹笔记列表查询的标记 private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0; - + // 用于标识文件夹列表查询的标记 private static final int FOLDER_LIST_QUERY_TOKEN = 1; - + // 菜单中删除文件夹选项的ID private static final int MENU_FOLDER_DELETE = 0; - + // 菜单中查看文件夹选项的ID private static final int MENU_FOLDER_VIEW = 1; - + // 菜单中修改文件夹名称选项的ID private static final int MENU_FOLDER_CHANGE_NAME = 2; - + // 用于存储是否添加介绍信息的偏好设置键 private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction"; + // 定义列表编辑状态的枚举类型,包含笔记列表、子文件夹、通话记录文件夹三种状态 private enum ListEditState { NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER }; - + // 当前的列表编辑状态 private ListEditState mState; - + // 用于处理后台查询的异步查询处理器 private BackgroundQueryHandler mBackgroundQueryHandler; - + // 笔记列表的适配器,用于展示笔记数据 private NotesListAdapter mNotesListAdapter; - + // 显示笔记列表的ListView组件 private ListView mNotesListView; - + // “新建笔记”按钮 private Button mAddNewNote; - + // 用于判断事件是否需要分发的标志 private boolean mDispatch; - + // 触摸事件起始的Y坐标 private int mOriginY; - + // 用于分发触摸事件的目标Y坐标 private int mDispatchY; - + // 标题栏的TextView组件 private TextView mTitleBar; - + // 当前所在文件夹的ID private long mCurrentFolderId; - + // 用于与内容提供器交互,获取数据等操作 private ContentResolver mContentResolver; - + // 模式回调接口的实现类,处理列表的多种模式相关操作 private ModeCallback mModeCallBack; - + // 用于日志输出的标签 private static final String TAG = "NotesListActivity"; - + // 笔记列表视图滚动的速率 public static final int NOTES_LISTVIEW_SCROLL_RATE = 30; - + // 当前聚焦的笔记数据项 private NoteItemData mFocusNoteDataItem; - + // 用于普通文件夹下查询笔记的选择条件语句 private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?"; - + // 用于根文件夹下查询笔记的选择条件语句 private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>" + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR (" + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND " + NoteColumns.NOTES_COUNT + ">0)"; - + // 打开节点(笔记等)的请求码 private final static int REQUEST_CODE_OPEN_NODE = 102; + // 新建节点(笔记等)的请求码 private final static int REQUEST_CODE_NEW_NODE = 103; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.note_list); + // 初始化相关资源 initResources(); /** - * Insert an introduction when user firstly use this application + * 当用户首次使用该应用时插入一个介绍信息 */ setAppInfoFromRawRes(); } - @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK && (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) { + // 如果是打开或新建节点操作成功返回,重置笔记列表适配器的游标 mNotesListAdapter.changeCursor(null); } else { super.onActivityResult(requestCode, resultCode, data); } } - private void setAppInfoFromRawRes() { + // 获取默认的共享偏好设置实例 SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) { StringBuilder sb = new StringBuilder(); InputStream in = null; try { - in = getResources().openRawResource(R.raw.introduction); - if (in != null) { + // 打开存储介绍信息的原始资源文件 + in = getResources().openRawResource(R.raw.introduction); + if (in!= null) { InputStreamReader isr = new InputStreamReader(in); BufferedReader br = new BufferedReader(isr); char [] buf = new char[1024]; int len = 0; + // 读取文件内容并添加到StringBuilder中 while ((len = br.read(buf)) > 0) { sb.append(buf, 0, len); } @@ -180,20 +185,22 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt e.printStackTrace(); return; } finally { - if(in != null) { + if(in!= null) { try { in.close(); } catch (IOException e) { - // TODO Auto-generated catch block + // 处理关闭输入流时可能出现的异常 e.printStackTrace(); } } } + // 创建一个空的工作笔记对象,设置相关属性并添加读取到的介绍文本内容 WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER, AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE, ResourceParser.RED); note.setWorkingText(sb.toString()); + // 保存笔记,如果保存成功则在共享偏好设置中标记已添加介绍信息 if (note.saveNote()) { sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit(); } else { @@ -202,44 +209,54 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } } } - - @Override - protected void onStart() { - super.onStart(); - startAsyncNotesListQuery(); - } - private void initResources() { + // 获取内容提供器实例 mContentResolver = this.getContentResolver(); + // 创建后台查询处理器实例,传入内容提供器 mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); + // 设置当前文件夹ID为根文件夹ID mCurrentFolderId = Notes.ID_ROOT_FOLDER; + // 通过ID查找笔记列表的ListView组件 mNotesListView = (ListView) findViewById(R.id.notes_list); + // 为笔记列表添加页脚视图 mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null), null, false); + // 设置笔记列表项的点击监听器 mNotesListView.setOnItemClickListener(new OnListItemClickListener()); + // 设置笔记列表项的长按监听器为当前Activity(实现了该接口) mNotesListView.setOnItemLongClickListener(this); + // 创建笔记列表的适配器实例 mNotesListAdapter = new NotesListAdapter(this); + // 为笔记列表设置适配器 mNotesListView.setAdapter(mNotesListAdapter); + // 通过ID查找“新建笔记”按钮 mAddNewNote = (Button) findViewById(R.id.btn_new_note); + // 设置“新建笔记”按钮的点击监听器为当前Activity(实现了该接口) mAddNewNote.setOnClickListener(this); + // 设置“新建笔记”按钮的触摸监听器 mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); mDispatch = false; mDispatchY = 0; mOriginY = 0; + // 通过ID查找标题栏的TextView组件 mTitleBar = (TextView) findViewById(R.id.tv_title_bar); + // 设置初始的列表编辑状态为笔记列表状态 mState = ListEditState.NOTE_LIST; + // 创建模式回调接口的实现类实例 mModeCallBack = new ModeCallback(); } - private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener { private DropdownMenu mDropDownMenu; private ActionMode mActionMode; private MenuItem mMoveMenu; + // 当动作模式创建时调用 public boolean onCreateActionMode(ActionMode mode, Menu menu) { + // 填充动作模式的菜单选项 getMenuInflater().inflate(R.menu.note_list_options, menu); menu.findItem(R.id.delete).setOnMenuItemClickListener(this); mMoveMenu = menu.findItem(R.id.move); + // 根据聚焦笔记数据项所在文件夹等情况设置“移动”菜单选项的可见性及点击监听器 if (mFocusNoteDataItem.getParentId() == Notes.ID_CALL_RECORD_FOLDER || DataUtils.getUserFolderCount(mContentResolver) == 0) { mMoveMenu.setVisible(false); @@ -248,10 +265,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt mMoveMenu.setOnMenuItemClickListener(this); } mActionMode = mode; + // 设置笔记列表适配器进入选择模式 mNotesListAdapter.setChoiceMode(true); + // 设置笔记列表不可长按(避免冲突) mNotesListView.setLongClickable(false); + // 隐藏“新建笔记”按钮 mAddNewNote.setVisibility(View.GONE); + // 为动作模式设置自定义视图 View customView = LayoutInflater.from(NotesListActivity.this).inflate( R.layout.note_list_dropdown_menu, null); mode.setCustomView(customView); @@ -269,13 +290,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt return true; } + // 更新菜单相关状态,比如根据选择的笔记数量更新下拉菜单标题等 private void updateMenu() { int selectedCount = mNotesListAdapter.getSelectedCount(); - // Update dropdown menu + // 更新下拉菜单的标题格式 String format = getResources().getString(R.string.menu_select_title, selectedCount); mDropDownMenu.setTitle(format); MenuItem item = mDropDownMenu.findItem(R.id.action_select_all); - if (item != null) { + if (item!= null) { if (mNotesListAdapter.isAllSelected()) { item.setChecked(true); item.setTitle(R.string.menu_deselect_all); @@ -287,15 +309,16 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - // TODO Auto-generated method stub + // 可在此方法中对动作模式下的菜单进行预处理,目前返回false表示无操作 return false; } public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - // TODO Auto-generated method stub + // 处理动作模式下菜单选项被点击的操作,目前返回false表示无操作,实际应根据需求完善 return false; } + // 当动作模式销毁时调用,恢复相关组件的初始状态 public void onDestroyActionMode(ActionMode mode) { mNotesListAdapter.setChoiceMode(false); mNotesListView.setLongClickable(true); @@ -306,12 +329,14 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt mActionMode.finish(); } + // 当列表项选中状态改变时调用,更新适配器相关状态并更新菜单 public void onItemCheckedStateChanged(ActionMode mode, int position, long id, - boolean checked) { + boolean checked) { mNotesListAdapter.setCheckedItem(position, checked); updateMenu(); } + // 处理菜单选项点击事件 public boolean onMenuItemClick(MenuItem item) { if (mNotesListAdapter.getSelectedCount() == 0) { Toast.makeText(NotesListActivity.this, getString(R.string.menu_select_none), @@ -321,18 +346,19 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt switch (item.getItemId()) { case R.id.delete: + // 点击删除菜单选项时,弹出确认删除的对话框 AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); builder.setTitle(getString(R.string.alert_title_delete)); builder.setIcon(android.R.drawable.ic_dialog_alert); builder.setMessage(getString(R.string.alert_message_delete_notes, - mNotesListAdapter.getSelectedCount())); + mNotesListAdapter.getSelectedCount())); builder.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, - int which) { - batchDelete(); - } - }); + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int which) { + batchDelete(); + } + }); builder.setNegativeButton(android.R.string.cancel, null); builder.show(); break; @@ -345,7 +371,6 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt return true; } } - private class NewNoteOnTouchListener implements OnTouchListener { public boolean onTouch(View v, MotionEvent event) { @@ -357,25 +382,19 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt int start = screenHeight - newNoteViewHeight; int eventY = start + (int) event.getY(); /** - * Minus TitleBar's height + * 如果处于子文件夹状态,减去标题栏的高度 */ if (mState == ListEditState.SUB_FOLDER) { eventY -= mTitleBar.getHeight(); start -= mTitleBar.getHeight(); } /** - * HACKME:When click the transparent part of "New Note" button, dispatch - * the event to the list view behind this button. The transparent part of - * "New Note" button could be expressed by formula y=-0.12x+94(Unit:pixel) - * and the line top of the button. The coordinate based on left of the "New - * Note" button. The 94 represents maximum height of the transparent part. - * Notice that, if the background of the button changes, the formula should - * also change. This is very bad, just for the UI designer's strong requirement. + * HACKME:当点击“新建笔记”按钮的透明部分时,将事件分发给按钮后面的列表视图。透明部分可以用公式y=-0.12x+94(单位:像素)和按钮的顶部线条表示。坐标基于“新建笔记”按钮的左侧。94表示透明部分的最大高度。注意,如果按钮的背景改变,公式也应改变。这是为了满足UI设计师的强烈要求而做的不太好的处理方式。 */ if (event.getY() < (event.getX() * (-0.12) + 94)) { View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1 - mNotesListView.getFooterViewsCount()); - if (view != null && view.getBottom() > start + if (view!= null && view.getBottom() > start && (view.getTop() < (start + 94))) { mOriginY = (int) event.getY(); mDispatchY = eventY; @@ -407,548 +426,192 @@ public class NotesListActivity extends Activity implements OnClickListener, OnIt } }; + private class NewNoteOnTouchListener implements OnTouchListener { - private void startAsyncNotesListQuery() { - String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER) ? ROOT_FOLDER_SELECTION - : NORMAL_SELECTION; - mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null, - Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] { - String.valueOf(mCurrentFolderId) - }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); - } - - private final class BackgroundQueryHandler extends AsyncQueryHandler { - public BackgroundQueryHandler(ContentResolver contentResolver) { - super(contentResolver); - } - - @Override - protected void onQueryComplete(int token, Object cookie, Cursor cursor) { - switch (token) { - case FOLDER_NOTE_LIST_QUERY_TOKEN: - mNotesListAdapter.changeCursor(cursor); - break; - case FOLDER_LIST_QUERY_TOKEN: - if (cursor != null && cursor.getCount() > 0) { - showFolderListMenu(cursor); - } else { - Log.e(TAG, "Query folder failed"); + public boolean onTouch(View v, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: { + Display display = getWindowManager().getDefaultDisplay(); + int screenHeight = display.getHeight(); + int newNoteViewHeight = mAddNewNote.getHeight(); + int start = screenHeight - newNoteViewHeight; + int eventY = start + (int) event.getY(); + /** + * 如果处于子文件夹状态,减去标题栏的高度 + */ + if (mState == ListEditState.SUB_FOLDER) { + eventY -= mTitleBar.getHeight(); + start -= mTitleBar.getHeight(); } - break; - default: - return; - } - } - } - - private void showFolderListMenu(Cursor cursor) { - AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); - builder.setTitle(R.string.menu_title_select_folder); - final FoldersListAdapter adapter = new FoldersListAdapter(this, cursor); - builder.setAdapter(adapter, new DialogInterface.OnClickListener() { - - public void onClick(DialogInterface dialog, int which) { - DataUtils.batchMoveToFolder(mContentResolver, - mNotesListAdapter.getSelectedItemIds(), adapter.getItemId(which)); - Toast.makeText( - NotesListActivity.this, - getString(R.string.format_move_notes_to_folder, - mNotesListAdapter.getSelectedCount(), - adapter.getFolderName(NotesListActivity.this, which)), - Toast.LENGTH_SHORT).show(); - mModeCallBack.finishActionMode(); - } - }); - builder.show(); - } - - private void createNewNote() { - Intent intent = new Intent(this, NoteEditActivity.class); - intent.setAction(Intent.ACTION_INSERT_OR_EDIT); - intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mCurrentFolderId); - this.startActivityForResult(intent, REQUEST_CODE_NEW_NODE); - } - - private void batchDelete() { - new AsyncTask>() { - protected HashSet doInBackground(Void... unused) { - HashSet widgets = mNotesListAdapter.getSelectedWidget(); - if (!isSyncMode()) { - // if not synced, delete notes directly - if (DataUtils.batchDeleteNotes(mContentResolver, mNotesListAdapter - .getSelectedItemIds())) { - } else { - Log.e(TAG, "Delete notes error, should not happens"); + /** + * HACKME:当点击“新建笔记”按钮的透明部分时,将事件分发给按钮后面的列表视图。透明部分可以用公式y=-0.12x+94(单位:像素)和按钮的顶部线条表示。坐标基于“新建笔记”按钮的左侧。94表示透明部分的最大高度。注意,如果按钮的背景改变,公式也应改变。这是为了满足UI设计师的强烈要求而做的不太好的处理方式。 + */ + if (event.getY() < (event.getX() * (-0.12) + 94)) { + View view = mNotesListView.getChildAt(mNotesListView.getChildCount() - 1 + - mNotesListView.getFooterViewsCount()); + if (view!= null && view.getBottom() > start + && (view.getTop() < (start + 94))) { + mOriginY = (int) event.getY(); + mDispatchY = eventY; + event.setLocation(event.getX(), mDispatchY); + mDispatch = true; + return mNotesListView.dispatchTouchEvent(event); + } } - } else { - // in sync mode, we'll move the deleted note into the trash - // folder - if (!DataUtils.batchMoveToFolder(mContentResolver, mNotesListAdapter - .getSelectedItemIds(), Notes.ID_TRASH_FOLER)) { - Log.e(TAG, "Move notes to trash folder error, should not happens"); + break; + } + case MotionEvent.ACTION_MOVE: { + if (mDispatch) { + mDispatchY += (int) event.getY() - mOriginY; + event.setLocation(event.getX(), mDispatchY); + return mNotesListView.dispatchTouchEvent(event); } + break; } - return widgets; - } - - @Override - protected void onPostExecute(HashSet widgets) { - if (widgets != null) { - for (AppWidgetAttribute widget : widgets) { - if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID - && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { - updateWidget(widget.widgetId, widget.widgetType); - } + default: { + if (mDispatch) { + event.setLocation(event.getX(), mDispatchY); + mDispatch = false; + return mNotesListView.dispatchTouchEvent(event); } + break; } - mModeCallBack.finishActionMode(); } - }.execute(); - } - - private void deleteFolder(long folderId) { - if (folderId == Notes.ID_ROOT_FOLDER) { - Log.e(TAG, "Wrong folder id, should not happen " + folderId); - return; + return false; } - HashSet ids = new HashSet(); - ids.add(folderId); - HashSet widgets = DataUtils.getFolderNoteWidget(mContentResolver, - folderId); - if (!isSyncMode()) { - // if not synced, delete folder directly - DataUtils.batchDeleteNotes(mContentResolver, ids); - } else { - // in sync mode, we'll move the deleted folder into the trash folder - DataUtils.batchMoveToFolder(mContentResolver, ids, Notes.ID_TRASH_FOLER); - } - if (widgets != null) { - for (AppWidgetAttribute widget : widgets) { - if (widget.widgetId != AppWidgetManager.INVALID_APPWIDGET_ID - && widget.widgetType != Notes.TYPE_WIDGET_INVALIDE) { - updateWidget(widget.widgetId, widget.widgetType); - } - } - } + }; + private void startAsyncNotesListQuery() { + String selection = (mCurrentFolderId == Notes.ID_ROOT_FOLDER)? ROOT_FOLDER_SELECTION + : NORMAL_SELECTION; + mBackgroundQueryHandler.startQuery(FOLDER_NOTE_LIST_QUERY_TOKEN, null, + Notes.CONTENT_NOTE_URI, NoteItemData.PROJECTION, selection, new String[] { + String.valueOf(mCurrentFolderId) + }, NoteColumns.TYPE + " DESC," + NoteColumns.MODIFIED_DATE + " DESC"); } - - private void openNode(NoteItemData data) { - Intent intent = new Intent(this, NoteEditActivity.class); - intent.setAction(Intent.ACTION_VIEW); - intent.putExtra(Intent.EXTRA_UID, data.getId()); - this.startActivityForResult(intent, REQUEST_CODE_OPEN_NODE); + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + // 创建并返回一个NotesListItem类型的视图实例,用于展示列表中的每一项 + return new NotesListItem(context); } - - private void openFolder(NoteItemData data) { - mCurrentFolderId = data.getId(); - startAsyncNotesListQuery(); - if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { - mState = ListEditState.CALL_RECORD_FOLDER; - mAddNewNote.setVisibility(View.GONE); - } else { - mState = ListEditState.SUB_FOLDER; - } - if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { - mTitleBar.setText(R.string.call_record_folder_name); - } else { - mTitleBar.setText(data.getSnippet()); + @Override + public void bindView(View view, Context context, Cursor cursor) { + if (view instanceof NotesListItem) { + // 根据游标数据创建NoteItemData对象,用于封装笔记相关的数据 + NoteItemData itemData = new NoteItemData(context, cursor); + // 调用NotesListItem的bind方法,将相关数据和选中状态等信息绑定到视图上进行展示 + ((NotesListItem) view).bind(context, itemData, mChoiceMode, + isSelectedItem(cursor.getPosition())); } - mTitleBar.setVisibility(View.VISIBLE); } - - public void onClick(View v) { - switch (v.getId()) { - case R.id.btn_new_note: - createNewNote(); - break; - default: - break; - } + public void setCheckedItem(final int position, final boolean checked) { + // 将指定位置的列表项的选中状态存入选中索引映射中 + mSelectedIndex.put(position, checked); + // 通知数据集已改变,以便视图根据新的选中状态进行更新显示 + notifyDataSetChanged(); } - - private void showSoftInput() { - InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - if (inputMethodManager != null) { - inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); - } + public boolean isInChoiceMode() { + // 返回当前是否处于选择模式的状态 + return mChoiceMode; } - - private void hideSoftInput(View view) { - InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0); + public void setChoiceMode(boolean mode) { + // 清除之前的选中索引记录,相当于重置选中状态 + mSelectedIndex.clear(); + // 设置当前的选择模式状态 + mChoiceMode = mode; } - - 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); - showSoftInput(); - if (!create) { - if (mFocusNoteDataItem != null) { - etName.setText(mFocusNoteDataItem.getSnippet()); - builder.setTitle(getString(R.string.menu_folder_change_name)); - } else { - Log.e(TAG, "The long click data item is null"); - return; + public void selectAll(boolean checked) { + Cursor cursor = getCursor(); + for (int i = 0; i < getCount(); i++) { + if (cursor.moveToPosition(i)) { + if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) { + setCheckedItem(i, checked); + } } - } else { - etName.setText(""); - builder.setTitle(this.getString(R.string.menu_create_folder)); } - - builder.setPositiveButton(android.R.string.ok, null); - builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - hideSoftInput(etName); - } - }); - - final Dialog dialog = builder.setView(view).show(); - final Button positive = (Button)dialog.findViewById(android.R.id.button1); - positive.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - hideSoftInput(etName); - String name = etName.getText().toString(); - if (DataUtils.checkVisibleFolderName(mContentResolver, name)) { - Toast.makeText(NotesListActivity.this, getString(R.string.folder_exist, name), - Toast.LENGTH_LONG).show(); - etName.setSelection(0, etName.length()); - return; - } - if (!create) { - if (!TextUtils.isEmpty(name)) { - ContentValues values = new ContentValues(); - values.put(NoteColumns.SNIPPET, name); - values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); - values.put(NoteColumns.LOCAL_MODIFIED, 1); - mContentResolver.update(Notes.CONTENT_NOTE_URI, values, NoteColumns.ID - + "=?", new String[] { - String.valueOf(mFocusNoteDataItem.getId()) - }); - } - } else if (!TextUtils.isEmpty(name)) { - ContentValues values = new ContentValues(); - values.put(NoteColumns.SNIPPET, name); - values.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); - mContentResolver.insert(Notes.CONTENT_NOTE_URI, values); + } + public HashSet getSelectedItemIds() { + HashSet itemSet = new HashSet(); + for (Integer position : mSelectedIndex.keySet()) { + if (mSelectedIndex.get(position) == true) { + Long id = getItemId(position); + if (id == Notes.ID_ROOT_FOLDER) { + Log.d(TAG, "Wrong item id, should not happen"); + } else { + itemSet.add(id); } - dialog.dismiss(); } - }); - - if (TextUtils.isEmpty(etName.getText())) { - positive.setEnabled(false); } - /** - * When the name edit text is null, disable the positive button - */ - etName.addTextChangedListener(new TextWatcher() { - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - // TODO Auto-generated method stub - - } - public void onTextChanged(CharSequence s, int start, int before, int count) { - if (TextUtils.isEmpty(etName.getText())) { - positive.setEnabled(false); + return itemSet; + } + public HashSet getSelectedWidget() { + HashSet itemSet = new HashSet(); + for (Integer position : mSelectedIndex.keySet()) { + if (mSelectedIndex.get(position) == true) { + Cursor c = (Cursor) getItem(position); + if (c!= null) { + AppWidgetAttribute widget = new AppWidgetAttribute(); + NoteItemData item = new NoteItemData(mContext, c); + widget.widgetId = item.getWidgetId(); + widget.widgetType = item.getWidgetType(); + itemSet.add(widget); + /** + * Don't close cursor here, only the adapter could close it + */ } else { - positive.setEnabled(true); + Log.e(TAG, "Invalid cursor"); + return null; } } - - public void afterTextChanged(Editable s) { - // TODO Auto-generated method stub - - } - }); - } - - @Override - public void onBackPressed() { - switch (mState) { - case SUB_FOLDER: - mCurrentFolderId = Notes.ID_ROOT_FOLDER; - mState = ListEditState.NOTE_LIST; - startAsyncNotesListQuery(); - mTitleBar.setVisibility(View.GONE); - break; - case CALL_RECORD_FOLDER: - mCurrentFolderId = Notes.ID_ROOT_FOLDER; - mState = ListEditState.NOTE_LIST; - mAddNewNote.setVisibility(View.VISIBLE); - mTitleBar.setVisibility(View.GONE); - startAsyncNotesListQuery(); - break; - case NOTE_LIST: - super.onBackPressed(); - break; - default: - break; - } - } - - private void updateWidget(int appWidgetId, int appWidgetType) { - Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); - if (appWidgetType == Notes.TYPE_WIDGET_2X) { - intent.setClass(this, NoteWidgetProvider_2x.class); - } else if (appWidgetType == Notes.TYPE_WIDGET_4X) { - intent.setClass(this, NoteWidgetProvider_4x.class); - } else { - Log.e(TAG, "Unspported widget type"); - return; } - - intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { - appWidgetId - }); - - sendBroadcast(intent); - setResult(RESULT_OK, intent); + return itemSet; } - - private final OnCreateContextMenuListener mFolderOnCreateContextMenuListener = new OnCreateContextMenuListener() { - public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { - if (mFocusNoteDataItem != null) { - menu.setHeaderTitle(mFocusNoteDataItem.getSnippet()); - menu.add(0, MENU_FOLDER_VIEW, 0, R.string.menu_folder_view); - 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); + public int getSelectedCount() { + Collection values = mSelectedIndex.values(); + if (null == values) { + return 0; + } + Iterator iter = values.iterator(); + int count = 0; + while (iter.hasNext()) { + if (true == iter.next()) { + count++; } } - }; - - @Override - public void onContextMenuClosed(Menu menu) { - if (mNotesListView != null) { - mNotesListView.setOnCreateContextMenuListener(null); - } - super.onContextMenuClosed(menu); + return count; } - - @Override - public boolean onContextItemSelected(MenuItem item) { - if (mFocusNoteDataItem == null) { - Log.e(TAG, "The long click data item is null"); - return false; - } - switch (item.getItemId()) { - case MENU_FOLDER_VIEW: - openFolder(mFocusNoteDataItem); - break; - case MENU_FOLDER_DELETE: - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.alert_title_delete)); - builder.setIcon(android.R.drawable.ic_dialog_alert); - builder.setMessage(getString(R.string.alert_message_delete_folder)); - builder.setPositiveButton(android.R.string.ok, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - deleteFolder(mFocusNoteDataItem.getId()); - } - }); - builder.setNegativeButton(android.R.string.cancel, null); - builder.show(); - break; - case MENU_FOLDER_CHANGE_NAME: - showCreateOrModifyFolderDialog(false); - break; - default: - break; - } - - return true; + public boolean isAllSelected() { + int checkedCount = getSelectedCount(); + return (checkedCount!= 0 && checkedCount == mNotesCount); } - - @Override - public boolean onPrepareOptionsMenu(Menu menu) { - menu.clear(); - if (mState == ListEditState.NOTE_LIST) { - getMenuInflater().inflate(R.menu.note_list, menu); - // set sync or sync_cancel - menu.findItem(R.id.menu_sync).setTitle( - GTaskSyncService.isSyncing() ? R.string.menu_sync_cancel : R.string.menu_sync); - } else if (mState == ListEditState.SUB_FOLDER) { - getMenuInflater().inflate(R.menu.sub_folder, menu); - } else if (mState == ListEditState.CALL_RECORD_FOLDER) { - getMenuInflater().inflate(R.menu.call_record_folder, menu); - } else { - Log.e(TAG, "Wrong state:" + mState); + public boolean isSelectedItem(final int position) { + if (null == mSelectedIndex.get(position)) { + return false; } - return true; + return mSelectedIndex.get(position); } - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.menu_new_folder: { - showCreateOrModifyFolderDialog(true); - break; - } - case R.id.menu_export_text: { - exportNoteToText(); - break; - } - case R.id.menu_sync: { - if (isSyncMode()) { - if (TextUtils.equals(item.getTitle(), getString(R.string.menu_sync))) { - GTaskSyncService.startSync(this); - } else { - GTaskSyncService.cancelSync(this); - } - } else { - startPreferenceActivity(); - } - break; - } - case R.id.menu_setting: { - startPreferenceActivity(); - break; - } - case R.id.menu_new_note: { - createNewNote(); - break; - } - case R.id.menu_search: - onSearchRequested(); - break; - default: - break; - } - return true; + protected void onContentChanged() { + super.onContentChanged(); + calcNotesCount(); } - @Override - public boolean onSearchRequested() { - startSearch(null, false, null /* appData */, false); - return true; + public void changeCursor(Cursor cursor) { + super.changeCursor(cursor); + calcNotesCount(); } - - private void exportNoteToText() { - final BackupUtils backup = BackupUtils.getInstance(NotesListActivity.this); - new AsyncTask() { - - @Override - protected Integer doInBackground(Void... unused) { - return backup.exportToText(); - } - - @Override - protected void onPostExecute(Integer result) { - if (result == BackupUtils.STATE_SD_CARD_UNMOUONTED) { - AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); - builder.setTitle(NotesListActivity.this - .getString(R.string.failed_sdcard_export)); - builder.setMessage(NotesListActivity.this - .getString(R.string.error_sdcard_unmounted)); - builder.setPositiveButton(android.R.string.ok, null); - builder.show(); - } else if (result == BackupUtils.STATE_SUCCESS) { - AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); - builder.setTitle(NotesListActivity.this - .getString(R.string.success_sdcard_export)); - builder.setMessage(NotesListActivity.this.getString( - R.string.format_exported_file_location, backup - .getExportedTextFileName(), backup.getExportedTextFileDir())); - builder.setPositiveButton(android.R.string.ok, null); - builder.show(); - } else if (result == BackupUtils.STATE_SYSTEM_ERROR) { - AlertDialog.Builder builder = new AlertDialog.Builder(NotesListActivity.this); - builder.setTitle(NotesListActivity.this - .getString(R.string.failed_sdcard_export)); - builder.setMessage(NotesListActivity.this - .getString(R.string.error_sdcard_export)); - builder.setPositiveButton(android.R.string.ok, null); - builder.show(); + private void calcNotesCount() { + mNotesCount = 0; + for (int i = 0; i < getCount(); i++) { + Cursor c = (Cursor) getItem(i); + if (c!= null) { + if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) { + mNotesCount++; } - } - - }.execute(); - } - - private boolean isSyncMode() { - return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; - } - - private void startPreferenceActivity() { - Activity from = getParent() != null ? getParent() : this; - Intent intent = new Intent(from, NotesPreferenceActivity.class); - from.startActivityIfNeeded(intent, -1); - } - - private class OnListItemClickListener implements OnItemClickListener { - - public void onItemClick(AdapterView parent, View view, int position, long id) { - if (view instanceof NotesListItem) { - NoteItemData item = ((NotesListItem) view).getItemData(); - if (mNotesListAdapter.isInChoiceMode()) { - if (item.getType() == Notes.TYPE_NOTE) { - position = position - mNotesListView.getHeaderViewsCount(); - mModeCallBack.onItemCheckedStateChanged(null, position, id, - !mNotesListAdapter.isSelectedItem(position)); - } - return; - } - - switch (mState) { - case NOTE_LIST: - if (item.getType() == Notes.TYPE_FOLDER - || item.getType() == Notes.TYPE_SYSTEM) { - openFolder(item); - } else if (item.getType() == Notes.TYPE_NOTE) { - openNode(item); - } else { - Log.e(TAG, "Wrong note type in NOTE_LIST"); - } - break; - case SUB_FOLDER: - case CALL_RECORD_FOLDER: - if (item.getType() == Notes.TYPE_NOTE) { - openNode(item); - } else { - Log.e(TAG, "Wrong note type in SUB_FOLDER"); - } - break; - default: - break; - } - } - } - - } - - private void startQueryDestinationFolders() { - String selection = NoteColumns.TYPE + "=? AND " + NoteColumns.PARENT_ID + "<>? AND " + NoteColumns.ID + "<>?"; - selection = (mState == ListEditState.NOTE_LIST) ? selection: - "(" + selection + ") OR (" + NoteColumns.ID + "=" + Notes.ID_ROOT_FOLDER + ")"; - - mBackgroundQueryHandler.startQuery(FOLDER_LIST_QUERY_TOKEN, - null, - Notes.CONTENT_NOTE_URI, - FoldersListAdapter.PROJECTION, - selection, - new String[] { - String.valueOf(Notes.TYPE_FOLDER), - String.valueOf(Notes.ID_TRASH_FOLER), - String.valueOf(mCurrentFolderId) - }, - NoteColumns.MODIFIED_DATE + " DESC"); - } - - public boolean onItemLongClick(AdapterView parent, View view, int position, long id) { - if (view instanceof NotesListItem) { - mFocusNoteDataItem = ((NotesListItem) view).getItemData(); - if (mFocusNoteDataItem.getType() == Notes.TYPE_NOTE && !mNotesListAdapter.isInChoiceMode()) { - if (mNotesListView.startActionMode(mModeCallBack) != null) { - mModeCallBack.onItemCheckedStateChanged(null, position, id, true); - mNotesListView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - } else { - Log.e(TAG, "startActionMode fails"); - } - } else if (mFocusNoteDataItem.getType() == Notes.TYPE_FOLDER) { - mNotesListView.setOnCreateContextMenuListener(mFolderOnCreateContextMenuListener); + } else { + Log.e(TAG, "Invalid cursor"); + return; } } - return false; - } -} + } \ No newline at end of file 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..c4a24cb 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesListAdapter.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesListAdapter.java @@ -32,153 +32,29 @@ 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) { + // 调用父类构造方法,传入上下文并将游标初始化为null super(context, null); + // 初始化选中索引的哈希映射 mSelectedIndex = new HashMap(); + // 保存传入的上下文对象 mContext = context; + // 初始化笔记数量为0 mNotesCount = 0; } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return new NotesListItem(context); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - if (view instanceof NotesListItem) { - NoteItemData itemData = new NoteItemData(context, cursor); - ((NotesListItem) view).bind(context, itemData, mChoiceMode, - isSelectedItem(cursor.getPosition())); - } - } - - public void setCheckedItem(final int position, final boolean checked) { - mSelectedIndex.put(position, checked); - notifyDataSetChanged(); - } - - public boolean isInChoiceMode() { - return mChoiceMode; - } - - public void setChoiceMode(boolean mode) { - mSelectedIndex.clear(); - mChoiceMode = mode; - } - - public void selectAll(boolean checked) { - Cursor cursor = getCursor(); - for (int i = 0; i < getCount(); i++) { - if (cursor.moveToPosition(i)) { - if (NoteItemData.getNoteType(cursor) == Notes.TYPE_NOTE) { - setCheckedItem(i, checked); - } - } - } - } - - public HashSet getSelectedItemIds() { - HashSet itemSet = new HashSet(); - for (Integer position : mSelectedIndex.keySet()) { - if (mSelectedIndex.get(position) == true) { - Long id = getItemId(position); - if (id == Notes.ID_ROOT_FOLDER) { - Log.d(TAG, "Wrong item id, should not happen"); - } else { - itemSet.add(id); - } - } - } - - return itemSet; - } - - public HashSet getSelectedWidget() { - HashSet itemSet = new HashSet(); - for (Integer position : mSelectedIndex.keySet()) { - if (mSelectedIndex.get(position) == true) { - Cursor c = (Cursor) getItem(position); - if (c != null) { - AppWidgetAttribute widget = new AppWidgetAttribute(); - NoteItemData item = new NoteItemData(mContext, c); - widget.widgetId = item.getWidgetId(); - widget.widgetType = item.getWidgetType(); - itemSet.add(widget); - /** - * Don't close cursor here, only the adapter could close it - */ - } else { - Log.e(TAG, "Invalid cursor"); - return null; - } - } - } - return itemSet; - } - - public int getSelectedCount() { - Collection values = mSelectedIndex.values(); - if (null == values) { - return 0; - } - Iterator iter = values.iterator(); - int count = 0; - while (iter.hasNext()) { - if (true == iter.next()) { - count++; - } - } - return count; - } - - public boolean isAllSelected() { - int checkedCount = getSelectedCount(); - return (checkedCount != 0 && checkedCount == mNotesCount); - } - - public boolean isSelectedItem(final int position) { - if (null == mSelectedIndex.get(position)) { - return false; - } - return mSelectedIndex.get(position); - } - - @Override - protected void onContentChanged() { - super.onContentChanged(); - calcNotesCount(); - } - - @Override - public void changeCursor(Cursor cursor) { - super.changeCursor(cursor); - calcNotesCount(); - } - - private void calcNotesCount() { - mNotesCount = 0; - for (int i = 0; i < getCount(); i++) { - Cursor c = (Cursor) getItem(i); - if (c != null) { - if (NoteItemData.getNoteType(c) == Notes.TYPE_NOTE) { - mNotesCount++; - } - } else { - Log.e(TAG, "Invalid cursor"); - return; - } - } - } -} 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..aa79af2 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesListItem.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesListItem.java @@ -31,33 +31,42 @@ import net.micode.notes.tool.ResourceParser.NoteItemBgResources; public class NotesListItem extends LinearLayout { + // 用于显示提醒相关图标的 ImageView,可能表示笔记有提醒等情况 private ImageView mAlert; + // 用于显示笔记或文件夹标题的 TextView private TextView mTitle; + // 用于显示时间相关信息(如修改时间等)的 TextView private TextView mTime; + // 用于显示通话记录相关名称的 TextView,可能在特定的通话记录相关场景下使用 private TextView mCallName; + // 封装了笔记或文件夹相关数据的对象,用于在列表项中展示具体内容 private NoteItemData mItemData; + // 复选框,用于在选择模式下标记该项是否被选中 private CheckBox mCheckBox; - public NotesListItem(Context context) { super(context); + // 通过布局资源文件来填充当前的LinearLayout(即NotesListItem自身),使其具备相应的子视图布局结构 inflate(context, R.layout.note_item, this); + // 通过ID查找并初始化对应的子视图组件 mAlert = (ImageView) findViewById(R.id.iv_alert_icon); mTitle = (TextView) findViewById(R.id.tv_title); mTime = (TextView) findViewById(R.id.tv_time); mCallName = (TextView) findViewById(R.id.tv_name); mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); } - public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) { if (choiceMode && data.getType() == Notes.TYPE_NOTE) { + // 如果处于选择模式且当前数据对应的是笔记类型,则显示复选框,并根据传入的选中状态设置复选框的勾选状态 mCheckBox.setVisibility(View.VISIBLE); mCheckBox.setChecked(checked); } else { + // 否则隐藏复选框,比如在非选择模式或者是文件夹类型等情况时不需要显示复选框 mCheckBox.setVisibility(View.GONE); } mItemData = data; if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { + // 如果当前数据对应的是通话记录文件夹 mCallName.setVisibility(View.GONE); mAlert.setVisibility(View.VISIBLE); mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); @@ -65,6 +74,7 @@ public class NotesListItem extends LinearLayout { + context.getString(R.string.format_folder_files_count, data.getNotesCount())); mAlert.setImageResource(R.drawable.call_record); } else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { + // 如果当前数据的父文件夹是通话记录文件夹,通常表示属于通话记录相关的具体内容 mCallName.setVisibility(View.VISIBLE); mCallName.setText(data.getCallName()); mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); @@ -80,11 +90,13 @@ public class NotesListItem extends LinearLayout { mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); if (data.getType() == Notes.TYPE_FOLDER) { + // 如果当前数据对应的是普通文件夹类型,设置标题文本包含文件夹名称及其中文件数量信息,并隐藏提醒图标 mTitle.setText(data.getSnippet() + context.getString(R.string.format_folder_files_count, - data.getNotesCount())); + data.getNotesCount())); mAlert.setVisibility(View.GONE); } else { + // 如果是普通笔记类型,设置标题为格式化后的笔记摘要内容,并根据是否有提醒来显示或隐藏提醒图标 mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); if (data.hasAlert()) { mAlert.setImageResource(R.drawable.clock); @@ -94,11 +106,12 @@ public class NotesListItem extends LinearLayout { } } } + // 设置时间文本,显示相对时间(可能是相对于当前时间的修改时间等) mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); + // 根据数据设置列表项的背景样式,区分笔记和文件夹以及笔记的不同状态等情况 setBackground(data); } - private void setBackground(NoteItemData data) { int id = data.getBgColorId(); if (data.getType() == Notes.TYPE_NOTE) { @@ -115,8 +128,6 @@ public class NotesListItem extends LinearLayout { setBackgroundResource(NoteItemBgResources.getFolderBgRes()); } } - public NoteItemData getItemData() { return mItemData; - } -} + } \ No newline at end of file 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..15a06ab 100644 --- a/src/Notes-master/src/net/micode/notes/ui/NotesPreferenceActivity.java +++ b/src/Notes-master/src/net/micode/notes/ui/NotesPreferenceActivity.java @@ -49,42 +49,51 @@ import net.micode.notes.gtask.remote.GTaskSyncService; public class NotesPreferenceActivity extends PreferenceActivity { + // 存储偏好设置的名称 public static final String PREFERENCE_NAME = "notes_preferences"; - + // 偏好设置中同步账号名称对应的键 public static final String PREFERENCE_SYNC_ACCOUNT_NAME = "pref_key_account_name"; - + // 偏好设置中上次同步时间对应的键 public static final String PREFERENCE_LAST_SYNC_TIME = "pref_last_sync_time"; - + // 偏好设置中设置背景颜色相关键(从变量名推测,可能用于控制背景颜色随机出现相关功能) public static final String PREFERENCE_SET_BG_COLOR_KEY = "pref_key_bg_random_appear"; - + // 同步账号相关偏好设置分类的键 private static final String PREFERENCE_SYNC_ACCOUNT_KEY = "pref_sync_account_key"; - + // 权限相关过滤键(可能用于账户相关权限过滤,具体需结合更多上下文判断) private static final String AUTHORITIES_FILTER_KEY = "authorities"; + // 用于展示同步账号相关偏好设置的分类 private PreferenceCategory mAccountCategory; - + // 广播接收器,用于接收同步服务相关广播 private GTaskReceiver mReceiver; - + // 记录原始的账号列表(用于对比账号变化等情况) private Account[] mOriAccounts; - + // 标记是否添加了新账号 private boolean mHasAddedAccount; @Override protected void onCreate(Bundle icicle) { super.onCreate(icicle); - /* using the app icon for navigation */ + // 使用应用图标进行导航,启用返回上级页面功能(ActionBar上显示返回箭头) getActionBar().setDisplayHomeAsUpEnabled(true); + // 从指定的XML资源文件加载偏好设置界面布局 addPreferencesFromResource(R.xml.preferences); + // 获取同步账号相关的偏好设置分类 mAccountCategory = (PreferenceCategory) findPreference(PREFERENCE_SYNC_ACCOUNT_KEY); + // 创建广播接收器实例 mReceiver = new GTaskReceiver(); IntentFilter filter = new IntentFilter(); + // 注册接收同步服务广播的过滤器,监听同步服务广播的特定动作 filter.addAction(GTaskSyncService.GTASK_SERVICE_BROADCAST_NAME); + // 注册广播接收器,用于接收同步服务相关广播 registerReceiver(mReceiver, filter); mOriAccounts = null; + // 加载设置界面头部视图 View header = LayoutInflater.from(this).inflate(R.layout.settings_header, null); + // 将头部视图添加到ListView(用于显示偏好设置的列表视图),并设置相关参数 getListView().addHeaderView(header, null, true); } @@ -92,19 +101,23 @@ public class NotesPreferenceActivity extends PreferenceActivity { protected void onResume() { super.onResume(); - // need to set sync account automatically if user has added a new - // account + // 如果用户添加了新账号,需要自动设置同步账号 if (mHasAddedAccount) { + // 获取当前的谷歌账号列表 Account[] accounts = getGoogleAccounts(); - if (mOriAccounts != null && accounts.length > mOriAccounts.length) { + // 判断原始账号列表不为空且当前账号数量比原始账号数量多(意味着可能添加了新账号) + if (mOriAccounts!= null && accounts.length > mOriAccounts.length) { + // 遍历新账号列表 for (Account accountNew : accounts) { boolean found = false; + // 遍历原始账号列表,对比新账号是否已经存在 for (Account accountOld : mOriAccounts) { if (TextUtils.equals(accountOld.name, accountNew.name)) { found = true; break; } } + // 如果新账号在原始账号中不存在,设置该新账号为同步账号,并跳出循环 if (!found) { setSyncAccount(accountNew.name); break; @@ -113,120 +126,164 @@ public class NotesPreferenceActivity extends PreferenceActivity { } } + // 刷新界面UI,更新相关显示内容(比如账号信息、同步按钮状态等) refreshUI(); } @Override protected void onDestroy() { - if (mReceiver != null) { + // 如果广播接收器不为空,注销广播接收器,避免内存泄漏等问题 + if (mReceiver!= null) { unregisterReceiver(mReceiver); } super.onDestroy(); } + /** + * 加载账号偏好设置相关内容,如创建账号偏好设置项、设置点击事件等。 + */ private void loadAccountPreference() { + // 移除账号分类下的所有已有偏好设置项 mAccountCategory.removeAll(); + // 创建一个新的偏好设置项(用于展示账号相关操作入口) Preference accountPref = new Preference(this); + // 获取当前设置的同步账号名称(如果有的话) final String defaultAccount = getSyncAccountName(this); + // 设置账号偏好设置项的标题 accountPref.setTitle(getString(R.string.preferences_account_title)); + // 设置账号偏好设置项的摘要内容 accountPref.setSummary(getString(R.string.preferences_account_summary)); + // 设置账号偏好设置项的点击监听器,处理点击事件逻辑 accountPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { public boolean onPreferenceClick(Preference preference) { + // 如果同步服务当前没有在同步中 if (!GTaskSyncService.isSyncing()) { + // 如果还没有设置过同步账号(账号名称为空) if (TextUtils.isEmpty(defaultAccount)) { - // the first time to set account + // 首次设置账号,弹出选择账号的对话框 showSelectAccountAlertDialog(); } else { - // if the account has already been set, we need to promp - // user about the risk + // 如果已经设置过账号,弹出确认修改账号的对话框(提示相关风险等) showChangeAccountConfirmAlertDialog(); } } else { + // 如果同步服务正在同步,显示提示信息告知用户不能修改账号 Toast.makeText(NotesPreferenceActivity.this, - R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT) + R.string.preferences_toast_cannot_change_account, Toast.LENGTH_SHORT) .show(); } return true; } }); + // 将账号偏好设置项添加到账号分类下 mAccountCategory.addPreference(accountPref); } + /** + * 加载同步按钮相关逻辑,包括设置按钮文本、点击事件以及同步状态显示文本等内容。 + */ private void loadSyncButton() { + // 获取同步按钮实例 Button syncButton = (Button) findViewById(R.id.preference_sync_button); + // 获取显示上次同步时间的文本视图实例 TextView lastSyncTimeView = (TextView) findViewById(R.id.prefenerece_sync_status_textview); - // set button state + // 根据同步服务是否正在同步来设置同步按钮的文本和点击事件逻辑 if (GTaskSyncService.isSyncing()) { + // 如果正在同步,按钮文本设置为取消同步 syncButton.setText(getString(R.string.preferences_button_sync_cancel)); + // 设置按钮点击事件,点击时取消同步操作 syncButton.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { GTaskSyncService.cancelSync(NotesPreferenceActivity.this); } }); } else { + // 如果没有同步,按钮文本设置为立即同步 syncButton.setText(getString(R.string.preferences_button_sync_immediately)); + // 设置按钮点击事件,点击时开始同步操作 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()) { + // 如果正在同步,显示同步进度相关信息 lastSyncTimeView.setText(GTaskSyncService.getProgressString()); lastSyncTimeView.setVisibility(View.VISIBLE); } else { + // 如果没有同步,获取上次同步时间 long lastSyncTime = getLastSyncTime(this); - if (lastSyncTime != 0) { + if (lastSyncTime!= 0) { + // 如果上次同步时间不为0,格式化时间并设置显示文本 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,隐藏该文本视图 lastSyncTimeView.setVisibility(View.GONE); } } } + /** + * 刷新整个界面UI,调用加载账号偏好设置和同步按钮相关逻辑的方法。 + */ private void refreshUI() { loadAccountPreference(); loadSyncButton(); } + /** + * 弹出选择账号的对话框,用于让用户选择要设置的同步账号等操作。 + */ private void showSelectAccountAlertDialog() { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + // 加载对话框标题视图布局 View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); + // 设置对话框标题文本 titleTextView.setText(getString(R.string.preferences_dialog_select_account_title)); 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 = accounts; mHasAddedAccount = false; if (accounts.length > 0) { + // 创建用于显示账号名称的字符序列数组(长度与账号列表长度一致) CharSequence[] items = new CharSequence[accounts.length]; final CharSequence[] itemMapping = items; int checkedItem = -1; int index = 0; + // 遍历账号列表,填充显示账号名称的数组,并记录当前已设置账号在数组中的位置(用于默认选中) 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) { @@ -237,16 +294,18 @@ public class NotesPreferenceActivity extends PreferenceActivity { }); } + // 加载添加账号相关视图布局(可能用于提示用户添加账号等操作) 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; Intent intent = new Intent("android.settings.ADD_ACCOUNT_SETTINGS"); intent.putExtra(AUTHORITIES_FILTER_KEY, new String[] { - "gmail-ls" + "gmail-ls" }); startActivityForResult(intent, -1); dialog.dismiss(); @@ -254,22 +313,30 @@ public class NotesPreferenceActivity extends PreferenceActivity { }); } + /** + * 弹出确认修改账号的对话框,提供修改账号、删除账号等操作选项供用户选择。 + */ private void showChangeAccountConfirmAlertDialog() { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); + // 加载对话框标题视图布局 View titleView = LayoutInflater.from(this).inflate(R.layout.account_dialog_title, null); TextView titleTextView = (TextView) titleView.findViewById(R.id.account_dialog_title); + // 设置对话框标题文本,包含当前已设置的同步账号名称 titleTextView.setText(getString(R.string.preferences_dialog_change_account_title, getSyncAccountName(this))); 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) { @@ -283,26 +350,40 @@ public class NotesPreferenceActivity extends PreferenceActivity { dialogBuilder.show(); } + /** + * 获取设备上的谷歌账号列表。 + * + * @return 谷歌账号数组 + */ private Account[] getGoogleAccounts() { AccountManager accountManager = AccountManager.get(this); return accountManager.getAccountsByType("com.google"); } + /** + * 设置同步账号,保存账号名称到偏好设置,并执行相关清理和更新操作。 + * + * @param account 要设置的同步账号名称 + */ 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) { + if (account!= null) { + // 如果账号不为空,将账号名称保存到偏好设置中 editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, account); } else { + // 如果账号为空,清除已保存的账号名称 editor.putString(PREFERENCE_SYNC_ACCOUNT_NAME, ""); } editor.commit(); - // clean up last sync time + // 清理上次同步时间(设置为0) setLastSyncTime(this, 0); - // clean up local gtask related info + // 清理本地与GTask相关的信息(在新线程中执行数据库更新操作) new Thread(new Runnable() { public void run() { ContentValues values = new ContentValues(); @@ -312,12 +393,16 @@ public class NotesPreferenceActivity extends PreferenceActivity { } }).start(); + // 显示设置账号成功的提示信息 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.Editor editor = settings.edit(); @@ -329,7 +414,7 @@ public class NotesPreferenceActivity extends PreferenceActivity { } editor.commit(); - // clean up local gtask related info + // 清理本地与GTask相关的信息(在新线程中执行数据库更新操作) new Thread(new Runnable() { public void run() { ContentValues values = new ContentValues(); @@ -340,49 +425,18 @@ public class NotesPreferenceActivity extends PreferenceActivity { }).start(); } + /** + * 获取当前设置的同步账号名称。 + * + * @param context 上下文环境 + * @return 同步账号名称,如果没有设置则返回空字符串 + */ 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); - SharedPreferences.Editor editor = settings.edit(); - editor.putLong(PREFERENCE_LAST_SYNC_TIME, time); - editor.commit(); - } - - public static long getLastSyncTime(Context context) { - SharedPreferences settings = context.getSharedPreferences(PREFERENCE_NAME, - Context.MODE_PRIVATE); - return settings.getLong(PREFERENCE_LAST_SYNC_TIME, 0); - } - - private class GTaskReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - refreshUI(); - if (intent.getBooleanExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_IS_SYNCING, false)) { - TextView syncStatus = (TextView) findViewById(R.id.prefenerece_sync_status_textview); - syncStatus.setText(intent - .getStringExtra(GTaskSyncService.GTASK_SERVICE_BROADCAST_PROGRESS_MSG)); - } - - } - } - - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case android.R.id.home: - Intent intent = new Intent(this, NotesListActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - return true; - default: - return false; - } - } -} +/** + * 设置上次同步时间到偏好设置中。 + * \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider.java b/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider.java index ec6f819..0b66738 100644 --- a/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider.java +++ b/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider.java @@ -32,24 +32,37 @@ import net.micode.notes.tool.ResourceParser; import net.micode.notes.ui.NoteEditActivity; import net.micode.notes.ui.NotesListActivity; +// NoteWidgetProvider抽象类继承自AppWidgetProvider,用于处理与笔记相关的桌面小部件逻辑 public abstract class NoteWidgetProvider extends AppWidgetProvider { + + // 定义一个字符串数组,用于指定从数据库查询时要获取的列名 + // 包含笔记的ID、背景颜色ID以及摘要信息等列 public static final String [] PROJECTION = new String [] { - NoteColumns.ID, - NoteColumns.BG_COLOR_ID, - NoteColumns.SNIPPET + NoteColumns.ID, + NoteColumns.BG_COLOR_ID, + NoteColumns.SNIPPET }; - public static final int COLUMN_ID = 0; - public static final int COLUMN_BG_COLOR_ID = 1; - public static final int COLUMN_SNIPPET = 2; + // 定义常量,表示PROJECTION数组中对应列的索引,方便后续从查询结果(如Cursor)中获取具体数据 + public static final int COLUMN_ID = 0; + public static final int COLUMN_BG_COLOR_ID = 1; + public static final int COLUMN_SNIPPET = 2; + // 定义一个私有静态常量,用于日志记录时标识当前类 private static final String TAG = "NoteWidgetProvider"; + // 重写AppWidgetProvider的onDeleted方法,当桌面小部件被删除时触发该方法 @Override public void onDeleted(Context context, int[] appWidgetIds) { + // 创建一个ContentValues对象,用于存储要更新的数据 ContentValues values = new ContentValues(); + // 将NoteColumns.WIDGET_ID对应的字段值设为AppWidgetManager.INVALID_APPWIDGET_ID,标记小部件已无效 values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); + + // 遍历被删除小部件的ID数组 for (int i = 0; i < appWidgetIds.length; i++) { + // 通过ContentResolver根据小部件ID条件去更新数据库中相关记录 + // 将对应的小部件ID标记为无效,Notes.CONTENT_NOTE_URI表示数据源 context.getContentResolver().update(Notes.CONTENT_NOTE_URI, values, NoteColumns.WIDGET_ID + "=?", @@ -57,7 +70,12 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { } } + // 私有方法,用于根据给定的小部件ID(widgetId)从内容提供器查询相关的笔记小部件信息 private Cursor getNoteWidgetInfo(Context context, int widgetId) { + // 通过ContentResolver进行查询操作 + // 查询的数据源是Notes.CONTENT_NOTE_URI,查询的列由PROJECTION指定 + // 筛选条件要求NoteColumns.WIDGET_ID等于传入的小部件ID且NoteColumns.PARENT_ID不等于特定的垃圾文件夹ID(Notes.ID_TRASH_FOLER) + // 最后返回查询得到的Cursor对象,如果没有符合条件的数据则返回null return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, PROJECTION, NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?", @@ -65,68 +83,95 @@ public abstract class NoteWidgetProvider extends AppWidgetProvider { null); } + // 对外可调用的更新小部件的方法,它内部调用另一个带有更多参数的update方法,并传入false作为隐私模式(privacyMode)的参数值 protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { update(context, appWidgetManager, appWidgetIds, false); } + // 具体执行小部件更新逻辑的方法,用于更新桌面小部件的显示内容等信息 private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, - boolean privacyMode) { + boolean privacyMode) { + // 遍历传入的小部件ID数组 for (int i = 0; i < appWidgetIds.length; i++) { - if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) { + // 只处理有效的小部件ID(不等于AppWidgetManager.INVALID_APPWIDGET_ID的情况) + if (appWidgetIds[i]!= AppWidgetManager.INVALID_APPWIDGET_ID) { + // 获取默认的背景ID,具体获取逻辑由ResourceParser.getDefaultBgId方法实现 int bgId = ResourceParser.getDefaultBgId(context); + // 初始化摘要信息为空字符串 String snippet = ""; + // 创建一个用于启动NoteEditActivity的Intent,设置相关标志位和额外数据 Intent intent = new Intent(context, NoteEditActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]); intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType()); + // 获取对应小部件的笔记信息Cursor Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]); - if (c != null && c.moveToFirst()) { + if (c!= null && c.moveToFirst()) { + // 如果查询结果中存在多条符合条件的数据(理论上小部件ID应该唯一对应一条记录),则记录错误日志并关闭Cursor,直接返回 if (c.getCount() > 1) { Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]); c.close(); return; } + // 从Cursor中获取摘要信息 snippet = c.getString(COLUMN_SNIPPET); + // 从Cursor中获取背景颜色ID bgId = c.getInt(COLUMN_BG_COLOR_ID); + // 向Intent中添加额外数据(用户ID等信息) intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); + // 设置Intent的动作,表示是查看操作 intent.setAction(Intent.ACTION_VIEW); } else { + // 如果没有查询到对应小部件的笔记信息,设置默认的摘要信息 snippet = context.getResources().getString(R.string.widget_havenot_content); + // 设置Intent的动作,表示是插入/编辑操作 intent.setAction(Intent.ACTION_INSERT_OR_EDIT); } - if (c != null) { + // 如果Cursor不为空,关闭它以释放资源 + if (c!= null) { c.close(); } + // 创建一个RemoteViews对象,用于构建小部件的界面显示,传入当前应用的包名和通过抽象方法获取的布局资源ID RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); + // 根据抽象方法获取的背景资源ID来设置小部件的背景图片资源 rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); + // 向Intent中添加额外的背景ID数据 intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); - /** - * Generate the pending intent to start host for the widget - */ + + // 用于生成点击小部件时启动对应Activity的PendingIntent,初始化为null PendingIntent pendingIntent = null; if (privacyMode) { + // 如果处于隐私模式,设置小部件文本显示特定的隐私提示信息 rv.setTextViewText(R.id.widget_text, context.getString(R.string.widget_under_visit_mode)); + // 创建一个用于启动NotesListActivity的PendingIntent,用于隐私模式下的点击响应 pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent( context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); } else { + // 正常情况下,设置小部件文本显示摘要信息(snippet) rv.setTextViewText(R.id.widget_text, snippet); + // 创建一个用于启动NoteEditActivity(由前面intent定义)的PendingIntent,用于正常模式下的点击响应 pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent, PendingIntent.FLAG_UPDATE_CURRENT); } + // 为小部件文本设置点击PendingIntent,实现点击交互功能 rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); + // 通过AppWidgetManager更新小部件的实际显示内容 appWidgetManager.updateAppWidget(appWidgetIds[i], rv); } } } + // 抽象方法,要求子类实现该方法,根据传入的背景ID获取对应的背景资源ID,用于设置小部件的背景显示资源 protected abstract int getBgResourceId(int bgId); + // 抽象方法,子类需实现此方法来返回小部件对应的布局资源ID,用于构建小部件的界面布局 protected abstract int getLayoutId(); + // 抽象方法,需要子类实现,用于返回小部件的类型相关信息,具体的类型定义由业务逻辑决定,在小部件相关处理中可能根据类型做不同操作 protected abstract int getWidgetType(); -} +} \ No newline at end of file diff --git a/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_2x.java b/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_2x.java index adcb2f7..bff815d 100644 --- a/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_2x.java +++ b/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_2x.java @@ -24,22 +24,34 @@ import net.micode.notes.data.Notes; import net.micode.notes.tool.ResourceParser; +// NoteWidgetProvider_2x类继承自NoteWidgetProvider抽象类,用于实现特定尺寸(可能是2倍尺寸相关,从类名推测)的笔记桌面小部件相关逻辑 public class NoteWidgetProvider_2x extends NoteWidgetProvider { + + // 重写onUpdate方法,当桌面小部件需要更新时会触发该方法 + // 这里调用了父类(NoteWidgetProvider)的update方法来执行具体的更新操作 @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.update(context, appWidgetManager, appWidgetIds); } + // 实现父类(NoteWidgetProvider)中定义的抽象方法getLayoutId + // 返回用于该特定小部件(2x尺寸相关)的布局资源ID,对应布局资源为R.layout.widget_2x @Override protected int getLayoutId() { return R.layout.widget_2x; } + // 实现父类(NoteWidgetProvider)中定义的抽象方法getBgResourceId + // 根据传入的背景ID(bgId),通过ResourceParser.WidgetBgResources.getWidget2xBgResource方法来获取对应的2x尺寸小部件的背景资源ID + // 用于设置该特定小部件的背景显示资源 @Override protected int getBgResourceId(int bgId) { return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); } + // 实现父类(NoteWidgetProvider)中定义的抽象方法getWidgetType + // 返回该小部件的类型标识,此处返回值为Notes.TYPE_WIDGET_2X,表示这是2x尺寸类型的小部件 + // 在整个小部件相关的处理逻辑中,可能会根据这个类型做不同的操作(例如不同类型小部件显示不同样式、功能等) @Override protected int getWidgetType() { return Notes.TYPE_WIDGET_2X; diff --git a/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_4x.java b/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_4x.java index c12a02e..14394cc 100644 --- a/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_4x.java +++ b/src/Notes-master/src/net/micode/notes/widget/NoteWidgetProvider_4x.java @@ -24,23 +24,34 @@ import net.micode.notes.data.Notes; import net.micode.notes.tool.ResourceParser; +// NoteWidgetProvider_4x类继承自NoteWidgetProvider抽象类,它主要负责实现特定的(推测为4倍尺寸相关,从类名判断)笔记桌面小部件相关的具体逻辑。 public class NoteWidgetProvider_4x extends NoteWidgetProvider { + + // 重写onUpdate方法,该方法会在桌面小部件需要进行更新操作时被触发。 + // 在这里,它调用了父类(NoteWidgetProvider)的update方法来执行实际的小部件更新逻辑,利用了父类中已经定义好的通用更新流程。 @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { super.update(context, appWidgetManager, appWidgetIds); } + // 重写父类(NoteWidgetProvider)中定义的抽象方法getLayoutId,用于返回此特定小部件(4x尺寸相关)对应的布局资源ID。 + // 通过返回R.layout.widget_4x,指定了该小部件所使用的布局文件,这个布局文件应该是按照4x尺寸的设计需求进行定制的。 + // 注意:这里方法的修饰符应该是@Override,原代码中缺少该注解,可能会导致代码逻辑与预期不符,若后续在父类中修改了抽象方法的签名等情况,编译器无法准确检查此处是否正确重写。 protected int getLayoutId() { return R.layout.widget_4x; } + // 重写父类(NoteWidgetProvider)中定义的抽象方法getBgResourceId,根据传入的背景ID(bgId)来获取对应的背景资源ID。 + // 具体是通过调用ResourceParser.WidgetBgResources.getWidget4xBgResource方法来获取适用于4x尺寸小部件的背景资源,以用于设置该小部件的背景显示效果。 @Override protected int getBgResourceId(int bgId) { return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); } + // 重写父类(NoteWidgetProvider)中定义的抽象方法getWidgetType,用于返回该小部件的类型标识。 + // 这里返回Notes.TYPE_WIDGET_4X,表示这是一个4x尺寸类型的小部件,在整个小部件相关的处理流程中,可能会依据这个类型标识进行不同的业务处理,比如展现不同的样式、启用不同的功能等。 @Override protected int getWidgetType() { return Notes.TYPE_WIDGET_4X; } -} +} \ No newline at end of file