diff --git a/src/net/micode/notes/ui/AlarmAlertActivity.java b/src/net/micode/notes/ui/AlarmAlertActivity.java index 85723be..0ba30ee 100644 --- a/src/net/micode/notes/ui/AlarmAlertActivity.java +++ b/src/net/micode/notes/ui/AlarmAlertActivity.java @@ -14,145 +14,170 @@ * limitations under the License. */ -package net.micode.notes.ui; - -import android.app.Activity; -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.content.DialogInterface.OnDismissListener; -import android.content.Intent; -import android.media.AudioManager; -import android.media.MediaPlayer; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.Bundle; -import android.os.PowerManager; -import android.provider.Settings; -import android.view.Window; -import android.view.WindowManager; - -import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.tool.DataUtils; - -import java.io.IOException; - - -public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { - private long mNoteId; - private String mSnippet; - private static final int SNIPPET_PREW_MAX_LEN = 60; - MediaPlayer mPlayer; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - requestWindowFeature(Window.FEATURE_NO_TITLE); - - final Window win = getWindow(); - win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); - - if (!isScreenOn()) { - win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON - | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON - | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON - | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); - } - - Intent intent = getIntent(); - - try { - mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); - mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); - mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, - SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) - : mSnippet; - } catch (IllegalArgumentException e) { - e.printStackTrace(); - return; - } - - mPlayer = new MediaPlayer(); - if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { - showActionDialog(); - playAlarmSound(); - } else { - finish(); - } - } - - private boolean isScreenOn() { - PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); - return pm.isScreenOn(); - } - - 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) { - mPlayer.setAudioStreamType(silentModeStreams); - } else { - mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); - } - try { - mPlayer.setDataSource(this, url); - mPlayer.prepare(); - mPlayer.setLooping(true); - mPlayer.start(); - } catch (IllegalArgumentException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (SecurityException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalStateException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IOException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - private void showActionDialog() { - AlertDialog.Builder dialog = new AlertDialog.Builder(this); - dialog.setTitle(R.string.app_name); - dialog.setMessage(mSnippet); - dialog.setPositiveButton(R.string.notealert_ok, this); - if (isScreenOn()) { - dialog.setNegativeButton(R.string.notealert_enter, this); - } - dialog.show().setOnDismissListener(this); - } - - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case DialogInterface.BUTTON_NEGATIVE: - Intent intent = new Intent(this, NoteEditActivity.class); - intent.setAction(Intent.ACTION_VIEW); - intent.putExtra(Intent.EXTRA_UID, mNoteId); - startActivity(intent); - break; - default: - break; - } - } - - public void onDismiss(DialogInterface dialog) { - stopAlarmSound(); - finish(); - } - - private void stopAlarmSound() { - if (mPlayer != null) { - mPlayer.stop(); - mPlayer.release(); - mPlayer = null; - } - } -} + package net.micode.notes.ui; + + import android.app.Activity; + import android.app.AlertDialog; + import android.content.Context; + import android.content.DialogInterface; + import android.content.DialogInterface.OnClickListener; + import android.content.DialogInterface.OnDismissListener; + import android.content.Intent; + import android.media.AudioManager; + import android.media.MediaPlayer; + import android.media.RingtoneManager; + import android.net.Uri; + import android.os.Bundle; + import android.os.PowerManager; + import android.provider.Settings; + import android.view.Window; + import android.view.WindowManager; + + import net.micode.notes.R; + import net.micode.notes.data.Notes; + import net.micode.notes.tool.DataUtils; + + import java.io.IOException; + + // AlarmAlertActivity类继承自Activity,同时实现了OnClickListener和OnDismissListener接口, + // 主要用于处理闹钟提醒相关的功能,例如展示提醒界面、播放提醒声音以及响应用户在提醒对话框中的操作等 + public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { + private long mNoteId; // 用于存储对应笔记的唯一标识符(ID) + private String mSnippet; // 用于存储笔记内容的摘要信息,方便展示给用户查看 + private static final int SNIPPET_PREW_MAX_LEN = 60; // 定义笔记摘要信息展示的最大长度,超过该长度会进行截断处理 + MediaPlayer mPlayer; // 用于播放闹钟提醒声音的MediaPlayer对象实例 + + // onCreate方法是Android系统中Activity的生命周期方法,在Activity创建时被调用,在此方法中完成Activity的初始化工作,例如设置窗口属性、获取传递过来的意图数据、准备播放声音以及展示提醒对话框等操作 + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + requestWindowFeature(Window.FEATURE_NO_TITLE); // 请求去除Activity的标题栏,使界面更加简洁,专注于闹钟提醒内容展示 + + final Window win = getWindow(); + win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); // 设置窗口的标志位,使得窗口在屏幕锁定的情况下也能够显示出来,确保用户能及时看到提醒 + + if (!isScreenOn()) { + // 如果屏幕当前处于关闭状态,添加以下多个标志位,用于控制屏幕相关的行为,比如保持屏幕常亮、点亮屏幕、允许在屏幕锁定时进行操作等 + win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON + | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON + | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); + } + + Intent intent = getIntent(); // 获取启动该Activity的Intent对象,用于获取传递过来的相关数据 + + try { + // 从Intent传递的数据中获取笔记的ID,这里假设数据的格式是特定的路径形式,从中提取出对应位置的元素并转换为长整型 + mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); + // 通过DataUtils工具类的方法,根据笔记ID从内容解析器中获取笔记的摘要内容 + mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); + // 如果摘要内容的长度超过了预设的最大长度,进行截断处理,并添加特定的提示字符串,否则直接使用原摘要内容 + mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN? mSnippet.substring(0, + SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) + : mSnippet; + } catch (IllegalArgumentException e) { + e.printStackTrace(); // 如果在获取数据过程中出现参数异常,打印异常堆栈信息方便调试 + return; // 直接返回,不再继续执行后续可能依赖正确数据的操作 + } + + mPlayer = new MediaPlayer(); // 创建一个新的MediaPlayer对象,用于后续播放闹钟提醒声音 + // 通过DataUtils工具类判断该笔记在数据库中是否可见(满足特定的类型条件等),如果可见则展示操作对话框并播放闹钟声音,否则直接结束该Activity + if (DataUtils.visibleInNoteDatabase(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { + showActionDialog(); + playAlarmSound(); + } else { + finish(); + } + } + + // isScreenOn方法用于判断当前设备的屏幕是否处于开启状态, + // 通过获取系统的PowerManager服务,并调用其isScreenOn方法来进行判断,返回一个布尔值表示屏幕的开启情况 + private boolean isScreenOn() { + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + return pm.isScreenOn(); + } + + // playAlarmSound方法用于播放闹钟提醒的声音,主要涉及获取铃声资源、根据系统设置配置音频流类型、准备并启动声音播放等操作, + // 如果在过程中出现各种异常情况,会打印相应的异常堆栈信息方便排查问题 + private void playAlarmSound() { + // 通过RingtoneManager获取系统中设置的默认闹钟铃声的Uri,作为播放声音的数据源 + Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); + + // 从系统设置中获取与静音模式相关的音频流设置值,用于后续判断如何配置MediaPlayer的音频流类型 + int silentModeStreams = Settings.System.getInt(getContentResolver(), + Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); + + // 根据静音模式相关设置判断是否使用特定的音频流类型,如果满足条件则设置MediaPlayer的音频流类型为获取到的设置值,否则设置为常规的闹钟音频流类型 + if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM))!= 0) { + mPlayer.setAudioStreamType(silentModeStreams); + } else { + mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); + } + try { + // 为MediaPlayer设置声音数据源,即之前获取到的闹钟铃声的Uri + mPlayer.setDataSource(this, url); + mPlayer.prepare(); // 准备MediaPlayer进行播放,例如加载音频数据等操作 + mPlayer.setLooping(true); // 设置声音循环播放,以持续提醒用户 + mPlayer.start(); // 启动MediaPlayer开始播放闹钟提醒声音 + } catch (IllegalArgumentException e) { + // 如果在设置数据源等操作中出现参数非法异常,打印异常堆栈信息 + e.printStackTrace(); + } catch (SecurityException e) { + // 如果出现安全相关异常,例如没有权限访问某些资源,打印异常堆栈信息 + e.printStackTrace(); + } catch (IllegalStateException e) { + // 如果MediaPlayer处于不正确的状态而导致操作异常,例如在未准备好的情况下尝试播放,打印异常堆栈信息 + e.printStackTrace(); + } catch (IOException e) { + // 如果在读取数据源等I/O操作中出现异常,例如文件不存在或无法读取,打印异常堆栈信息 + e.printStackTrace(); + } + } + // showActionDialog方法用于创建并显示一个包含操作按钮的对话框,用于向用户展示笔记摘要信息以及提供相应的操作选项, + // 例如确认提醒或者进入笔记详情等操作,同时设置对话框关闭的监听器为当前类(因为实现了OnDismissListener接口) + private void showActionDialog() { + AlertDialog.Builder dialog = new AlertDialog.Builder(this); // 创建一个AlertDialog的构建器,传入当前Activity上下文 + dialog.setTitle(R.string.app_name); // 设置对话框的标题为应用名称,通常用于标识该提醒所属的应用 + dialog.setMessage(mSnippet); // 设置对话框显示的消息内容为之前获取到的笔记摘要信息 + dialog.setPositiveButton(R.string.notealert_ok, this); // 设置对话框的确定按钮,点击按钮的响应逻辑由当前类实现的OnClickListener接口处理 + if (isScreenOn()) { + // 如果屏幕处于开启状态,添加一个进入按钮,点击该按钮可进入笔记编辑等相关界面,同样点击响应逻辑由当前类处理 + dialog.setNegativeButton(R.string.notealert_enter, this); + } + dialog.show().setOnDismissListener(this); // 显示对话框,并设置对话框关闭的监听器为当前类实例,以便在对话框关闭时执行相应逻辑 + } + + // onClick方法是实现OnClickListener接口的方法,用于处理对话框中按钮点击的事件, + // 根据点击的按钮不同执行相应的操作,例如点击进入按钮时会跳转到笔记编辑页面等 + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_NEGATIVE: + // 当点击Negative按钮(通常是进入相关界面的按钮)时,创建一个Intent用于启动NoteEditActivity, + // 设置相应的动作和传递笔记的ID作为额外数据,然后启动该Activity实现界面跳转 + Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_UID, mNoteId); + startActivity(intent); + break; + default: + break; + } + } + + // onDismiss方法是实现OnDismissListener接口的方法,用于处理对话框关闭的事件, + // 在对话框关闭时,会调用stopAlarmSound方法停止正在播放的闹钟声音,并结束当前Activity + public void onDismiss(DialogInterface dialog) { + stopAlarmSound(); + finish(); + } + + // stopAlarmSound方法用于停止正在播放的闹钟声音, + // 首先判断MediaPlayer对象是否为空,如果不为空则执行停止播放、释放资源的操作,并将MediaPlayer对象置为null,释放相关内存资源 + private void stopAlarmSound() { + if (mPlayer!= null) { + mPlayer.stop(); + mPlayer.release(); + mPlayer = null; + } + } + } \ No newline at end of file diff --git a/src/net/micode/notes/ui/AlarmInitReceiver.java b/src/net/micode/notes/ui/AlarmInitReceiver.java index f221202..de98dc0 100644 --- a/src/net/micode/notes/ui/AlarmInitReceiver.java +++ b/src/net/micode/notes/ui/AlarmInitReceiver.java @@ -14,52 +14,74 @@ * limitations under the License. */ -package net.micode.notes.ui; + package net.micode.notes.ui; -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.ContentUris; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; - -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.NoteColumns; - - -public class AlarmInitReceiver extends BroadcastReceiver { - - private static final String [] PROJECTION = new String [] { - NoteColumns.ID, - NoteColumns.ALERTED_DATE - }; - - private static final int COLUMN_ID = 0; - private static final int COLUMN_ALERTED_DATE = 1; - - @Override - public void onReceive(Context context, Intent intent) { - long currentDate = System.currentTimeMillis(); - Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, - PROJECTION, - NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, - new String[] { String.valueOf(currentDate) }, - null); - - if (c != null) { - if (c.moveToFirst()) { - do { - long alertDate = c.getLong(COLUMN_ALERTED_DATE); - Intent sender = new Intent(context, AlarmReceiver.class); - sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); - PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); - AlarmManager alermManager = (AlarmManager) context + import android.app.AlarmManager; + import android.app.PendingIntent; + import android.content.BroadcastReceiver; + import android.content.ContentUris; + import android.content.Context; + import android.content.Intent; + import android.database.Cursor; + + import net.micode.notes.data.Notes; + import net.micode.notes.data.Notes.NoteColumns; + + // AlarmInitReceiver类继承自BroadcastReceiver,用于接收系统广播消息, + // 其主要功能是根据笔记的提醒日期等条件,初始化设置闹钟提醒相关的PendingIntent和AlarmManager, + // 以便在指定时间触发相应的提醒操作。 + public class AlarmInitReceiver extends BroadcastReceiver { + + // 定义一个字符串数组用于指定查询数据库时需要获取的列信息,这里只获取笔记的ID和提醒日期这两列数据。 + private static final String [] PROJECTION = new String [] { + NoteColumns.ID, + NoteColumns.ALERTED_DATE + }; + + // 定义常量表示查询结果中笔记ID列对应的索引位置,方便后续从Cursor中获取相应数据,初始化为0,表示在结果集中的第一列(从0开始计数)。 + private static final int COLUMN_ID = 0; + // 定义常量表示查询结果中提醒日期列对应的索引位置,方便后续从Cursor中获取相应数据,初始化为1,表示在结果集中的第二列(从0开始计数)。 + private static final int COLUMN_ALERTED_DATE = 1; + + // onReceive方法是BroadcastReceiver类中必须实现的抽象方法,当该广播接收器接收到相应广播时会被调用, + // 在此方法中会查询数据库中符合条件的笔记记录,并为每条记录设置对应的闹钟提醒。 + @Override + public void onReceive(Context context, Intent intent) { + // 获取当前系统的时间戳(以毫秒为单位),用于后续作为条件筛选出提醒日期大于当前时间的笔记记录,即还未到提醒时间但需要设置提醒的笔记。 + long currentDate = System.currentTimeMillis(); + // 通过内容解析器查询数据库,从指定的笔记内容Uri(Notes.CONTENT_NOTE_URI)中获取数据, + // 使用之前定义的PROJECTION指定要获取的列,通过条件筛选出提醒日期大于当前时间且类型为常规笔记(Notes.TYPE_NOTE)的记录, + // 条件中的占位符通过后面的字符串数组进行替换,最后一个参数null表示不进行排序等额外操作。 + 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) { + // 将游标移动到查询结果的第一条记录位置,如果有记录则返回true,可以开始遍历结果集。 + if (c.moveToFirst()) { + do { + // 从游标中获取提醒日期这一列的数据(以长整型表示),对应之前定义的COLUMN_ALERTED_DATE索引位置, + // 该日期就是这条笔记设置的提醒时间点(以毫秒为单位的时间戳)。 + long alertDate = c.getLong(COLUMN_ALERTED_DATE); + // 创建一个Intent对象,用于指定当闹钟触发时要启动的广播接收器,这里指定为AlarmReceiver类, + // 并且通过ContentUris.withAppendedId方法将当前笔记的ID附加到对应的内容Uri上,以便后续能识别是哪个笔记的提醒触发了。 + Intent sender = new Intent(context, AlarmReceiver.class); + sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); + // 创建一个PendingIntent对象,用于包装之前创建的Intent,使得该Intent可以在合适的时机(闹钟触发时)被系统安全地调用, + // 参数中的0表示请求码,这里暂设为0,最后的0表示默认的标志位,也可根据具体需求设置不同的标志位来控制其行为。 + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); + // 获取系统的AlarmManager服务,用于设置闹钟相关的操作,如指定触发时间、关联PendingIntent等。 + AlarmManager alermManager = (AlarmManager) context .getSystemService(Context.ALARM_SERVICE); - alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); - } while (c.moveToNext()); - } - c.close(); - } - } -} + // 使用AlarmManager的set方法设置一个一次性的闹钟提醒,指定闹钟类型为RTC_WAKEUP(在指定的绝对时间唤醒设备并触发提醒,即使设备处于睡眠状态), + // 传入之前获取到的提醒日期作为触发时间,以及关联对应的PendingIntent,当到达提醒时间时,系统会通过该PendingIntent触发相应的广播操作。 + alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); + } while (c.moveToNext()); // 将游标移动到下一条记录位置,如果还有下一条记录则继续循环执行上述设置闹钟的操作。 + } + c.close(); // 关闭游标,释放相关资源,避免内存泄漏等问题,因为查询操作完成后不再需要游标了。 + } + } + } \ No newline at end of file diff --git a/src/net/micode/notes/ui/AlarmReceiver.java b/src/net/micode/notes/ui/AlarmReceiver.java index 54e503b..97b5064 100644 --- a/src/net/micode/notes/ui/AlarmReceiver.java +++ b/src/net/micode/notes/ui/AlarmReceiver.java @@ -14,17 +14,22 @@ * limitations under the License. */ -package net.micode.notes.ui; + package net.micode.notes.ui; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -public class AlarmReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - intent.setClass(context, AlarmAlertActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(intent); - } -} + import android.content.BroadcastReceiver; + import android.content.Context; + import android.content.Intent; + + // 定义一个广播接收器类,继承自BroadcastReceiver,用于接收系统广播等相关消息 + public class AlarmReceiver extends BroadcastReceiver { + // 重写onReceive方法,当接收到广播时会执行此方法 + @Override + public void onReceive(Context context, Intent intent) { + // 设置intent要启动的目标Activity为AlarmAlertActivity.class,这样后续启动时就会启动对应的Activity + intent.setClass(context, AlarmAlertActivity.class); + // 给intent添加一个标志,表明启动的Activity是一个新任务,常用于从非Activity的上下文中启动Activity的场景 + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // 通过传入的上下文对象启动对应的Activity,这里利用了之前设置好的intent相关信息 + context.startActivity(intent); + } + } \ No newline at end of file diff --git a/src/net/micode/notes/ui/DateTimePickerDialog.java b/src/net/micode/notes/ui/DateTimePickerDialog.java index 2c47ba4..4d3933a 100644 --- a/src/net/micode/notes/ui/DateTimePickerDialog.java +++ b/src/net/micode/notes/ui/DateTimePickerDialog.java @@ -14,77 +14,108 @@ * limitations under the License. */ -package net.micode.notes.ui; + package net.micode.notes.ui; -import java.util.Calendar; - -import net.micode.notes.R; -import net.micode.notes.ui.DateTimePicker; -import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener; - -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.DialogInterface.OnClickListener; -import android.text.format.DateFormat; -import android.text.format.DateUtils; - -public class DateTimePickerDialog extends AlertDialog implements OnClickListener { - - private Calendar mDate = Calendar.getInstance(); - private boolean mIs24HourView; - private OnDateTimeSetListener mOnDateTimeSetListener; - private DateTimePicker mDateTimePicker; - - public interface OnDateTimeSetListener { - void OnDateTimeSet(AlertDialog dialog, long date); - } - - public DateTimePickerDialog(Context context, long date) { - super(context); - mDateTimePicker = new DateTimePicker(context); - setView(mDateTimePicker); - mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { - public void onDateTimeChanged(DateTimePicker view, int year, int month, - int dayOfMonth, int hourOfDay, int minute) { - mDate.set(Calendar.YEAR, year); - mDate.set(Calendar.MONTH, month); - mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); - mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); - mDate.set(Calendar.MINUTE, minute); - updateTitle(mDate.getTimeInMillis()); - } - }); - mDate.setTimeInMillis(date); - mDate.set(Calendar.SECOND, 0); - mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); - setButton(context.getString(R.string.datetime_dialog_ok), this); - setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); - set24HourView(DateFormat.is24HourFormat(this.getContext())); - updateTitle(mDate.getTimeInMillis()); - } - - public void set24HourView(boolean is24HourView) { - mIs24HourView = is24HourView; - } - - public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { - mOnDateTimeSetListener = callBack; - } - - private void updateTitle(long date) { - int flag = - DateUtils.FORMAT_SHOW_YEAR | - DateUtils.FORMAT_SHOW_DATE | - DateUtils.FORMAT_SHOW_TIME; - flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; - setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); - } - - public void onClick(DialogInterface arg0, int arg1) { - if (mOnDateTimeSetListener != null) { - mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); - } - } - -} \ No newline at end of file + import java.util.Calendar; + + import net.micode.notes.R; + import net.micode.notes.ui.DateTimePicker; + import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener; + + import android.app.AlertDialog; + import android.content.Context; + import android.content.DialogInterface; + import android.content.DialogInterface.OnClickListener; + import android.text.format.DateFormat; + import android.text.format.DateUtils; + + // 自定义的日期时间选择对话框类,继承自AlertDialog,用于以对话框的形式展示日期时间选择界面,并处理相关交互逻辑 + public class DateTimePickerDialog extends AlertDialog implements OnClickListener { + + // 用于存储当前选择的日期时间信息的Calendar对象,初始化为当前系统时间对应的实例,后续会根据用户选择进行更新 + private Calendar mDate = Calendar.getInstance(); + // 用于标记是否处于24小时制视图,初始会根据系统设置进行判断赋值,后续也可通过方法进行设置改变 + private boolean mIs24HourView; + // 定义一个接口类型的成员变量,用于监听用户完成日期时间选择并点击确定按钮后的操作,外部类可实现此接口来响应最终的选择结果 + private OnDateTimeSetListener mOnDateTimeSetListener; + // 包含日期时间选择具体交互控件的DateTimePicker实例,用于在对话框中展示可操作的日期时间选择界面 + private DateTimePicker mDateTimePicker; + + // 定义一个接口,外部类需要实现此接口,当用户在对话框中完成日期时间选择并点击确定按钮后,会回调此接口中的方法,传入对话框本身以及最终选择的日期时间(以毫秒为单位的时间戳) + public interface OnDateTimeSetListener { + void OnDateTimeSet(AlertDialog dialog, long date); + } + + // 构造函数,创建一个DateTimePickerDialog实例,传入上下文以及初始的日期时间(以毫秒为单位的时间戳),完成对话框的初始化设置 + public DateTimePickerDialog(Context context, long date) { + super(context); + // 创建一个DateTimePicker实例,用于在对话框中展示日期时间选择的具体交互界面 + mDateTimePicker = new DateTimePicker(context); + // 将DateTimePicker实例设置为对话框的视图内容,这样对话框中就会显示日期时间选择相关的UI组件 + setView(mDateTimePicker); + + // 为DateTimePicker设置日期时间改变监听器,当用户在DateTimePicker中操作改变了日期、时间等内容时,会触发此监听器中的方法 + mDateTimePicker.setOnDateTimeChangedListener(new OnDateTimeChangedListener() { + public void onDateTimeChanged(DateTimePicker view, int year, int month, + int dayOfMonth, int hourOfDay, int minute) { + // 根据用户选择的新日期时间信息,更新内部存储的日期时间(mDate)对应的各个字段,如年、月、日、小时、分钟等 + mDate.set(Calendar.YEAR, year); + mDate.set(Calendar.MONTH, month); + mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); + mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); + mDate.set(Calendar.MINUTE, minute); + // 根据更新后的日期时间,调用updateTitle方法更新对话框标题显示,使其展示当前选择的日期时间信息 + updateTitle(mDate.getTimeInMillis()); + } + }); + + // 设置对话框初始显示的日期时间,先将传入的时间戳设置到内部的Calendar对象(mDate)中,同时将秒数设置为0,保证初始时间更规整 + mDate.setTimeInMillis(date); + mDate.set(Calendar.SECOND, 0); + // 将DateTimePicker的当前日期时间设置为和内部存储的初始日期时间一致,保证界面显示和内部数据的同步 + mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); + + // 设置对话框的确定按钮文本以及点击监听器,点击确定按钮时会触发当前类(实现了OnClickListener接口)的onClick方法 + setButton(context.getString(R.string.datetime_dialog_ok), this); + // 设置对话框的取消按钮文本以及点击监听器为null,表示点击取消按钮直接关闭对话框,不做额外处理 + setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); + + // 根据系统设置判断是否为24小时制视图,并设置到内部相应的变量以及对应的UI显示状态(例如是否显示AM/PM选择器等) + set24HourView(DateFormat.is24HourFormat(this.getContext())); + // 初始更新一次对话框标题,展示初始的日期时间信息 + updateTitle(mDate.getTimeInMillis()); + } + + // 设置是否为24小时制视图的方法,传入布尔值参数,更新内部的标记变量,并会影响DateTimePicker中相关UI组件的显示情况(如AM/PM选择器的可见性等) + 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; // 显示时间(小时、分钟等) + + // 根据是否为24小时制视图,添加对应的时间格式标志,用于控制最终显示的时间格式(24小时制还是12小时制带AM/PM) + flag |= mIs24HourView? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_12HOUR; + + // 使用DateUtils工具类的formatDateTime方法,按照设置好的格式标志,将传入的日期时间转换为字符串,并设置为对话框的标题内容 + setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); + } + + // 实现OnClickListener接口的onClick方法,当用户点击对话框中的按钮(确定按钮,因为在构造函数中设置了当前类为确定按钮的点击监听器)时会触发此方法 + // 如果设置了日期时间选择完成后的监听器(mOnDateTimeSetListener),则调用其OnDateTimeSet方法,传入对话框本身以及当前选择的日期时间(以毫秒为单位的时间戳),通知外部类用户已完成选择操作 + public void onClick(DialogInterface arg0, int arg1) { + if (mOnDateTimeSetListener!= null) { + mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); + } + } + + } \ No newline at end of file diff --git a/src/net/micode/notes/ui/DropdownMenu.java b/src/net/micode/notes/ui/DropdownMenu.java index 613dc74..5ef1027 100644 --- a/src/net/micode/notes/ui/DropdownMenu.java +++ b/src/net/micode/notes/ui/DropdownMenu.java @@ -14,48 +14,67 @@ * limitations under the License. */ -package net.micode.notes.ui; + package net.micode.notes.ui; -import android.content.Context; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.PopupMenu; -import android.widget.PopupMenu.OnMenuItemClickListener; - -import net.micode.notes.R; - -public class DropdownMenu { - private Button mButton; - private PopupMenu mPopupMenu; - private Menu mMenu; - - public DropdownMenu(Context context, Button button, int menuId) { - mButton = button; - mButton.setBackgroundResource(R.drawable.dropdown_icon); - mPopupMenu = new PopupMenu(context, mButton); - mMenu = mPopupMenu.getMenu(); - mPopupMenu.getMenuInflater().inflate(menuId, mMenu); - mButton.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - mPopupMenu.show(); - } - }); - } - - public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { - if (mPopupMenu != null) { - mPopupMenu.setOnMenuItemClickListener(listener); - } - } - - public MenuItem findItem(int id) { - return mMenu.findItem(id); - } - - public void setTitle(CharSequence title) { - mButton.setText(title); - } -} + import android.content.Context; + import android.view.Menu; + import android.view.MenuItem; + import android.view.View; + import android.view.View.OnClickListener; + import android.widget.Button; + import android.widget.PopupMenu; + import android.widget.PopupMenu.OnMenuItemClickListener; + + import net.micode.notes.R; + + // 自定义的下拉菜单类,用于创建和管理一个基于按钮点击弹出的菜单,方便在安卓界面中实现下拉式的功能菜单交互 + public class DropdownMenu { + + // 用于显示下拉菜单的按钮控件,点击此按钮会弹出对应的菜单,外部传入并在类内部进行相关操作 + private Button mButton; + // 实际的PopupMenu实例,它管理着弹出菜单的显示、隐藏以及菜单项相关操作等逻辑 + private PopupMenu mPopupMenu; + // 对应的Menu实例,用于获取、操作具体的菜单项,通过PopupMenu获取并进行后续的查找、设置等操作 + private Menu mMenu; + + // 构造函数,用于创建一个DropdownMenu实例,传入上下文、用于触发弹出菜单的按钮以及菜单资源ID(用于定义菜单项等信息) + public DropdownMenu(Context context, Button button, int menuId) { + mButton = button; + // 设置按钮的背景资源为指定的下拉图标资源(通常用于提示用户此按钮可点击弹出菜单),这里从R.drawable.dropdown_icon获取对应的图标资源 + mButton.setBackgroundResource(R.drawable.dropdown_icon); + + // 创建一个PopupMenu实例,关联传入的上下文以及触发弹出的按钮,这样弹出菜单会基于此按钮的位置等属性进行显示 + mPopupMenu = new PopupMenu(context, mButton); + + // 获取PopupMenu对应的Menu实例,后续可以通过这个实例来操作具体的菜单项,例如添加、查找、设置菜单项属性等 + mMenu = mPopupMenu.getMenu(); + + // 使用菜单填充器(从PopupMenu获取),根据传入的菜单资源ID(通常在XML资源文件中定义了菜单项的文本、图标等信息),将菜单项填充到Menu实例中,完成菜单的初始化构建 + mPopupMenu.getMenuInflater().inflate(menuId, mMenu); + + // 为按钮设置点击监听器,当按钮被点击时,触发内部的onClick方法,在该方法中调用PopupMenu的show方法来显示弹出菜单 + mButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mPopupMenu.show(); + } + }); + } + + // 设置下拉菜单中菜单项的点击监听器方法,传入实现了OnMenuItemClickListener接口的实例,用于响应各个菜单项被点击后的操作逻辑 + // 如果PopupMenu实例不为空,则将传入的监听器设置给PopupMenu,这样当用户点击菜单项时,对应的回调方法就会被触发 + public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { + if (mPopupMenu!= null) { + mPopupMenu.setOnMenuItemClickListener(listener); + } + } + + // 根据传入的菜单项ID,在已构建的Menu实例中查找对应的MenuItem实例,方便外部类获取菜单项进行进一步的操作,例如获取菜单项的文本、图标、设置是否可用等 + public MenuItem findItem(int id) { + return mMenu.findItem(id); + } + + // 设置按钮显示的文本内容,可用于给下拉菜单按钮设置一个标题之类的提示性文字,方便用户了解此下拉菜单的大致功能等信息 + public void setTitle(CharSequence title) { + mButton.setText(title); + } + } \ No newline at end of file diff --git a/src/net/micode/notes/ui/FoldersListAdapter.java b/src/net/micode/notes/ui/FoldersListAdapter.java index 96b77da..8559cde 100644 --- a/src/net/micode/notes/ui/FoldersListAdapter.java +++ b/src/net/micode/notes/ui/FoldersListAdapter.java @@ -14,67 +14,90 @@ * limitations under the License. */ -package net.micode.notes.ui; + package net.micode.notes.ui; -import android.content.Context; -import android.database.Cursor; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CursorAdapter; -import android.widget.LinearLayout; -import android.widget.TextView; - -import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.NoteColumns; - - -public class FoldersListAdapter extends CursorAdapter { - public static final String [] PROJECTION = { - NoteColumns.ID, - NoteColumns.SNIPPET - }; - - public static final int ID_COLUMN = 0; - public static final int NAME_COLUMN = 1; - - public FoldersListAdapter(Context context, Cursor c) { - super(context, c); - // TODO Auto-generated constructor stub - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return new FolderListItem(context); - } - - @Override - public void bindView(View view, Context context, Cursor cursor) { - if (view instanceof FolderListItem) { - String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context + import android.content.Context; + import android.database.Cursor; + import android.view.View; + import android.view.ViewGroup; + import android.widget.CursorAdapter; + import android.widget.LinearLayout; + import android.widget.TextView; + + import net.micode.notes.R; + import net.micode.notes.data.Notes; + import net.micode.notes.data.Notes.NoteColumns; + + // FoldersListAdapter类继承自CursorAdapter,用于将数据库游标(Cursor)中的数据适配到特定的视图上, + // 在这里主要是为了展示文件夹相关信息列表,比如文件夹名称等,方便在列表视图等UI组件中显示。 + public class FoldersListAdapter extends CursorAdapter { + + // 定义一个字符串数组,用于指定从数据库查询时需要获取的列名,这里只获取了"ID"和"SNIPPET"两列数据, + // 通常会对应数据库中存储文件夹相关信息的列,后续用于填充视图展示给用户。 + public static final String [] PROJECTION = { + NoteColumns.ID, + NoteColumns.SNIPPET + }; + + // 定义一个常量,表示在查询结果游标(Cursor)中"ID"列所在的索引位置,方便后续通过游标获取对应列的数据。 + public static final int ID_COLUMN = 0; + // 定义一个常量,表示在查询结果游标(Cursor)中"NAME"(此处实际对应SNIPPET列,可能用于表示文件夹名称相关内容)列所在的索引位置。 + public static final int NAME_COLUMN = 1; + + // 构造函数,接收上下文(Context)和数据库游标(Cursor)作为参数,调用父类(CursorAdapter)的构造函数进行初始化操作。 + public FoldersListAdapter(Context context, Cursor c) { + super(context, c); + // TODO Auto-generated constructor stub,这里可以添加一些自定义的初始化逻辑,如果有需要的话,当前为空。 + } + + // 重写CursorAdapter的newView方法,此方法用于创建一个新的视图(View)对象,该视图将用于显示游标中对应的数据项。 + // 在这里返回一个自定义的FolderListItem实例,它继承自LinearLayout,是每个列表项对应的视图布局。 + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return new FolderListItem(context); + } + + // 重写CursorAdapter的bindView方法,此方法用于将游标(Cursor)中当前位置的数据绑定到指定的视图(View)上, + // 也就是填充视图中的各个控件(如TextView等),使其显示对应的数据内容。 + @Override + public void bindView(View view, Context context, Cursor cursor) { + if (view instanceof FolderListItem) { + // 根据游标中获取的文件夹ID判断是否是根文件夹(通过和Notes.ID_ROOT_FOLDER比较), + // 如果是根文件夹,则获取对应的字符串资源(通常是用于显示"上级文件夹"之类表示根文件夹含义的文本)作为文件夹名称, + // 如果不是根文件夹,则从游标中获取对应列(NAME_COLUMN)的数据作为文件夹名称。 + String folderName = (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER)? context .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); - ((FolderListItem) view).bind(folderName); - } - } - - public String getFolderName(Context context, int position) { - Cursor cursor = (Cursor) getItem(position); - return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER) ? context + // 调用FolderListItem的bind方法,将获取到的文件夹名称设置到对应的TextView控件中,完成数据绑定显示操作。 + ((FolderListItem) view).bind(folderName); + } + } + + // 定义一个方法,用于根据给定的位置(position)获取对应的文件夹名称。 + // 首先通过getItem方法(继承自CursorAdapter)获取该位置对应的游标(Cursor)对象, + // 然后同样根据游标中文件夹ID判断是否是根文件夹来确定返回的文件夹名称内容。 + public String getFolderName(Context context, int position) { + Cursor cursor = (Cursor) getItem(position); + return (cursor.getLong(ID_COLUMN) == Notes.ID_ROOT_FOLDER)? context .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); - } - - private class FolderListItem extends LinearLayout { - private TextView mName; - - public FolderListItem(Context context) { - super(context); - inflate(context, R.layout.folder_list_item, this); - mName = (TextView) findViewById(R.id.tv_folder_name); - } - - public void bind(String name) { - mName.setText(name); - } - } - -} + } + + // 定义一个内部类FolderListItem,继承自LinearLayout,代表每个文件夹列表项的具体视图布局, + // 内部包含了用于显示文件夹名称的TextView控件,并提供了相应的方法来设置显示的文本内容。 + private class FolderListItem extends LinearLayout { + private TextView mName; + + // 构造函数,接收上下文(Context)作为参数,调用父类(LinearLayout)的构造函数进行初始化, + // 并通过inflate方法加载对应的布局文件(R.layout.folder_list_item)到当前视图中, + // 然后获取布局中用于显示文件夹名称的TextView控件实例。 + public FolderListItem(Context context) { + super(context); + inflate(context, R.layout.folder_list_item, this); + mName = (TextView) findViewById(R.id.tv_folder_name); + } + + // 定义一个方法,用于将传入的文件夹名称(name)设置到内部的TextView控件(mName)中,实现文本显示更新。 + public void bind(String name) { + mName.setText(name); + } + } + } \ No newline at end of file diff --git a/src/net/micode/notes/ui/NoteEditText.java b/src/net/micode/notes/ui/NoteEditText.java index 2afe2a8..dc4b7ed 100644 --- a/src/net/micode/notes/ui/NoteEditText.java +++ b/src/net/micode/notes/ui/NoteEditText.java @@ -14,204 +14,269 @@ * limitations under the License. */ -package net.micode.notes.ui; + package net.micode.notes.ui; -import android.content.Context; -import android.graphics.Rect; -import android.text.Layout; -import android.text.Selection; -import android.text.Spanned; -import android.text.TextUtils; -import android.text.style.URLSpan; -import android.util.AttributeSet; -import android.util.Log; -import android.view.ContextMenu; -import android.view.KeyEvent; -import android.view.MenuItem; -import android.view.MenuItem.OnMenuItemClickListener; -import android.view.MotionEvent; -import android.widget.EditText; - -import net.micode.notes.R; - -import java.util.HashMap; -import java.util.Map; - -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:" ; - - private static final Map sSchemaActionResMap = new HashMap(); - static { - sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); - sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); - sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); - } - - /** - * Call by the {@link NoteEditActivity} to delete or add edit text - */ - public interface OnTextViewChangeListener { - /** - * Delete current edit text when {@link KeyEvent#KEYCODE_DEL} happens - * and the text is null - */ - void onEditTextDelete(int index, String text); - - /** - * Add edit text after current edit text when {@link KeyEvent#KEYCODE_ENTER} - * happen - */ - void onEditTextEnter(int index, String text); - - /** - * Hide or show item option when text change - */ - void onTextChange(int index, boolean hasText); - } - - private OnTextViewChangeListener mOnTextViewChangeListener; - - public NoteEditText(Context context) { - super(context, null); - mIndex = 0; - } - - public void setIndex(int index) { - mIndex = index; - } - - public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { - mOnTextViewChangeListener = listener; - } - - public NoteEditText(Context context, AttributeSet attrs) { - super(context, attrs, android.R.attr.editTextStyle); - } - - public NoteEditText(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - // TODO Auto-generated constructor stub - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - - int x = (int) event.getX(); - int y = (int) event.getY(); - x -= getTotalPaddingLeft(); - y -= getTotalPaddingTop(); - x += getScrollX(); - y += getScrollY(); - - Layout layout = getLayout(); - int line = layout.getLineForVertical(y); - int off = layout.getOffsetForHorizontal(line, x); - Selection.setSelection(getText(), off); - break; - } - - return super.onTouchEvent(event); - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - switch (keyCode) { - case KeyEvent.KEYCODE_ENTER: - if (mOnTextViewChangeListener != null) { - return false; - } - break; - case KeyEvent.KEYCODE_DEL: - mSelectionStartBeforeDelete = getSelectionStart(); - break; - default: - break; - } - return super.onKeyDown(keyCode, event); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - switch(keyCode) { - case KeyEvent.KEYCODE_DEL: - if (mOnTextViewChangeListener != null) { - if (0 == mSelectionStartBeforeDelete && mIndex != 0) { - mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); - return true; - } - } else { - Log.d(TAG, "OnTextViewChangeListener was not seted"); - } - break; - case KeyEvent.KEYCODE_ENTER: - if (mOnTextViewChangeListener != null) { - int selectionStart = getSelectionStart(); - String text = getText().subSequence(selectionStart, length()).toString(); - setText(getText().subSequence(0, selectionStart)); - mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); - } else { - Log.d(TAG, "OnTextViewChangeListener was not seted"); - } - break; - default: - break; - } - return super.onKeyUp(keyCode, event); - } - - @Override - protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { - if (mOnTextViewChangeListener != null) { - if (!focused && TextUtils.isEmpty(getText())) { - mOnTextViewChangeListener.onTextChange(mIndex, false); - } else { - mOnTextViewChangeListener.onTextChange(mIndex, true); - } - } - super.onFocusChanged(focused, direction, previouslyFocusedRect); - } - - @Override - protected void onCreateContextMenu(ContextMenu menu) { - if (getText() instanceof Spanned) { - int selStart = getSelectionStart(); - int selEnd = getSelectionEnd(); - - int min = Math.min(selStart, selEnd); - int max = Math.max(selStart, selEnd); - - final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); - if (urls.length == 1) { - int defaultResId = 0; - for(String schema: sSchemaActionResMap.keySet()) { - if(urls[0].getURL().indexOf(schema) >= 0) { - defaultResId = sSchemaActionResMap.get(schema); - break; - } - } - - if (defaultResId == 0) { - defaultResId = R.string.note_link_other; - } - - menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( - new OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - // goto a new intent - urls[0].onClick(NoteEditText.this); - return true; - } - }); - } - } - super.onCreateContextMenu(menu); - } -} + import android.content.Context; + import android.graphics.Rect; + import android.text.Layout; + import android.text.Selection; + import android.text.Spanned; + import android.text.TextUtils; + import android.text.style.URLSpan; + import android.util.AttributeSet; + import android.util.Log; + import android.view.ContextMenu; + import android.view.KeyEvent; + import android.view.MenuItem; + import android.view.MenuItem.OnMenuItemClickListener; + import android.view.MotionEvent; + import android.widget.EditText; + + import net.micode.notes.R; + + import java.util.HashMap; + import java.util.Map; + + // NoteEditText类继承自EditText,是一个自定义的文本编辑框控件,在原生EditText的基础上添加了一些特定的功能和交互逻辑, + // 例如处理特定按键事件、触摸事件以及上下文菜单相关操作等,用于笔记编辑等相关场景。 + public class NoteEditText extends EditText { + private static final String TAG = "NoteEditText"; + // 用于记录当前文本编辑框的索引位置,可能在多个编辑框组成的列表等场景下用于区分不同的编辑框个体。 + private int mIndex; + // 用于记录在删除操作前文本选择的起始位置,方便后续判断删除相关逻辑,例如是否删除整个编辑框内容等情况。 + private int mSelectionStartBeforeDelete; + + // 定义表示电话号码链接的协议头(scheme)字符串,用于识别文本中是否包含电话号码链接。 + private static final String SCHEME_TEL = "tel:" ; + // 定义表示网页链接(HTTP协议)的协议头字符串,用于识别文本中的网页链接。 + private static final String SCHEME_HTTP = "http:" ; + // 定义表示电子邮件链接的协议头字符串,用于识别文本中的邮件链接。 + private static final String SCHEME_EMAIL = "mailto:" ; + + // 创建一个HashMap,用于存储不同协议头(scheme)与对应的字符串资源ID的映射关系, + // 这些字符串资源ID通常用于在界面上显示对应链接类型的操作提示文本等内容。 + private static final Map sSchemaActionResMap = new HashMap(); + static { + // 将不同协议头与对应的字符串资源ID添加到映射表中,方便后续根据链接类型查找对应的显示文本资源。 + sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); + sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); + sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); + } + + // 定义一个接口,用于与外部类进行交互,外部类实现此接口可以监听该文本编辑框内文本相关的变化事件, + // 例如文本删除、回车键按下添加新编辑框以及文本有无变化等情况,并做出相应处理。 + public interface OnTextViewChangeListener { + /** + * 当按下删除键({@link KeyEvent#KEYCODE_DEL})且文本内容为空时,删除当前编辑文本框, + * 外部类实现此方法可处理对应删除逻辑,比如从列表中移除该编辑框等操作。 + */ + void onEditTextDelete(int index, String text); + + /** + * 当按下回车键({@link KeyEvent#KEYCODE_ENTER})时,在当前编辑文本框之后添加新的编辑文本框, + * 外部类实现此方法可进行创建新编辑框并添加到合适位置等相关操作。 + */ + void onEditTextEnter(int index, String text); + + /** + * 当文本内容发生变化时,隐藏或显示相关的菜单项选项等,外部类可根据文本有无来控制对应UI元素的显示隐藏状态。 + */ + void onTextChange(int index, boolean hasText); + } + + // 用于存储实现了OnTextViewChangeListener接口的实例,以便在文本编辑框相关事件发生时通知外部类进行相应处理。 + private OnTextViewChangeListener mOnTextViewChangeListener; + + // 构造函数,只传入上下文(Context)参数,调用父类构造函数创建实例,并初始化索引位置为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; + } + + // 构造函数,传入上下文(Context)和属性集(AttributeSet)参数,按照安卓系统默认的文本编辑框样式(通过android.R.attr.editTextStyle指定)创建实例, + // 通常用于在XML布局文件中定义该编辑框并解析相关属性进行初始化的场景。 + public NoteEditText(Context context, AttributeSet attrs) { + super(context, attrs, android.R.attr.editTextStyle); + } + + // 构造函数,传入上下文(Context)、属性集(AttributeSet)和默认样式(defStyle)参数,创建编辑框实例, + // 可用于更灵活地自定义编辑框的样式和属性,当前构造函数中没有添加额外的初始化逻辑(TODO处可按需添加)。 + 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坐标 + int x = (int) event.getX(); + // 获取触摸点的原始Y坐标 + int y = (int) event.getY(); + // 减去左边的总内边距,将坐标转换为相对于文本内容区域的坐标 + x -= getTotalPaddingLeft(); + // 减去上边的总内边距,同样将坐标转换为相对于文本内容区域的坐标 + y -= getTotalPaddingTop(); + // 加上滚动的X偏移量,考虑文本滚动情况,获取准确的相对于文本内容的坐标 + x += getScrollX(); + // 加上滚动的Y偏移量,获取准确的相对于文本内容的坐标 + y += getScrollY(); + + // 获取文本的布局信息对象,用于后续根据坐标获取对应文本位置等操作 + Layout layout = getLayout(); + // 根据触摸点的垂直坐标(Y坐标)获取对应的文本行数 + int line = layout.getLineForVertical(y); + // 根据触摸点的水平坐标(X坐标)以及所在行数,获取对应的文本字符偏移量(即文本中的位置索引) + int off = layout.getOffsetForHorizontal(line, x); + // 根据获取到的字符偏移量设置文本的选择范围,这里只设置了一个位置,相当于将光标定位到该位置 + Selection.setSelection(getText(), off); + break; + } + + // 调用父类的onTouchEvent方法继续处理其他触摸事件相关逻辑,保证原生的触摸行为也能正常执行 + return super.onTouchEvent(event); + } + + // 重写父类的onKeyDown方法,用于处理按键按下事件,在这里主要是针对回车键(KEYCODE_ENTER)和删除键(KEYCODE_DEL)按下时做一些前期记录等准备工作, + // 具体的业务逻辑处理在按键抬起(onKeyUp)方法中进行。 + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_ENTER: + // 如果设置了文本变化监听器(mOnTextViewChangeListener不为空),表示外部类关注回车键按下事件, + // 这里返回false,让后续的按键抬起(onKeyUp)事件能继续处理相关逻辑,例如添加新编辑框等操作。 + if (mOnTextViewChangeListener!= null) { + return false; + } + break; + case KeyEvent.KEYCODE_DEL: + // 当按下删除键时,记录当前文本选择的起始位置,方便后续判断是否删除整个编辑框内容等情况。 + mSelectionStartBeforeDelete = getSelectionStart(); + break; + default: + break; + } + // 调用父类的onKeyDown方法继续处理其他按键按下相关逻辑,保证原生的按键按下行为也能正常执行 + return super.onKeyDown(keyCode, event); + } + + // 重写父类的onKeyUp方法,用于处理按键抬起事件,在这里针对回车键(KEYCODE_ENTER)和删除键(KEYCODE_DEL)抬起时, + // 根据不同情况执行相应的业务逻辑,例如删除编辑框、添加新编辑框等操作,前提是设置了文本变化监听器(mOnTextViewChangeListener不为空)。 + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + switch(keyCode) { + case KeyEvent.KEYCODE_DEL: + if (mOnTextViewChangeListener!= null) { + // 判断如果文本选择的起始位置为0(通常表示光标在文本开头)且当前编辑框索引不为0(说明不是第一个编辑框), + // 则调用文本变化监听器的onEditTextDelete方法通知外部类删除当前编辑框,然后返回true,表示已经处理了该按键事件。 + if (0 == mSelectionStartBeforeDelete && mIndex!= 0) { + mOnTextViewChangeListener.onEditTextDelete(mIndex, getText().toString()); + return true; + } + } else { + // 如果没有设置文本变化监听器,则打印日志提示信息,表示监听器未设置,无法处理删除相关逻辑。 + Log.d(TAG, "OnTextViewChangeListener was not seted"); + } + break; + case KeyEvent.KEYCODE_ENTER: + if (mOnTextViewChangeListener!= null) { + // 获取当前文本选择的起始位置,用于后续截取文本操作。 + int selectionStart = getSelectionStart(); + // 获取从文本选择起始位置到文本末尾的子字符串,可能是要添加到新编辑框的内容(如果有需要的话)。 + String text = getText().subSequence(selectionStart, length()).toString(); + // 将当前编辑框的文本内容截取为从开头到选择起始位置的部分,相当于清空了选择位置之后的文本(可能是要添加到新编辑框的部分)。 + setText(getText().subSequence(0, selectionStart)); + // 调用文本变化监听器的onEditTextEnter方法通知外部类在当前编辑框之后添加新编辑框,并传入新编辑框索引和可能要添加的文本内容。 + mOnTextViewChangeListener.onEditTextEnter(mIndex + 1, text); + } else { + // 如果没有设置文本变化监听器,则打印日志提示信息,表示监听器未设置,无法处理回车键按下添加新编辑框相关逻辑。 + Log.d(TAG, "OnTextViewChangeListener was not seted"); + } + break; + default: + break; + } + // 调用父类的onKeyUp方法继续处理其他按键抬起相关逻辑,保证原生的按键抬起行为也能正常执行 + return super.onKeyUp(keyCode, event); + } + + // 重写父类的onFocusChanged方法,用于处理焦点变化事件,在这里根据焦点状态(是否获得焦点)以及文本内容是否为空, + // 通过文本变化监听器(mOnTextViewChangeListener)通知外部类文本有无情况,以便外部类进行相应的UI元素显示隐藏等操作。 + @Override + protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { + if (mOnTextViewChangeListener!= null) { + if (!focused && TextUtils.isEmpty(getText())) { + // 如果失去焦点且文本内容为空,调用文本变化监听器的onTextChange方法通知外部类文本为空的情况,参数hasText为false。 + mOnTextViewChangeListener.onTextChange(mIndex, false); + } else { + // 如果获得焦点或者文本内容不为空,调用文本变化监听器的onTextChange方法通知外部类文本有内容的情况,参数hasText为true。 + mOnTextViewChangeListener.onTextChange(mIndex, true); + } + } + super.onFocusChanged(focused, direction, previouslyFocusedRect); + } + + // 重写父类的onCreateContextMenu方法,用于创建上下文菜单(长按文本等操作弹出的菜单), + // 在这里主要是检查当前选中的文本中是否包含URL链接(通过URLSpan判断),如果包含且只有一个链接, + // 则根据链接的协议头(scheme)查找对应的字符串资源ID,添加一个菜单项用于执行链接相关操作(如打开网页、拨打电话等)。 + @Override + protected void onCreateContextMenu(ContextMenu menu) { + if (getText() instanceof Spanned) { + // 获取当前文本选择的起始位置 + int selStart = getSelectionStart(); + // 获取当前文本选择的结束位置 + int selEnd = getSelectionEnd(); + + // 获取选择起始位置和结束位置中的较小值,用于后续范围判断等操作 + int min = Math.min(selStart, selEnd); + // 获取选择起始位置和结束位置中的较大值,用于后续范围判断等操作 + int max = Math.max(selStart, selEnd); + + // 获取在选择文本范围内的所有URLSpan对象(即包含的链接信息),返回一个数组,可能包含多个链接(但这里后续只处理长度为1的情况)。 + final URLSpan[] urls = ((Spanned) getText()).getSpans(min, max, URLSpan.class); + if (urls.length == 1) { + int defaultResId = 0; + for(String schema: sSchemaActionResMap.keySet()) { + // 遍历存储协议头与字符串资源ID映射关系的集合,检查链接的协议头是否匹配已定义的协议头(如tel、http、mailto等) + if(urls[0].getURL().indexOf(schema) >= 0) { + // 如果匹配,获取对应的字符串资源ID,用于设置菜单项显示的文本内容。 + defaultResId = sSchemaActionResMap.get(schema); + break; + } + } + + if (defaultResId == 0) { + // 如果没有匹配到已知的协议头,设置一个默认的字符串资源ID,表示其他类型链接。 + defaultResId = R.string.note_link_other; + } + + // 添加一个菜单项到上下文菜单中,菜单项的ID、顺序、分组等参数暂设为0(可根据实际需求调整),显示的文本内容根据获取到的字符串资源ID获取, + // 并设置菜单项的点击监听器,当点击该菜单项时,通过URLSpan的onClick方法执行链接相关操作(如打开网页、拨打电话等)。 + menu.add(0, 0, 0, defaultResId).setOnMenuItemClickListener( + new OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + // goto a new intent + urls[0].onClick(NoteEditText.this); + return true; + } + }); + } + } + super.onCreateContextMenu(menu); + } + } \ No newline at end of file diff --git a/src/net/micode/notes/ui/NoteItemData.java b/src/net/micode/notes/ui/NoteItemData.java index 0f5a878..f6c282a 100644 --- a/src/net/micode/notes/ui/NoteItemData.java +++ b/src/net/micode/notes/ui/NoteItemData.java @@ -14,211 +14,320 @@ * limitations under the License. */ -package net.micode.notes.ui; - -import android.content.Context; -import android.database.Cursor; -import android.text.TextUtils; - -import net.micode.notes.data.Contact; -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.tool.DataUtils; - - -public class NoteItemData { - static final String [] PROJECTION = new String [] { - NoteColumns.ID, - NoteColumns.ALERTED_DATE, - NoteColumns.BG_COLOR_ID, - NoteColumns.CREATED_DATE, - NoteColumns.HAS_ATTACHMENT, - NoteColumns.MODIFIED_DATE, - NoteColumns.NOTES_COUNT, - NoteColumns.PARENT_ID, - NoteColumns.SNIPPET, - NoteColumns.TYPE, - NoteColumns.WIDGET_ID, - NoteColumns.WIDGET_TYPE, - }; - - private static final int ID_COLUMN = 0; - private static final int ALERTED_DATE_COLUMN = 1; - private static final int BG_COLOR_ID_COLUMN = 2; - private static final int CREATED_DATE_COLUMN = 3; - private static final int HAS_ATTACHMENT_COLUMN = 4; - private static final int MODIFIED_DATE_COLUMN = 5; - private static final int NOTES_COUNT_COLUMN = 6; - private static final int PARENT_ID_COLUMN = 7; - private static final int SNIPPET_COLUMN = 8; - private static final int TYPE_COLUMN = 9; - private static final int WIDGET_ID_COLUMN = 10; - private static final int WIDGET_TYPE_COLUMN = 11; - - private long mId; - private long mAlertDate; - private int mBgColorId; - private long mCreatedDate; - private boolean mHasAttachment; - private long mModifiedDate; - private int mNotesCount; - private long mParentId; - private String mSnippet; - private int mType; - private int mWidgetId; - private int mWidgetType; - private String mName; - private String mPhoneNumber; - - private boolean mIsLastItem; - private boolean mIsFirstItem; - private boolean mIsOnlyOneItem; - private boolean mIsOneNoteFollowingFolder; - private boolean mIsMultiNotesFollowingFolder; - - public NoteItemData(Context context, Cursor cursor) { - mId = cursor.getLong(ID_COLUMN); - mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); - mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); - mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN); - mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0) ? true : false; - mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN); - mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); - mParentId = cursor.getLong(PARENT_ID_COLUMN); - mSnippet = cursor.getString(SNIPPET_COLUMN); - mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace( - NoteEditActivity.TAG_UNCHECKED, ""); - mType = cursor.getInt(TYPE_COLUMN); - mWidgetId = cursor.getInt(WIDGET_ID_COLUMN); - mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); - - mPhoneNumber = ""; - if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { - mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); - if (!TextUtils.isEmpty(mPhoneNumber)) { - mName = Contact.getContact(context, mPhoneNumber); - if (mName == null) { - mName = mPhoneNumber; - } - } - } - - if (mName == null) { - mName = ""; - } - checkPostion(cursor); - } - - private void checkPostion(Cursor cursor) { - mIsLastItem = cursor.isLast() ? true : false; - mIsFirstItem = cursor.isFirst() ? true : false; - mIsOnlyOneItem = (cursor.getCount() == 1); - mIsMultiNotesFollowingFolder = false; - mIsOneNoteFollowingFolder = false; - - if (mType == Notes.TYPE_NOTE && !mIsFirstItem) { - int position = cursor.getPosition(); - if (cursor.moveToPrevious()) { - if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER - || cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) { - if (cursor.getCount() > (position + 1)) { - mIsMultiNotesFollowingFolder = true; - } else { - mIsOneNoteFollowingFolder = true; - } - } - if (!cursor.moveToNext()) { - throw new IllegalStateException("cursor move to previous but can't move back"); - } - } - } - } - - public boolean isOneFollowingFolder() { - return mIsOneNoteFollowingFolder; - } - - 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; - } - - public long getId() { - return mId; - } - - public long getAlertDate() { - return mAlertDate; - } - - public long getCreatedDate() { - return mCreatedDate; - } - + package net.micode.notes.ui; + + import android.content.Context; + import android.database.Cursor; + import android.text.TextUtils; + + import net.micode.notes.data.Contact; + import net.micode.notes.data.Notes; + import net.micode.notes.data.Notes.NoteColumns; + import net.micode.notes.tool.DataUtils; + + // NoteItemData类用于从数据库游标(Cursor)中提取笔记相关的数据信息,并进行一些必要的处理和状态判断, + // 方便在应用中以对象的形式使用和传递笔记的各项属性数据,例如笔记的ID、创建日期、是否有附件等信息。 + public class NoteItemData { + + // 定义一个字符串数组,用于指定从数据库查询笔记相关信息时需要获取的列名,涵盖了笔记的多个属性字段, + // 后续可通过游标(Cursor)根据这些列名获取对应的数据来填充当前类的各个成员变量。 + static final String [] PROJECTION = new String [] { + NoteColumns.ID, + NoteColumns.ALERTED_DATE, + NoteColumns.BG_COLOR_ID, + NoteColumns.CREATED_DATE, + NoteColumns.HAS_ATTACHMENT, + NoteColumns.MODIFIED_DATE, + NoteColumns.NOTES_COUNT, + NoteColumns.PARENT_ID, + NoteColumns.SNIPPET, + NoteColumns.TYPE, + NoteColumns.WIDGET_ID, + NoteColumns.WIDGET_TYPE, + }; + + // 定义一个常量,表示在查询结果游标(Cursor)中"ID"列所在的索引位置,方便后续通过游标获取对应列的数据。 + private static final int ID_COLUMN = 0; + // 定义一个常量,表示在查询结果游标(Cursor)中"ALERTED_DATE"列所在的索引位置,用于获取笔记的提醒日期数据。 + private static final int ALERTED_DATE_COLUMN = 1; + // 定义一个常量,表示在查询结果游标(Cursor)中"BG_COLOR_ID"列所在的索引位置,用于获取笔记的背景颜色ID数据。 + private static final int BG_COLOR_ID_COLUMN = 2; + // 定义一个常量,表示在查询结果游标(Cursor)中"CREATED_DATE"列所在的索引位置,用于获取笔记的创建日期数据。 + private static final int CREATED_DATE_COLUMN = 3; + // 定义一个常量,表示在查询结果游标(Cursor)中"HAS_ATTACHMENT"列所在的索引位置,用于判断笔记是否有附件。 + private static final int HAS_ATTACHMENT_COLUMN = 4; + // 定义一个常量,表示在查询结果游标(Cursor)中"MODIFIED_DATE"列所在的索引位置,用于获取笔记的修改日期数据。 + private static final int MODIFIED_DATE_COLUMN = 5; + // 定义一个常量,表示在查询结果游标(Cursor)中"NOTES_COUNT"列所在的索引位置,可能用于获取与笔记相关的数量信息(具体含义取决于业务逻辑)。 + private static final int NOTES_COUNT_COLUMN = 6; + // 定义一个常量,表示在查询结果游标(Cursor)中"PARENT_ID"列所在的索引位置,用于获取笔记所属父级的ID(例如所属文件夹的ID等)。 + private static final int PARENT_ID_COLUMN = 7; + // 定义一个常量,表示在查询结果游标(Cursor)中"SNIPPET"列所在的索引位置,通常用于获取笔记的摘要、简短描述等文本内容。 + private static final int SNIPPET_COLUMN = 8; + // 定义一个常量,表示在查询结果游标(Cursor)中"TYPE"列所在的索引位置,用于获取笔记的类型信息(例如普通笔记、系统笔记等不同类型的区分)。 + private static final int TYPE_COLUMN = 9; + // 定义一个常量,表示在查询结果游标(Cursor)中"WIDGET_ID"列所在的索引位置,可能与笔记在桌面小部件等相关功能中的标识ID有关。 + private static final int WIDGET_ID_COLUMN = 10; + // 定义一个常量,表示在查询结果游标(Cursor)中"WIDGET_TYPE"列所在的索引位置,可能用于区分不同类型的桌面小部件相关信息(如果有涉及的话)。 + private static final int WIDGET_TYPE_COLUMN = 11; + + // 用于存储笔记的ID,从数据库游标中获取对应列的数据进行赋值,代表该笔记在系统中的唯一标识。 + private long mId; + // 用于存储笔记的提醒日期,从数据库游标获取对应数据,可用于判断笔记是否设置了提醒以及具体的提醒时间点。 + private long mAlertDate; + // 用于存储笔记的背景颜色ID,根据游标数据赋值,可能用于在界面上显示笔记时设置对应的背景颜色。 + private int mBgColorId; + // 用于存储笔记的创建日期,通过游标获取,记录笔记最初被创建的时间信息。 + private long mCreatedDate; + // 用于标记笔记是否有附件,根据游标中对应列的数据转换为布尔值进行存储(大于0表示有附件,赋值为true,否则为false)。 + private boolean mHasAttachment; + // 用于存储笔记的修改日期,从游标获取相应数据,可用于跟踪笔记最后一次被修改的时间。 + private long mModifiedDate; + // 用于存储与笔记相关的数量信息(具体含义取决于业务逻辑,可能是关联的子笔记数量等情况),从游标获取对应整数值进行赋值。 + private int mNotesCount; + // 用于存储笔记所属父级的ID(比如所属文件夹的ID),从游标获取相应长整数值,方便判断笔记的归属关系等。 + private long mParentId; + // 用于存储笔记的摘要、简短描述等文本内容,从游标获取字符串数据,并进行一些特定字符串替换操作后赋值,去除可能存在的特定标记字符。 + private String mSnippet; + // 用于存储笔记的类型信息,从游标获取对应整数来表示不同类型(例如普通笔记、系统笔记等不同分类),便于后续根据类型进行不同的业务逻辑处理。 + private int mType; + // 用于存储可能与笔记在桌面小部件等相关功能中的标识ID,从游标获取对应整数值进行赋值(具体用途取决于相关小部件功能实现)。 + private int mWidgetId; + // 用于存储可能用于区分不同类型桌面小部件相关信息的类型值(如果有涉及桌面小部件相关业务逻辑的话),从游标获取对应整数进行赋值。 + private int mWidgetType; + // 用于存储与笔记相关的联系人姓名信息,如果笔记属于通话记录文件夹等相关情况,会尝试获取对应的联系人姓名,初始化为空字符串。 + private String mName; + // 用于存储与笔记相关的电话号码信息,如果笔记属于通话记录文件夹,会尝试获取对应的电话号码,初始化为空字符串。 + private String mPhoneNumber; + + // 用于标记当前笔记数据对应的笔记是否是列表中的最后一项,根据游标是否处于最后一条记录来判断赋值。 + private boolean mIsLastItem; + // 用于标记当前笔记数据对应的笔记是否是列表中的第一项,根据游标是否处于第一条记录来判断赋值。 + private boolean mIsFirstItem; + // 用于标记当前笔记数据对应的笔记是否是列表中唯一的一项,通过判断游标获取的总记录数是否为1来赋值。 + private boolean mIsOnlyOneItem; + // 用于标记当前笔记数据对应的笔记是否是某文件夹下仅跟随的一个笔记(具体判断逻辑在后续方法中实现),初始化为false。 + private boolean mIsOneNoteFollowingFolder; + // 用于标记当前笔记数据对应的笔记是否是某文件夹下跟随的多个笔记之一(具体判断逻辑在后续方法中实现),初始化为false。 + private boolean mIsMultiNotesFollowingFolder; + + // 构造函数,接收上下文(Context)和数据库游标(Cursor)作为参数,用于从游标中提取笔记相关的各项数据信息并进行初始化操作, + // 同时还会进行一些与笔记位置、关联情况相关的状态判断。 + public NoteItemData(Context context, Cursor cursor) { + // 从游标中获取笔记的ID数据,赋值给对应的成员变量mId。 + mId = cursor.getLong(ID_COLUMN); + // 从游标中获取笔记的提醒日期数据,赋值给mAlertDate成员变量。 + mAlertDate = cursor.getLong(ALERTED_DATE_COLUMN); + // 从游标中获取笔记的背景颜色ID数据,赋值给mBgColorId成员变量。 + mBgColorId = cursor.getInt(BG_COLOR_ID_COLUMN); + // 从游标中获取笔记的创建日期数据,赋值给mCreatedDate成员变量。 + mCreatedDate = cursor.getLong(CREATED_DATE_COLUMN); + // 根据游标中获取的表示是否有附件的数据(整数值),转换为布尔值后赋值给mHasAttachment成员变量,大于0表示有附件,赋值为true,否则为false。 + mHasAttachment = (cursor.getInt(HAS_ATTACHMENT_COLUMN) > 0)? true : false; + // 从游标中获取笔记的修改日期数据,赋值给mModifiedDate成员变量。 + mModifiedDate = cursor.getLong(MODIFIED_DATE_COLUMN); + // 从游标中获取与笔记相关的数量信息(具体含义取决于业务逻辑),赋值给mNotesCount成员变量。 + mNotesCount = cursor.getInt(NOTES_COUNT_COLUMN); + // 从游标中获取笔记所属父级的ID(例如所属文件夹的ID等),赋值给mParentId成员变量。 + mParentId = cursor.getLong(PARENT_ID_COLUMN); + // 从游标中获取笔记的摘要、简短描述等文本内容,赋值给mSnippet成员变量,并进行特定字符串替换操作,去除可能存在的特定标记字符(如NoteEditActivity.TAG_CHECKED和NoteEditActivity.TAG_UNCHECKED)。 + mSnippet = cursor.getString(SNIPPET_COLUMN); + mSnippet = mSnippet.replace(NoteEditActivity.TAG_CHECKED, "").replace( + NoteEditActivity.TAG_UNCHECKED, ""); + // 从游标中获取笔记的类型信息(整数表示不同类型),赋值给mType成员变量,便于后续根据类型进行不同业务逻辑处理。 + mType = cursor.getInt(TYPE_COLUMN); + // 从游标中获取可能与笔记在桌面小部件等相关功能中的标识ID(具体用途取决于相关小部件功能实现),赋值给mWidgetId成员变量。 + mWidgetId = cursor.getInt(WIDGET_TYPE_COLUMN); + // 从游标中获取可能用于区分不同类型桌面小部件相关信息的类型值(如果有涉及桌面小部件相关业务逻辑的话),赋值给mWidgetType成员变量。 + mWidgetType = cursor.getInt(WIDGET_TYPE_COLUMN); + + // 初始化电话号码成员变量为空字符串,后续根据笔记所属情况等可能会重新赋值。 + mPhoneNumber = ""; + // 如果笔记所属父级ID等于通话记录文件夹的特定ID(Notes.ID_CALL_RECORD_FOLDER),表示该笔记与通话记录相关,尝试获取对应的电话号码。 + if (mParentId == Notes.ID_CALL_RECORD_FOLDER) { + // 通过DataUtils工具类的方法,根据笔记ID从内容解析器(context.getContentResolver())中获取对应的电话号码信息,并赋值给mPhoneNumber成员变量。 + mPhoneNumber = DataUtils.getCallNumberByNoteId(context.getContentResolver(), mId); + // 如果获取到的电话号码不为空字符串,说明找到了对应的电话号码,尝试获取对应的联系人姓名。 + if (!TextUtils.isEmpty(mPhoneNumber)) { + // 通过Contact类的静态方法,根据上下文(context)和电话号码(mPhoneNumber)获取对应的联系人姓名,赋值给mName成员变量。 + mName = Contact.getContact(context, mPhoneNumber); + // 如果获取联系人姓名失败(返回null),则将电话号码本身作为联系人姓名赋值给mName成员变量。 + if (mName == null) { + mName = mPhoneNumber; + } + } + } + + // 如果最终联系人姓名仍为null(可能之前获取过程都失败了),则将其设置为空字符串,保证成员变量有合理的初始值。 + if (mName == null) { + mName = ""; + } + + // 调用checkPostion方法,根据游标情况进行一些与笔记位置、在文件夹下关联情况等相关的状态判断,更新对应的成员变量值。 + checkPostion(cursor); + } + + // 私有方法,用于根据游标情况进行一些与笔记位置、在文件夹下关联情况等相关的状态判断,更新对应的成员变量值, + // 例如判断是否是某文件夹下唯一跟随的笔记、是否是多个笔记跟随某文件夹等情况。 + private void checkPostion(Cursor cursor) { + // 根据游标是否处于最后一条记录,判断当前笔记是否是列表中的最后一项,赋值给mIsLastItem成员变量。 + mIsLastItem = cursor.isLast()? true : false; + // 根据游标是否处于第一条记录,判断当前笔记是否是列表中的第一项,赋值给mIsFirstItem成员变量。 + mIsFirstItem = cursor.isFirst()? true : false; + // 通过判断游标获取的总记录数是否为1,确定当前笔记是否是列表中唯一的一项,赋值给mIsOnlyOneItem成员变量。 + mIsOnlyOneItem = (cursor.getCount() == 1); + // 初始化是否是某文件夹下跟随的多个笔记的标记为false,后续根据具体判断逻辑可能会更新。 + mIsMultiNotesFollowingFolder = false; + // 初始化是否是某文件夹下仅跟随的一个笔记的标记为false,后续根据具体判断逻辑可能会更新。 + mIsOneNoteFollowingFolder = false; + + // 如果笔记类型是普通笔记(Notes.TYPE_NOTE)且不是列表中的第一项(即前面还有其他记录),进行以下判断逻辑。 + if (mType == Notes.TYPE_NOTE &&!mIsFirstItem) { + // 获取当前游标所在的位置索引,用于后续判断操作。 + int position = cursor.getPosition(); + // 将游标移动到前一条记录,以便查看前一条记录对应的笔记类型等信息。 + if (cursor.moveToPrevious()) { + // 判断前一条记录对应的笔记类型是否是文件夹类型(Notes.TYPE_FOLDER)或者系统类型(Notes.TYPE_SYSTEM), + // 如果是,则说明当前笔记可能是跟随在某个文件夹或系统记录之后的笔记,继续进行后续判断。 + if (cursor.getInt(TYPE_COLUMN) == Notes.TYPE_FOLDER + || cursor.getInt(TYPE_COLUMN) == Notes.TYPE_SYSTEM) { + // 判断游标获取的总记录数是否大于当前位置索引加1(即当前笔记后面是否还有其他记录), + // 如果是,则说明当前笔记是某文件夹下跟随的多个笔记之一,将对应标记变量设置为true。 + if (cursor.getCount() > (position + 1)) { + mIsMultiNotesFollowingFolder = true; + } else { + // 如果后面没有其他记录了,则说明当前笔记是某文件夹下仅跟随的一个笔记,将对应标记变量设置为true。 + mIsOneNoteFollowingFolder = true; + } + } + // 将游标再移回原来的位置(即当前笔记对应的记录位置),保证游标位置的正确性,避免影响后续其他操作对游标的使用。 + if (!cursor.moveToNext()) { + throw new IllegalStateException("cursor move to previous but can't move back"); + } + } + } + } + + // 判断当前笔记是否是某文件夹下仅跟随的一个笔记,返回对应的标记变量值,外部可调用此方法获取该状态信息。 + public boolean isOneFollowingFolder() { + return mIsOneNoteFollowingFolder; + } + + // 判断当前笔记是否是某文件夹下跟随的多个笔记之一,返回对应的标记变量值,外部可调用此方法获取该状态信息。 + public boolean isMultiFollowingFolder() { + return mIsMultiNotesFollowingFolder; + } + + // 判断当前笔记是否是列表中的最后一项,返回对应的标记变量值,外部可调用此方法获取该状态信息。 + public boolean isLast() { + return mIsLastItem; + } + + // 获取与笔记相关的联系人姓名信息(如果有获取到的话),返回对应的成员变量值,外部可调用此方法获取该姓名信息。 + public String getCallName() { + return mName; + } + + // 判断当前笔记是否是列表中的第一项,返回对应的标记变量值,外部可调用此方法获取该状态信息。 + public boolean isFirst() { + return mIsFirstItem; + } + + // 判断当前笔记是否是列表中唯一的一项,返回对应的标记变量值,外部可调用此方法获取该状态信息。 + public boolean isSingle() { + return mIsOnlyOneItem; + } + + // 获取笔记的ID,返回对应的成员变量值,外部可调用此方法获取笔记的唯一标识信息。 + public long getId() { + return mId; + } + + // 获取笔记的提醒日期,返回对应的成员变量值,外部可调用此方法获取笔记设置的提醒时间信息。 + public long getAlertDate() { + return mAlertDate; + } + + // 获取笔记的创建日期,返回对应的成员变量值,外部可调用此方法获取笔记最初被创建的时间信息。 + public long getCreatedDate() { + return mCreatedDate; + } + + // 判断笔记是否有附件,返回对应的标记变量值(mHasAttachment),外部可调用此方法获取笔记是否包含附件的状态信息, + // 如果返回true,表示笔记存在附件,否则表示没有附件。 public boolean hasAttachment() { return mHasAttachment; } + // 获取笔记的修改日期,返回对应的成员变量值(mModifiedDate),外部可调用此方法获取笔记最后一次被修改的时间信息, + // 常用于记录笔记的修改历史、版本管理等相关业务逻辑场景中。 public long getModifiedDate() { return mModifiedDate; } + // 获取笔记的背景颜色ID,返回对应的成员变量值(mBgColorId),外部可调用此方法获取用于设置笔记显示背景颜色的标识信息, + // 根据该ID可以在应用的颜色资源等相关配置中找到对应的具体颜色来应用到笔记展示界面上。 public int getBgColorId() { return mBgColorId; } + // 获取笔记所属父级的ID(例如所属文件夹的ID等),返回对应的成员变量值(mParentId),外部可调用此方法获取笔记的归属关系信息, + // 便于判断笔记位于哪个文件夹下或者隶属于哪个上级模块等情况,方便进行分类管理、层级展示等操作。 public long getParentId() { return mParentId; } + // 获取与笔记相关的数量信息(具体含义取决于业务逻辑,可能是关联的子笔记数量等情况),返回对应的成员变量值(mNotesCount), + // 外部可调用此方法获取该数量值,在涉及笔记的统计、分组等业务场景中可能会用到。 public int getNotesCount() { return mNotesCount; } + // 获取笔记所属文件夹的ID,这里直接返回笔记的父级ID(mParentId),因为通常情况下父级就是所属的文件夹,外部可调用此方法明确获取到笔记所属文件夹的标识, + // 方便进行文件夹相关的操作,比如在文件夹内查找笔记、统计文件夹内笔记数量等操作。 public long getFolderId () { return mParentId; } + // 获取笔记的类型信息,返回对应的成员变量值(mType),外部可调用此方法获取笔记所属的类型(例如普通笔记、系统笔记等不同分类), + // 根据不同类型可以在应用中进行差异化的展示、操作等业务逻辑处理,比如不同类型笔记有不同的编辑权限、显示样式等。 public int getType() { return mType; } + // 获取可能用于区分不同类型桌面小部件相关信息的类型值(如果有涉及桌面小部件相关业务逻辑的话),返回对应的成员变量值(mWidgetType), + // 外部可调用此方法获取该类型值,以便针对不同小部件类型进行相应的配置、显示控制等操作,例如不同类型小部件展示笔记的不同部分内容或者交互方式不同等。 public int getWidgetType() { return mWidgetType; } + // 获取可能与笔记在桌面小部件等相关功能中的标识ID(具体用途取决于相关小部件功能实现),返回对应的成员变量值(mWidgetId), + // 外部可调用此方法获取该标识ID,用于在小部件相关功能中准确识别和操作对应的笔记,比如在小部件中更新特定笔记的显示内容等情况。 public int getWidgetId() { return mWidgetId; } + // 获取笔记的摘要、简短描述等文本内容,返回对应的成员变量值(mSnippet),外部可调用此方法获取笔记的简要信息, + // 常用于在列表展示等场景中先显示笔记的简短描述,用户点击后再查看详细内容,起到一个预览的作用。 public String getSnippet() { return mSnippet; } + // 判断笔记是否设置了提醒,通过检查提醒日期(mAlertDate)是否大于0来确定,如果大于0则表示设置了提醒,返回true,否则返回false, + // 外部可调用此方法快速知晓笔记是否有提醒相关的设置,以便在应用中进行相应的提醒业务逻辑处理,比如定时提醒用户查看笔记等操作。 public boolean hasAlert() { return (mAlertDate > 0); } + // 判断笔记是否属于通话记录类型,通过检查笔记所属父级ID是否等于通话记录文件夹的特定ID(Notes.ID_CALL_RECORD_FOLDER)并且电话号码(mPhoneNumber)不为空字符串来确定, + // 如果满足条件则返回true,表示是通话记录相关笔记,否则返回false,外部可调用此方法区分通话记录笔记和其他类型笔记,进行针对性的业务操作,例如通话记录笔记可能有特殊的展示格式或关联操作等。 public boolean isCallRecord() { - return (mParentId == Notes.ID_CALL_RECORD_FOLDER && !TextUtils.isEmpty(mPhoneNumber)); + return (mParentId == Notes.ID_CALL_RECORD_FOLDER &&!TextUtils.isEmpty(mPhoneNumber)); } + // 静态方法,接收数据库游标(Cursor)作为参数,从游标中获取笔记的类型信息(通过TYPE_COLUMN指定的列索引获取整数值)并返回, + // 外部可直接通过类名调用此方法,方便在不需要创建NoteItemData类实例的情况下,仅根据游标快速获取笔记的类型信息,常用于一些仅需判断类型的简单业务场景中。 public static int getNoteType(Cursor cursor) { return cursor.getInt(TYPE_COLUMN); } -} +} \ No newline at end of file diff --git a/src/net/micode/notes/ui/NotesListItem.java b/src/net/micode/notes/ui/NotesListItem.java index 1221e80..9fa79a6 100644 --- a/src/net/micode/notes/ui/NotesListItem.java +++ b/src/net/micode/notes/ui/NotesListItem.java @@ -14,109 +14,170 @@ * limitations under the License. */ -package net.micode.notes.ui; + package net.micode.notes.ui; -import android.content.Context; -import android.text.format.DateUtils; -import android.view.View; -import android.widget.CheckBox; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.tool.DataUtils; -import net.micode.notes.tool.ResourceParser.NoteItemBgResources; - - -public class NotesListItem extends LinearLayout { - private ImageView mAlert; - private TextView mTitle; - private TextView mTime; - private TextView mCallName; - private NoteItemData mItemData; - private CheckBox mCheckBox; - - public NotesListItem(Context context) { - super(context); - inflate(context, R.layout.note_item, this); - mAlert = (ImageView) findViewById(R.id.iv_alert_icon); - mTitle = (TextView) findViewById(R.id.tv_title); - mTime = (TextView) findViewById(R.id.tv_time); - mCallName = (TextView) findViewById(R.id.tv_name); - mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); - } - - 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); - mTitle.setText(context.getString(R.string.call_record_folder_name) - + context.getString(R.string.format_folder_files_count, data.getNotesCount())); - mAlert.setImageResource(R.drawable.call_record); - } else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { - mCallName.setVisibility(View.VISIBLE); - mCallName.setText(data.getCallName()); - mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); - mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); - if (data.hasAlert()) { - mAlert.setImageResource(R.drawable.clock); - mAlert.setVisibility(View.VISIBLE); - } else { - mAlert.setVisibility(View.GONE); - } - } else { - mCallName.setVisibility(View.GONE); - mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); - - if (data.getType() == Notes.TYPE_FOLDER) { - mTitle.setText(data.getSnippet() - + context.getString(R.string.format_folder_files_count, - data.getNotesCount())); - mAlert.setVisibility(View.GONE); - } else { - mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); - if (data.hasAlert()) { - mAlert.setImageResource(R.drawable.clock); - mAlert.setVisibility(View.VISIBLE); - } else { - mAlert.setVisibility(View.GONE); - } - } - } - mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); - - setBackground(data); - } - - private void setBackground(NoteItemData data) { - int id = data.getBgColorId(); - if (data.getType() == Notes.TYPE_NOTE) { - if (data.isSingle() || data.isOneFollowingFolder()) { - setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id)); - } else if (data.isLast()) { - setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id)); - } else if (data.isFirst() || data.isMultiFollowingFolder()) { - setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id)); - } else { - setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id)); - } - } else { - setBackgroundResource(NoteItemBgResources.getFolderBgRes()); - } - } - - public NoteItemData getItemData() { - return mItemData; - } -} + import android.content.Context; + import android.text.format.DateUtils; + import android.view.View; + import android.widget.CheckBox; + import android.widget.ImageView; + import android.widget.LinearLayout; + import android.widget.TextView; + + import net.micode.notes.R; + import net.micode.notes.data.Notes; + import net.micode.notes.tool.DataUtils; + import net.micode.notes.tool.ResourceParser.NoteItemBgResources; + + // NotesListItem类继承自LinearLayout,代表了笔记列表中每一项的视图布局,用于展示笔记相关的各项信息, + // 如标题、时间、是否有提醒等,并根据笔记的不同类型、状态等来设置对应的显示样式和背景等。 + public class NotesListItem extends LinearLayout { + + // 用于显示提醒相关图标的ImageView控件,例如有提醒时显示闹钟图标等,方便用户直观看到笔记是否设置了提醒功能。 + private ImageView mAlert; + // 用于显示笔记标题的TextView控件,会根据笔记的类型、具体内容等情况设置相应的文本内容进行展示。 + private TextView mTitle; + // 用于显示笔记时间相关信息的TextView控件,通常会展示笔记的修改时间等,并以相对时间的格式(如“几分钟前”“昨天”等)进行显示。 + private TextView mTime; + // 用于显示与笔记相关的联系人姓名(如果笔记属于通话记录等相关情况)的TextView控件,若不涉及则可能隐藏该控件。 + private TextView mCallName; + // 用于存储当前列表项对应的笔记数据对象(NoteItemData类型),包含了笔记的各种详细信息,方便在视图设置等操作中获取对应的数据进行展示和判断。 + private NoteItemData mItemData; + // 用于在选择模式下(例如多选操作时)显示选择状态的CheckBox控件,根据是否处于选择模式以及笔记类型等情况决定其可见性和选中状态。 + private CheckBox mCheckBox; + + // 构造函数,接收上下文(Context)作为参数,调用父类(LinearLayout)的构造函数进行初始化, + // 并通过inflate方法加载对应的布局文件(R.layout.note_item)到当前视图中,然后获取布局中各个相关的子控件实例。 + public NotesListItem(Context context) { + super(context); + inflate(context, R.layout.note_item, this); + mAlert = (ImageView) findViewById(R.id.iv_alert_icon); + mTitle = (TextView) findViewById(R.id.tv_title); + mTime = (TextView) findViewById(R.id.tv_time); + mCallName = (TextView) findViewById(R.id.tv_name); + mCheckBox = (CheckBox) findViewById(android.R.id.checkbox); + } + + // 用于将笔记相关的数据绑定到当前列表项视图上,根据笔记数据的不同情况(如类型、是否有提醒等)以及传入的一些显示控制参数(如是否处于选择模式、是否已选中), + // 来设置各个子控件的显示内容、可见性以及背景等样式,完成数据与视图的关联展示。 + public void bind(Context context, NoteItemData data, boolean choiceMode, boolean checked) { + // 如果处于选择模式(choiceMode为true)并且笔记类型是普通笔记(Notes.TYPE_NOTE),则显示CheckBox控件, + // 并根据传入的是否已选中参数(checked)设置其选中状态,用于在多选等操作场景下展示选择情况。 + if (choiceMode && data.getType() == Notes.TYPE_NOTE) { + mCheckBox.setVisibility(View.VISIBLE); + mCheckBox.setChecked(checked); + } else { + // 如果不满足上述条件(非选择模式或者不是普通笔记类型),则隐藏CheckBox控件,不展示选择相关操作界面。 + mCheckBox.setVisibility(View.GONE); + } + + // 将传入的笔记数据对象赋值给成员变量mItemData,方便后续在其他方法中获取该笔记的各项详细信息。 + mItemData = data; + + // 如果笔记的ID等于通话记录文件夹的特定ID(Notes.ID_CALL_RECORD_FOLDER),说明当前列表项对应的是通话记录文件夹,进行以下设置: + if (data.getId() == Notes.ID_CALL_RECORD_FOLDER) { + // 隐藏显示联系人姓名的TextView控件(mCallName),因为通话记录文件夹本身不需要显示联系人姓名。 + mCallName.setVisibility(View.GONE); + // 显示提醒图标相关的ImageView控件(mAlert),可能用于表示该文件夹有特殊含义或者相关提醒功能(具体取决于业务逻辑)。 + mAlert.setVisibility(View.VISIBLE); + // 设置标题(mTitle)的文本外观样式为主要项的样式(通过R.style.TextAppearancePrimaryItem资源样式设置),使其在界面上以相应的样式展示。 + mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); + // 设置标题的文本内容,由通话记录文件夹的名称(通过字符串资源获取)和该文件夹内文件数量(通过格式化字符串资源结合笔记数据中的文件数量信息获取)组成,展示给用户文件夹相关信息。 + mTitle.setText(context.getString(R.string.call_record_folder_name) + + context.getString(R.string.format_folder_files_count, data.getNotesCount())); + // 设置提醒图标(mAlert)显示的图片资源为通话记录相关的特定图标(R.drawable.call_record),以特定图标表示该文件夹的性质。 + mAlert.setImageResource(R.drawable.call_record); + } + // 如果笔记所属父级的ID等于通话记录文件夹的特定ID(Notes.ID_CALL_RECORD_FOLDER),说明当前笔记属于通话记录文件夹下的具体笔记,进行如下设置: + else if (data.getParentId() == Notes.ID_CALL_RECORD_FOLDER) { + // 显示联系人姓名的TextView控件(mCallName),并设置其文本内容为笔记相关的联系人姓名(通过笔记数据中的联系人姓名信息获取),展示给用户对应的联系人信息。 + mCallName.setVisibility(View.VISIBLE); + mCallName.setText(data.getCallName()); + // 设置标题(mTitle)的文本外观样式为次要项的样式(通过R.style.TextAppearanceSecondaryItem资源样式设置),使其与文件夹等其他项在样式上有所区分展示。 + mTitle.setTextAppearance(context,R.style.TextAppearanceSecondaryItem); + // 设置标题的文本内容为经过格式化后的笔记摘要信息(通过DataUtils工具类的方法对笔记数据中的摘要内容进行格式化处理获取),展示笔记的简要内容。 + mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); + // 如果笔记设置了提醒(通过笔记数据中的提醒日期判断,hasAlert方法返回true),则进行以下提醒图标相关设置: + if (data.hasAlert()) { + // 设置提醒图标(mAlert)显示的图片资源为闹钟图标(R.drawable.clock),直观提示用户该笔记设置了提醒功能。 + mAlert.setImageResource(R.drawable.clock); + // 显示提醒图标相关的ImageView控件(mAlert),使其在界面上可见。 + mAlert.setVisibility(View.VISIBLE); + } else { + // 如果笔记没有设置提醒,则隐藏提醒图标相关的ImageView控件(mAlert),不在界面上展示该图标。 + mAlert.setVisibility(View.GONE); + } + } + // 如果笔记不属于上述通话记录文件夹相关的情况,则进行以下通用设置: + else { + // 隐藏联系人姓名的TextView控件(mCallName),因为该笔记与通话记录无关,不需要展示联系人姓名。 + mCallName.setVisibility(View.GONE); + // 设置标题(mTitle)的文本外观样式为主要项的样式(通过R.style.TextAppearancePrimaryItem资源样式设置),以相应的样式展示标题内容。 + mTitle.setTextAppearance(context, R.style.TextAppearancePrimaryItem); + + // 如果笔记类型是文件夹类型(Notes.TYPE_FOLDER),进行以下文件夹相关的标题设置: + if (data.getType() == Notes.TYPE_FOLDER) { + // 设置标题的文本内容为笔记的摘要信息(通过笔记数据中的摘要内容获取)加上该文件夹内文件数量(通过格式化字符串资源结合笔记数据中的文件数量信息获取),展示文件夹及其包含文件数量的相关信息给用户。 + mTitle.setText(data.getSnippet() + + context.getString(R.string.format_folder_files_count, + data.getNotesCount())); + // 隐藏提醒图标相关的ImageView控件(mAlert),文件夹通常不需要展示提醒图标(除非有特殊业务逻辑要求)。 + mAlert.setVisibility(View.GONE); + } else { + // 如果笔记不是文件夹类型(即普通笔记等其他类型),设置标题的文本内容为经过格式化后的笔记摘要信息(通过DataUtils工具类的方法对笔记数据中的摘要内容进行格式化处理获取),展示笔记的简要内容。 + mTitle.setText(DataUtils.getFormattedSnippet(data.getSnippet())); + // 如果笔记设置了提醒(通过笔记数据中的提醒日期判断,hasAlert方法返回true),则进行以下提醒图标相关设置: + if (data.hasAlert()) { + // 设置提醒图标(mAlert)显示的图片资源为闹钟图标(R.drawable.clock),直观提示用户该笔记设置了提醒功能。 + mAlert.setImageResource(R.drawable.clock); + // 显示提醒图标相关的ImageView控件(mAlert),使其在界面上可见。 + mAlert.setVisibility(View.VISIBLE); + } else { + // 如果笔记没有设置提醒,则隐藏提醒图标相关的ImageView控件(mAlert),不在界面上展示该图标。 + mAlert.setVisibility(View.GONE); + } + } + } + + // 设置显示时间的TextView控件(mTime)的文本内容,通过DateUtils工具类的方法,将笔记的修改日期(通过笔记数据中的修改日期信息获取)转换为相对时间格式的字符串(如“几分钟前”“昨天”等)进行展示,方便用户直观了解笔记的修改时间情况。 + mTime.setText(DateUtils.getRelativeTimeSpanString(data.getModifiedDate())); + + // 调用setBackground方法,根据笔记数据中的背景颜色ID以及笔记的类型、位置等状态信息,设置当前列表项的背景样式,使其在列表中以合适的背景展示。 + setBackground(data); + } + + // 私有方法,根据笔记数据(NoteItemData类型)中的背景颜色ID以及笔记的类型、位置等状态信息, + // 通过NoteItemBgResources工具类获取对应的背景资源,并设置为当前列表项的背景,实现不同情况下的差异化背景展示。 + private void setBackground(NoteItemData data) { + int id = data.getBgColorId(); + // 如果笔记类型是普通笔记(Notes.TYPE_NOTE),根据笔记在列表中的不同位置、数量等情况设置不同的背景资源: + if (data.getType() == Notes.TYPE_NOTE) { + // 如果笔记是列表中唯一的一项或者是某文件夹下仅跟随的一个笔记(通过笔记数据中的相关判断方法确定), + // 通过NoteItemBgResources工具类获取对应的单个笔记背景资源(根据背景颜色ID获取具体资源ID),并设置为当前列表项的背景。 + if (data.isSingle() || data.isOneFollowingFolder()) { + setBackgroundResource(NoteItemBgResources.getNoteBgSingleRes(id)); + } + // 如果笔记是列表中的最后一项(通过笔记数据中的相关判断方法确定),通过NoteItemBgResources工具类获取对应的最后一个笔记背景资源(根据背景颜色ID获取具体资源ID),并设置为当前列表项的背景。 + else if (data.isLast()) { + setBackgroundResource(NoteItemBgResources.getNoteBgLastRes(id)); + } + // 如果笔记是列表中的第一项或者是某文件夹下跟随的多个笔记之一(通过笔记数据中的相关判断方法确定), + // 通过NoteItemBgResources工具类获取对应的第一个笔记或多个笔记中某个的背景资源(根据背景颜色ID获取具体资源ID),并设置为当前列表项的背景。 + else if (data.isFirst() || data.isMultiFollowingFolder()) { + setBackgroundResource(NoteItemBgResources.getNoteBgFirstRes(id)); + } else { + // 如果笔记不属于上述特殊位置等情况,则通过NoteItemBgResources工具类获取对应的普通笔记背景资源(根据背景颜色ID获取具体资源ID),并设置为当前列表项的背景。 + setBackgroundResource(NoteItemBgResources.getNoteBgNormalRes(id)); + } + } else { + // 如果笔记不是普通笔记类型(例如是文件夹类型等),通过NoteItemBgResources工具类获取对应的文件夹背景资源,并设置为当前列表项的背景,以区别于普通笔记的背景展示。 + setBackgroundResource(NoteItemBgResources.getFolderBgRes()); + } + } + + // 对外提供获取当前列表项对应的笔记数据对象的方法,外部可调用此方法获取笔记的详细信息,例如在点击列表项等操作后, + // 通过获取笔记数据进一步进行查看详情、编辑等相关业务逻辑处理。 + public NoteItemData getItemData() { + return mItemData; + } + } \ No newline at end of file diff --git a/src/net/micode/notes/widget/NoteWidgetProvider.java b/src/net/micode/notes/widget/NoteWidgetProvider.java index ec6f819..7a30d01 100644 --- a/src/net/micode/notes/widget/NoteWidgetProvider.java +++ b/src/net/micode/notes/widget/NoteWidgetProvider.java @@ -14,119 +14,179 @@ * limitations under the License. */ -package net.micode.notes.widget; -import android.app.PendingIntent; -import android.appwidget.AppWidgetManager; -import android.appwidget.AppWidgetProvider; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.util.Log; -import android.widget.RemoteViews; - -import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.data.Notes.NoteColumns; -import net.micode.notes.tool.ResourceParser; -import net.micode.notes.ui.NoteEditActivity; -import net.micode.notes.ui.NotesListActivity; - -public abstract class NoteWidgetProvider extends AppWidgetProvider { - public static final String [] PROJECTION = new String [] { - NoteColumns.ID, - NoteColumns.BG_COLOR_ID, - NoteColumns.SNIPPET - }; - - 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"; - - @Override - public void onDeleted(Context context, int[] appWidgetIds) { - ContentValues values = new ContentValues(); - values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); - for (int i = 0; i < appWidgetIds.length; i++) { - context.getContentResolver().update(Notes.CONTENT_NOTE_URI, - values, - NoteColumns.WIDGET_ID + "=?", - new String[] { String.valueOf(appWidgetIds[i])}); - } - } - - private Cursor getNoteWidgetInfo(Context context, int widgetId) { - return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, - PROJECTION, - NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?", - new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) }, - null); - } - - protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { - update(context, appWidgetManager, appWidgetIds, false); - } - - private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, - boolean privacyMode) { - for (int i = 0; i < appWidgetIds.length; i++) { - if (appWidgetIds[i] != AppWidgetManager.INVALID_APPWIDGET_ID) { - int bgId = ResourceParser.getDefaultBgId(context); - String snippet = ""; - 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 c = getNoteWidgetInfo(context, appWidgetIds[i]); - if (c != null && c.moveToFirst()) { - if (c.getCount() > 1) { - Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]); - c.close(); - return; - } - snippet = c.getString(COLUMN_SNIPPET); - bgId = c.getInt(COLUMN_BG_COLOR_ID); - intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); - intent.setAction(Intent.ACTION_VIEW); - } else { - snippet = context.getResources().getString(R.string.widget_havenot_content); - intent.setAction(Intent.ACTION_INSERT_OR_EDIT); - } - - if (c != null) { - c.close(); - } - - RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); - rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); - intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); - /** - * Generate the pending intent to start host for the widget - */ - PendingIntent pendingIntent = null; - if (privacyMode) { - rv.setTextViewText(R.id.widget_text, - context.getString(R.string.widget_under_visit_mode)); - pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent( - context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); - } else { - rv.setTextViewText(R.id.widget_text, snippet); - pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent, - PendingIntent.FLAG_UPDATE_CURRENT); - } - - rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); - appWidgetManager.updateAppWidget(appWidgetIds[i], rv); - } - } - } - - protected abstract int getBgResourceId(int bgId); - - protected abstract int getLayoutId(); - - protected abstract int getWidgetType(); -} + package net.micode.notes.widget; + + import android.app.PendingIntent; + import android.appwidget.AppWidgetManager; + import android.appwidget.AppWidgetProvider; + import android.content.ContentValues; + import android.content.Context; + import android.content.Intent; + import android.database.Cursor; + import android.util.Log; + import android.widget.RemoteViews; + + import net.micode.notes.R; + import net.micode.notes.data.Notes; + import net.micode.notes.data.Notes.NoteColumns; + 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以及摘要信息等, + // 后续通过数据库游标(Cursor)可根据这些列名获取对应的数据来进行小部件的内容展示等操作。 + public static final String [] PROJECTION = new String [] { + NoteColumns.ID, + NoteColumns.BG_COLOR_ID, + NoteColumns.SNIPPET + }; + + // 定义一个常量,表示在查询结果游标(Cursor)中"ID"列所在的索引位置,方便后续通过游标获取对应列的数据。 + public static final int COLUMN_ID = 0; + // 定义一个常量,表示在查询结果游标(Cursor)中"BG_COLOR_ID"列所在的索引位置,用于获取笔记的背景颜色ID数据来设置小部件的背景样式。 + public static final int COLUMN_BG_COLOR_ID = 1; + // 定义一个常量,表示在查询结果游标(Cursor)中"SNIPPET"列所在的索引位置,用于获取笔记的摘要信息,可展示在小部件上作为简要内容。 + public static final int COLUMN_SNIPPET = 2; + + private static final String TAG = "NoteWidgetProvider"; + + // 重写父类(AppWidgetProvider)的onDeleted方法,该方法在桌面小部件被删除时会被调用, + // 这里主要用于处理与小部件相关的数据清理工作,例如将数据库中与被删除小部件关联的记录进行更新,清除对应的小部件ID关联等信息。 + @Override + public void onDeleted(Context context, int[] appWidgetIds) { + // 创建一个ContentValues对象,用于存储要更新到数据库中的键值对数据,这里主要是要更新笔记数据表中与小部件ID相关的字段。 + ContentValues values = new ContentValues(); + // 将NoteColumns.WIDGET_ID字段的值设置为无效的小部件ID(AppWidgetManager.INVALID_APPWIDGET_ID),表示该笔记不再与任何小部件关联。 + values.put(NoteColumns.WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); + + // 遍历传入的被删除小部件的ID数组,对每个小部件ID执行以下数据库更新操作。 + for (int i = 0; i < appWidgetIds.length; i++) { + // 使用上下文的内容解析器(context.getContentResolver())来更新数据库中的数据, + // 更新的表是Notes.CONTENT_NOTE_URI所指向的表(通常是存储笔记相关信息的表), + // 使用values中设置的数据进行更新,更新的条件是NoteColumns.WIDGET_ID字段等于当前遍历到的小部件ID, + // 并且传入一个字符串数组作为条件参数的值,将小部件ID转换为字符串形式用于条件匹配。 + context.getContentResolver().update(Notes.CONTENT_NOTE_URI, + values, + NoteColumns.WIDGET_ID + "=?", + new String[] { String.valueOf(appWidgetIds[i])}); + } + } + + // 私有方法,用于根据给定的小部件ID(widgetId)从数据库中查询获取与该小部件相关的笔记信息,返回一个数据库游标(Cursor)对象, + // 通过设置查询条件,筛选出特定小部件关联且不属于回收站文件夹(Notes.ID_TRASH_FOLER)的笔记信息,方便后续获取具体的数据进行小部件内容展示等操作。 + private Cursor getNoteWidgetInfo(Context context, int widgetId) { + return context.getContentResolver().query(Notes.CONTENT_NOTE_URI, + PROJECTION, + NoteColumns.WIDGET_ID + "=? AND " + NoteColumns.PARENT_ID + "<>?", + new String[] { String.valueOf(widgetId), String.valueOf(Notes.ID_TRASH_FOLER) }, + null); + } + + // 对外公开的update方法,用于触发小部件的更新操作,它实际上是调用了另一个重载的私有update方法,并传入默认的隐私模式参数(false), + // 方便外部类(如子类或者其他调用者)简单地调用该方法来启动小部件更新流程,而无需关心隐私模式相关的细节(可在重载方法中处理)。 + protected void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + update(context, appWidgetManager, appWidgetIds, false); + } + + // 私有重载的update方法,是真正执行小部件更新具体逻辑的地方,它会遍历传入的小部件ID数组,对每个有效的小部件ID进行一系列操作, + // 包括从数据库获取相关笔记信息、设置小部件的视图内容(如文本、背景图片等)、创建点击小部件时的意图(PendingIntent)等,最终完成小部件的更新展示。 + private void update(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds, + boolean privacyMode) { + // 遍历传入的小部件ID数组,对每个小部件ID进行更新操作。 + for (int i = 0; i < appWidgetIds.length; i++) { + // 检查当前小部件ID是否是有效的小部件ID(不等于AppWidgetManager.INVALID_APPWIDGET_ID),如果有效则进行后续更新操作。 + if (appWidgetIds[i]!= AppWidgetManager.INVALID_APPWIDGET_ID) { + // 获取默认的背景颜色ID,通过ResourceParser工具类的方法获取,用于在没有获取到具体笔记的背景颜色ID时作为默认背景设置,初始设置小部件的背景样式。 + int bgId = ResourceParser.getDefaultBgId(context); + // 初始化用于存储笔记摘要信息的字符串为空,后续会根据从数据库获取的情况进行更新,该摘要信息会展示在小部件上作为简要内容。 + String snippet = ""; + // 创建一个意图(Intent)对象,用于指定点击小部件时要启动的活动(Activity),这里初始设置为启动NoteEditActivity,后续会根据情况调整。 + Intent intent = new Intent(context, NoteEditActivity.class); + // 设置意图的标志位,使得如果对应的活动已经在栈顶(处于运行状态),则不会重新创建新的实例,而是复用已有的活动实例,优化性能并保证界面状态的连贯性。 + intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); + // 将当前小部件的ID作为额外的数据添加到意图中,方便在目标活动(NoteEditActivity)中获取并知道是哪个小部件触发的操作,用于后续相关逻辑处理。 + intent.putExtra(Notes.INTENT_EXTRA_WIDGET_ID, appWidgetIds[i]); + // 将当前小部件的类型(通过抽象方法getWidgetType获取,由具体子类实现返回不同类型值)作为额外的数据添加到意图中,同样方便在目标活动中区分不同类型小部件的相关操作。 + intent.putExtra(Notes.INTENT_EXTRA_WIDGET_TYPE, getWidgetType()); + + // 调用getNoteWidgetInfo方法,根据当前小部件ID从数据库中获取与该小部件相关的笔记信息,返回一个游标对象,用于后续获取具体数据。 + Cursor c = getNoteWidgetInfo(context, appWidgetIds[i]); + if (c!= null && c.moveToFirst()) { + // 如果游标不为空且能够移动到第一条记录(表示查询到了相关数据),进行以下操作: + if (c.getCount() > 1) { + // 如果查询到的记录数大于1,说明存在多个笔记与同一个小部件ID关联,这可能是不符合预期的情况,记录错误日志并关闭游标,直接返回,不进行后续更新操作。 + Log.e(TAG, "Multiple message with same widget id:" + appWidgetIds[i]); + c.close(); + return; + } + // 获取游标中对应列(COLUMN_SNIPPET)的笔记摘要信息,并赋值给snippet变量,用于后续展示在小部件上。 + snippet = c.getString(COLUMN_SNIPPET); + // 获取游标中对应列(COLUMN_BG_COLOR_ID)的笔记背景颜色ID,并赋值给bgId变量,用于设置小部件的背景样式,覆盖之前的默认背景颜色ID。 + bgId = c.getInt(COLUMN_BG_COLOR_ID); + // 将游标中对应列(COLUMN_ID)的笔记ID作为额外的数据添加到意图中,方便在目标活动中明确具体是哪个笔记相关的操作,例如编辑该笔记等。 + intent.putExtra(Intent.EXTRA_UID, c.getLong(COLUMN_ID)); + // 设置意图的动作(Action)为Intent.ACTION_VIEW,通常表示查看相关内容的操作,意味着点击小部件后可能是查看对应的笔记详情等行为(具体取决于目标活动的实现)。 + intent.setAction(Intent.ACTION_VIEW); + } else { + // 如果游标为空或者无法移动到第一条记录(表示没有查询到与该小部件相关的有效笔记信息),进行以下操作: + snippet = context.getResources().getString(R.string.widget_havenot_content); + // 设置意图的动作(Action)为Intent.ACTION_INSERT_OR_EDIT,意味着点击小部件后可能是进行插入新笔记或者编辑相关内容的操作(同样具体取决于目标活动的实现),因为没有关联的已有笔记,所以提供创建或编辑的入口。 + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); + } + + // 如果游标对象不为空,关闭游标,释放相关资源,避免内存泄漏等问题,因为已经获取完需要的数据了。 + if (c!= null) { + c.close(); + } + + // 创建一个RemoteViews对象,用于设置小部件的远程视图内容,通过传入应用的包名和小部件的布局资源ID(由抽象方法getLayoutId获取,具体子类实现返回不同布局资源)来初始化, + // 后续可通过该对象设置小部件上各个子视图(如文本视图、图片视图等)的显示内容、属性等。 + RemoteViews rv = new RemoteViews(context.getPackageName(), getLayoutId()); + // 设置小部件上背景图片视图(通过R.id.widget_bg_image指定)的图片资源,调用抽象方法getBgResourceId并传入获取到的背景颜色ID(bgId)来获取对应的背景图片资源,进行背景设置。 + rv.setImageViewResource(R.id.widget_bg_image, getBgResourceId(bgId)); + // 将背景颜色ID作为额外的数据添加到意图中,方便在目标活动中获取并可能用于保持界面显示等相关一致性操作(例如设置相同的背景颜色等)。 + intent.putExtra(Notes.INTENT_EXTRA_BACKGROUND_ID, bgId); + + // 定义一个PendingIntent对象,用于包装意图(intent),使其可以在合适的时候被触发执行(例如点击小部件时),初始化为null,后续根据隐私模式情况进行创建。 + PendingIntent pendingIntent = null; + if (privacyMode) { + // 如果处于隐私模式(privacyMode为true),进行以下操作: + rv.setTextViewText(R.id.widget_text, + context.getString(R.string.widget_under_visit_mode)); + // 创建一个PendingIntent,用于启动NotesListActivity,当点击小部件时会跳转到该活动, + // 并设置标志位为PendingIntent.FLAG_UPDATE_CURRENT,保证如果已经存在相同的PendingIntent,会更新其携带的意图数据,保持最新状态。 + pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], new Intent( + context, NotesListActivity.class), PendingIntent.FLAG_UPDATE_CURRENT); + } else { + // 如果不处于隐私模式(privacyMode为false),进行以下操作: + rv.setTextViewText(R.id.widget_text, snippet); + // 创建一个PendingIntent,包装之前创建的intent(根据情况设置了不同动作、携带不同数据等,用于执行与笔记相关的操作,如查看、编辑等), + // 同样设置标志位为PendingIntent.FLAG_UPDATE_CURRENT,保证意图数据的更新,使得点击小部件时能正确执行相应操作。 + pendingIntent = PendingIntent.getActivity(context, appWidgetIds[i], intent, + PendingIntent.FLAG_UPDATE_CURRENT); + } + + // 设置小部件上文本视图(通过R.id.widget_text指定)的点击事件PendingIntent,使得点击该文本区域时会触发对应的操作(如跳转到相关活动等),完成小部件交互逻辑的设置。 + rv.setOnClickPendingIntent(R.id.widget_text, pendingIntent); + // 通过AppWidgetManager对象,使用小部件的ID,更新小部件的远程视图内容,将之前设置好的RemoteViews对象应用到小部件上,完成小部件的更新展示。 + appWidgetManager.updateAppWidget(appWidgetIds[i], rv); + } + } + } + + // 抽象方法,用于获取给定背景颜色ID(bgId)对应的小部件背景资源ID,由具体的子类实现,根据不同类型小部件或者不同的业务逻辑返回相应的背景资源ID, + // 以便在小部件更新时设置合适的背景样式。 + protected abstract int getBgResourceId(int bgId); + + // 抽象方法,用于获取小部件对应的布局资源ID,由具体的子类实现,不同类型的小部件(如不同尺寸、不同样式等)可以返回各自对应的布局资源, + // 用于在小部件更新时创建RemoteViews对象来设置小部件的整体布局和显示内容。 + protected abstract int getLayoutId(); + + // 抽象方法,用于获取小部件的类型标识,由具体的子类实现返回不同的类型值(例如不同尺寸类型的小部件返回不同的类型常量等), + // 方便在应用中对不同类型小部件进行区分和针对性的管理、操作以及相关业务逻辑处理,比如根据类型设置不同的显示规则、数据加载方式等。 + protected abstract int getWidgetType(); + } \ No newline at end of file diff --git a/src/net/micode/notes/widget/NoteWidgetProvider_2x.java b/src/net/micode/notes/widget/NoteWidgetProvider_2x.java index adcb2f7..6bda022 100644 --- a/src/net/micode/notes/widget/NoteWidgetProvider_2x.java +++ b/src/net/micode/notes/widget/NoteWidgetProvider_2x.java @@ -14,34 +14,46 @@ * limitations under the License. */ -package net.micode.notes.widget; - -import android.appwidget.AppWidgetManager; -import android.content.Context; - -import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.tool.ResourceParser; - - -public class NoteWidgetProvider_2x extends NoteWidgetProvider { - @Override - public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { - super.update(context, appWidgetManager, appWidgetIds); - } - - @Override - protected int getLayoutId() { - return R.layout.widget_2x; - } - - @Override - protected int getBgResourceId(int bgId) { - return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); - } - - @Override - protected int getWidgetType() { - return Notes.TYPE_WIDGET_2X; - } -} + package net.micode.notes.widget; + + import android.appwidget.AppWidgetManager; + import android.content.Context; + + import net.micode.notes.R; + import net.micode.notes.data.Notes; + import net.micode.notes.tool.ResourceParser; + + // NoteWidgetProvider_2x类继承自NoteWidgetProvider,是用于实现特定尺寸(这里可能是2倍尺寸相关,从类名推测)桌面小部件的提供类, + // 它重写了父类的一些方法来定制该尺寸小部件的相关属性和更新逻辑等内容。 + public class NoteWidgetProvider_2x extends NoteWidgetProvider { + + // 重写父类的onUpdate方法,该方法会在桌面小部件需要更新时被调用(例如定时更新或者有相关数据变化触发更新的情况)。 + // 在这里直接调用了父类的update方法,将更新的具体操作委托给父类来处理,自身没有添加额外的更新逻辑(当然也可以根据需要添加特定于2倍尺寸小部件的更新操作)。 + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + super.update(context, appWidgetManager, appWidgetIds); + } + + // 重写父类的getLayoutId方法,用于获取该尺寸桌面小部件对应的布局资源ID。 + // 返回的是R.layout.widget_2x,意味着这个2倍尺寸的小部件会使用名为"widget_2x"的布局文件来进行界面展示, + // 该布局文件中定义了小部件在界面上显示的各种控件及其布局方式等内容。 + @Override + protected int getLayoutId() { + return R.layout.widget_2x; + } + + // 重写父类的getBgResourceId方法,该方法的作用是根据传入的背景资源ID(bgId)获取适用于该尺寸桌面小部件的背景资源ID。 + // 通过调用ResourceParser.WidgetBgResources工具类中的getWidget2xBgResource方法,传入bgId来获取对应2倍尺寸小部件的背景资源, + // 这样可以针对不同的背景设置需求,为该尺寸小部件准确配置合适的背景样式。 + @Override + protected int getBgResourceId(int bgId) { + return ResourceParser.WidgetBgResources.getWidget2xBgResource(bgId); + } + + // 重写父类的getWidgetType方法,用于明确该桌面小部件的类型。 + // 返回的是Notes.TYPE_WIDGET_2X,以此标识该小部件属于特定的2倍尺寸类型,方便在应用中对不同类型的小部件进行区分和针对性的管理、操作等处理。 + @Override + protected int getWidgetType() { + return Notes.TYPE_WIDGET_2X; + } + } \ No newline at end of file diff --git a/src/net/micode/notes/widget/NoteWidgetProvider_4x.java b/src/net/micode/notes/widget/NoteWidgetProvider_4x.java index c12a02e..4d5fdf3 100644 --- a/src/net/micode/notes/widget/NoteWidgetProvider_4x.java +++ b/src/net/micode/notes/widget/NoteWidgetProvider_4x.java @@ -14,33 +14,48 @@ * limitations under the License. */ -package net.micode.notes.widget; - -import android.appwidget.AppWidgetManager; -import android.content.Context; - -import net.micode.notes.R; -import net.micode.notes.data.Notes; -import net.micode.notes.tool.ResourceParser; - - -public class NoteWidgetProvider_4x extends NoteWidgetProvider { - @Override - public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { - super.update(context, appWidgetManager, appWidgetIds); - } - - protected int getLayoutId() { - return R.layout.widget_4x; - } - - @Override - protected int getBgResourceId(int bgId) { - return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); - } - - @Override - protected int getWidgetType() { - return Notes.TYPE_WIDGET_4X; - } -} + package net.micode.notes.widget; + + import android.appwidget.AppWidgetManager; + import android.content.Context; + + import net.micode.notes.R; + import net.micode.notes.data.Notes; + import net.micode.notes.tool.ResourceParser; + + // NoteWidgetProvider_4x类继承自NoteWidgetProvider,它主要用于处理特定的4倍尺寸桌面小部件相关的逻辑, + // 通过重写父类的部分方法来定制该4倍尺寸小部件在布局、背景资源以及类型标识等方面的特性。 + + public class NoteWidgetProvider_4x extends NoteWidgetProvider { + + // 重写父类的onUpdate方法,此方法会在桌面小部件需要更新时被调用,例如系统定时触发小部件更新、相关数据变化导致小部件内容需刷新等情况。 + // 在这里它直接调用了父类的update方法,将具体的更新操作交给父类去执行,自身暂时没有添加额外的、针对4倍尺寸小部件特有的更新逻辑, + // 不过后续可以根据具体需求在此方法中添加相应内容,比如根据4倍尺寸特点来更新小部件显示的内容等操作。 + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + super.update(context, appWidgetManager, appWidgetIds); + } + + // 重写父类的getLayoutId方法,其目的是获取该4倍尺寸桌面小部件所对应的布局资源ID。 + // 这里返回R.layout.widget_4x,表示这个4倍尺寸的小部件将会使用名为“widget_4x”的布局文件来进行界面展示, + // 该布局文件中定义了适合4倍尺寸小部件的各种控件摆放位置、大小以及样式等布局相关内容,以此来呈现出符合该尺寸特点的视觉效果。 + protected int getLayoutId() { + return R.layout.widget_4x; + } + + // 重写父类的getBgResourceId方法,该方法负责根据传入的背景资源ID(bgId)来获取适用于这个4倍尺寸桌面小部件的背景资源ID。 + // 通过调用ResourceParser.WidgetBgResources工具类中的getWidget4xBgResource方法,并传入bgId参数,就能得到对应4倍尺寸小部件的特定背景资源, + // 这样可以依据不同的背景设置需求,为该尺寸小部件准确地配置合适的背景样式,使其在外观上更符合整体设计要求。 + @Override + protected int getBgResourceId(int bgId) { + return ResourceParser.WidgetBgResources.getWidget4xBgResource(bgId); + } + + // 重写父类的getWidgetType方法,用于明确标识该桌面小部件的类型为4倍尺寸类型。 + // 返回的是Notes.TYPE_WIDGET_4X,借助这个返回值,在整个应用中就能方便地区分不同尺寸类型的小部件,进而对其进行有针对性的管理、操作以及相关业务逻辑处理, + // 比如针对不同类型小部件采用不同的数据加载方式、显示规则等。 + @Override + protected int getWidgetType() { + return Notes.TYPE_WIDGET_4X; + } + } \ No newline at end of file