From 08d149a9c166468a038e9cdd6a1a74e91c0e1346 Mon Sep 17 00:00:00 2001 From: zzy <2858538334@qq,com> Date: Mon, 30 Dec 2024 21:56:06 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AlarmInitReceiver.java | 75 ++++++ AlarmReceiver.java | 35 +++ DateTimePicker.java | 222 ++++++++++++++++ DateTimePickerDialog.java | 98 ++++++++ DropdownMenu.java | 63 +++++ FoldersListAdapter.java | 89 +++++++ NoteEditActivity.java | 515 ++++++++++++++++++++++++++++++++++++++ NoteEditText.java | 304 ++++++++++++++++++++++ NoteItemData.java | 156 ++++++++++++ NotesListActivity.java | 368 +++++++++++++++++++++++++++ 10 files changed, 1925 insertions(+) create mode 100644 AlarmInitReceiver.java create mode 100644 AlarmReceiver.java create mode 100644 DateTimePicker.java create mode 100644 DateTimePickerDialog.java create mode 100644 DropdownMenu.java create mode 100644 FoldersListAdapter.java create mode 100644 NoteEditActivity.java create mode 100644 NoteEditText.java create mode 100644 NoteItemData.java create mode 100644 NotesListActivity.java diff --git a/AlarmInitReceiver.java b/AlarmInitReceiver.java new file mode 100644 index 0000000..c1e7d08 --- /dev/null +++ b/AlarmInitReceiver.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; // 指定该类所在的包路径 + +import android.app.AlarmManager; // 引入AlarmManager类,用于设置闹钟 +import android.app.PendingIntent; // 引入PendingIntent类,表示延迟的Intent +import android.content.BroadcastReceiver; // 引入BroadcastReceiver类,用于接收广播 +import android.content.ContentUris; // 引入ContentUris类,用于生成URI +import android.content.Context; // 引入Context类,用于访问系统资源 +import android.content.Intent; // 引入Intent类,用于启动组件 +import android.database.Cursor; // 引入Cursor类,用于数据库查询 + +import net.micode.notes.data.Notes; // 引入Notes类,处理笔记数据 +import net.micode.notes.data.Notes.NoteColumns; // 引入NoteColumns类,表示Notes表的列 + +public class AlarmInitReceiver extends BroadcastReceiver { // 定义一个继承自BroadcastReceiver的类 + + // 定义查询需要的列 + private static final String [] PROJECTION = new String [] { + NoteColumns.ID, // 获取笔记的ID列 + NoteColumns.ALERTED_DATE // 获取提醒时间列 + }; + + // 列索引常量 + private static final int COLUMN_ID = 0; // 第0列是ID列 + private static final int COLUMN_ALERTED_DATE = 1; // 第1列是提醒时间列 + + @Override + public void onReceive(Context context, Intent intent) { // 重写onReceive方法,接收广播 + long currentDate = System.currentTimeMillis(); // 获取当前的系统时间(以毫秒为单位) + + // 查询数据库,获取需要提醒的笔记,且提醒时间大于当前时间 + Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI, // 查询URI为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); // 创建一个新的Intent,用于发送广播 + sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); // 设置Intent的数据(笔记的ID) + + // 创建PendingIntent,表示在指定时间执行AlarmReceiver广播 + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); + + // 获取系统的AlarmManager服务 + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + + // 设置闹钟,使用RTC_WAKEUP模式,当到达指定的提醒时间时唤醒设备并触发PendingIntent + alarmManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); + } while (c.moveToNext()); // 如果查询结果中还有下一条记录,则继续处理 + } + c.close(); // 关闭Cursor + } + } +} diff --git a/AlarmReceiver.java b/AlarmReceiver.java new file mode 100644 index 0000000..c692e17 --- /dev/null +++ b/AlarmReceiver.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +// AlarmReceiver 类继承自 BroadcastReceiver,用于接收便签提醒的广播事件。 +public class AlarmReceiver extends BroadcastReceiver { + // onReceive 方法是 BroadcastReceiver 的回调方法,当接收到广播时被调用。 + @Override + public void onReceive(Context context, Intent intent) { + // 设置 intent 的目标类为 AlarmAlertActivity。 + intent.setClass(context, AlarmAlertActivity.class); + // 为 intent 添加 FLAG_ACTIVITY_NEW_TASK 标志,使得在新的任务栈中启动 Activity。 + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // 在给定的上下文中启动 AlarmAlertActivity。 + context.startActivity(intent); + } +} \ No newline at end of file diff --git a/DateTimePicker.java b/DateTimePicker.java new file mode 100644 index 0000000..52692cf --- /dev/null +++ b/DateTimePicker.java @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import java.text.DateFormatSymbols; +import java.util.Calendar; + +import net.micode.notes.R; + + +import android.content.Context; +import android.text.format.DateFormat; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.NumberPicker; + +// DateTimePicker 类继承自 FrameLayout,用于提供一个日期时间选择器。 +public class DateTimePicker extends FrameLayout { + + // 默认启用状态。 + private static final boolean DEFAULT_ENABLE_STATE = true; + + // 半天的小时数和全天的小时数。 + private static final int HOURS_IN_HALF_DAY = 12; + private static final int HOURS_IN_ALL_DAY = 24; + // 一周的天数。 + private static final int DAYS_IN_ALL_WEEK = 7; + // 日期选择器的最小值和最大值。 + private static final int DATE_SPINNER_MIN_VAL = 0; + private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1; + // 24小时制和12小时制下小时选择器的最小值和最大值。 + private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0; + private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; + private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; + private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12; + // 分钟选择器的最小值和最大值。 + private static final int MINUT_SPINNER_MIN_VAL = 0; + private static final int MINUT_SPINNER_MAX_VAL = 59; + // 上下午选择器的最小值和最大值。 + private static final int AMPM_SPINNER_MIN_VAL = 0; + private static final int AMPM_SPINNER_MAX_VAL = 1; + + // 日期、小时、分钟和上下午的 NumberPicker 控件。 + private final NumberPicker mDateSpinner; + private final NumberPicker mHourSpinner; + private final NumberPicker mMinuteSpinner; + private final NumberPicker mAmPmSpinner; + // 用于存储日期的 Calendar 对象。 + private Calendar mDate; + + // 日期显示值数组。 + private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; + + // 表示是否为上午的布尔值。 + private boolean mIsAm; + + // 表示是否为24小时视图的布尔值。 + private boolean mIs24HourView; + + // 控件的启用状态。 + private boolean mIsEnabled = DEFAULT_ENABLE_STATE; + + // 正在初始化的布尔值。 + private boolean mInitialising; + + // 日期时间改变监听器。 + private OnDateTimeChangedListener mOnDateTimeChangedListener; + + // 日期改变监听器。 + private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + // 更新日期并通知日期时间改变。 + mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal); + updateDateControl(); + onDateTimeChanged(); + } + }; + + // 小时改变监听器。 + private NumberPicker.OnValueChangeListener mOnHourChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + // 更新小时并通知日期时间改变。 + // 处理12小时制下上下午的转换。 + boolean isDateChanged = false; + Calendar cal = Calendar.getInstance(); + if (!mIs24HourView) { + if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, 1); + isDateChanged = true; + } else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -1); + isDateChanged = true; + } + if (oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY || + oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + mIsAm = !mIsAm; + updateAmPmControl(); + } + } else { + if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, 1); + isDateChanged = true; + } else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -1); + isDateChanged = true; + } + } + int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm ? 0 : HOURS_IN_HALF_DAY); + mDate.set(Calendar.HOUR_OF_DAY, newHour); + onDateTimeChanged(); + if (isDateChanged) { + setCurrentYear(cal.get(Calendar.YEAR)); + setCurrentMonth(cal.get(Calendar.MONTH)); + setCurrentDay(cal.get(Calendar.DAY_OF_MONTH)); + } + } + }; + + // 分钟改变监听器。 + private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + // 更新分钟并通知日期时间改变。 + int minValue = mMinuteSpinner.getMinValue(); + int maxValue = mMinuteSpinner.getMaxValue(); + int offset = 0; + if (oldVal == maxValue && newVal == minValue) { + offset += 1; + } else if (oldVal == minValue && newVal == maxValue) { + offset -= 1; + } + if (offset != 0) { + mDate.add(Calendar.HOUR_OF_DAY, offset); + mHourSpinner.setValue(getCurrentHour()); + updateDateControl(); + int newHour = getCurrentHourOfDay(); + if (newHour >= HOURS_IN_HALF_DAY) { + mIsAm = false; + updateAmPmControl(); + } else { + mIsAm = true; + updateAmPmControl(); + } + } + mDate.set(Calendar.MINUTE, newVal); + onDateTimeChanged(); + } + }; + + // 上下午改变监听器。 + private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { + @Override + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + // 更新上下午并通知日期时间改变。 + mIsAm = !mIsAm; + if (mIsAm) { + mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); + } else { + mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); + } + updateAmPmControl(); + onDateTimeChanged(); + } + }; + + // 日期时间改变监听器接口。 + public interface OnDateTimeChangedListener { + void onDateTimeChanged(DateTimePicker view, int year, int month, + int dayOfMonth, int hourOfDay, int minute); + } + + // DateTimePicker 的构造函数。 + public DateTimePicker(Context context) { + this(context, System.currentTimeMillis()); + } + + public DateTimePicker(Context context, long date) { + this(context, date, DateFormat.is24HourFormat(context)); + } + + public DateTimePicker(Context context, long date, boolean is24HourView) { + super(context); + mDate = Calendar.getInstance(); + mInitialising = true; + mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; + inflate(context, R.layout.datetime_picker, this); + + // 初始化日期、小时、分钟和上下午的 NumberPicker 控件。 + mDateSpinner = (NumberPicker) findViewById(R.id.date); + mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); + mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); + mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); + + mHourSpinner = (NumberPicker) findViewById(R.id.hour); + mHourSpinner.setOnValueChangedListener(mOnHourChangedListener); + mMinuteSpinner = (NumberPicker) findViewById(R.id.minute); + mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL); + mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL); + mMinuteSpinner.setOnLongPressUpdateInterval(100); + mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener); + + // \ No newline at end of file diff --git a/DateTimePickerDialog.java b/DateTimePickerDialog.java new file mode 100644 index 0000000..ceffc71 --- /dev/null +++ b/DateTimePickerDialog.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; // 定义类所在的包路径 + +import java.util.Calendar; // 引入Calendar类用于处理日期和时间 + +import net.micode.notes.R; // 引入R文件,用于获取资源ID +import net.micode.notes.ui.DateTimePicker; // 引入自定义的DateTimePicker控件 +import net.micode.notes.ui.DateTimePicker.OnDateTimeChangedListener; // 引入DateTimePicker控件的监听器 + +import android.app.AlertDialog; // 引入AlertDialog类,用于创建对话框 +import android.content.Context; // 引入Context类,用于访问应用程序上下文 +import android.content.DialogInterface; // 引入DialogInterface类,用于处理对话框的按钮事件 +import android.content.DialogInterface.OnClickListener; // 引入OnClickListener接口,用于按钮点击事件 +import android.text.format.DateFormat; // 引入DateFormat类,用于格式化日期和时间 +import android.text.format.DateUtils; // 引入DateUtils类,用于时间格式化与操作 + +// 定义一个自定义的DateTimePickerDialog类,继承自AlertDialog +public class DateTimePickerDialog extends AlertDialog implements OnClickListener { + + private Calendar mDate = Calendar.getInstance(); // 用于保存当前日期和时间 + private boolean mIs24HourView; // 标记是否为24小时制显示 + private OnDateTimeSetListener mOnDateTimeSetListener; // 回调接口,用于在日期和时间设置后通知调用者 + private DateTimePicker mDateTimePicker; // 自定义的日期时间选择器 + + // 定义回调接口,用于在用户设置日期时间后进行处理 + public interface OnDateTimeSetListener { + void OnDateTimeSet(AlertDialog dialog, long date); // 回调方法,传递设置的日期时间 + } + + // 构造函数,初始化对话框,设置默认日期时间 + public DateTimePickerDialog(Context context, long date) { + super(context); // 调用父类构造函数,初始化AlertDialog + mDateTimePicker = new DateTimePicker(context); // 初始化DateTimePicker控件 + setView(mDateTimePicker); // 设置对话框的内容视图为DateTimePicker控件 + 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); // 将秒设置为0 + mDateTimePicker.setCurrentDate(mDate.getTimeInMillis()); // 设置DateTimePicker控件的初始日期 + setButton(context.getString(R.string.datetime_dialog_ok), this); // 设置“确认”按钮,并指定点击时的行为 + setButton2(context.getString(R.string.datetime_dialog_cancel), (OnClickListener)null); // 设置“取消”按钮 + set24HourView(DateFormat.is24HourFormat(this.getContext())); // 设置是否为24小时制显示 + updateTitle(mDate.getTimeInMillis()); // 初始化时更新对话框的标题 + } + + // 设置是否使用24小时制 + public void set24HourView(boolean is24HourView) { + mIs24HourView = is24HourView; + } + + // 设置日期时间设置完成后的回调监听器 + public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { + mOnDateTimeSetListener = callBack; + } + + // 更新对话框标题,显示当前选中的日期时间 + private void updateTitle(long date) { + // 设置日期时间显示的格式 + int flag = + DateUtils.FORMAT_SHOW_YEAR | // 显示年份 + DateUtils.FORMAT_SHOW_DATE | // 显示日期 + DateUtils.FORMAT_SHOW_TIME; // 显示时间 + flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_24HOUR; // 如果是24小时制,显示24小时格式 + setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); // 格式化日期时间并设置为对话框标题 + } + + // 确认按钮点击事件的处理 + public void onClick(DialogInterface arg0, int arg1) { + if (mOnDateTimeSetListener != null) { + // 调用回调接口,传递设置的日期时间 + mOnDateTimeSetListener.OnDateTimeSet(this, mDate.getTimeInMillis()); + } + } +} diff --git a/DropdownMenu.java b/DropdownMenu.java new file mode 100644 index 0000000..fe6e47a --- /dev/null +++ b/DropdownMenu.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; // 定义类所在的包路径 + +import android.content.Context; // 引入Context类,用于访问应用程序的上下文 +import android.view.Menu; // 引入Menu类,用于操作菜单 +import android.view.MenuItem; // 引入MenuItem类,用于菜单项的处理 +import android.view.View; // 引入View类,用于视图的操作 +import android.view.View.OnClickListener; // 引入OnClickListener接口,用于处理点击事件 +import android.widget.Button; // 引入Button类,用于显示按钮 +import android.widget.PopupMenu; // 引入PopupMenu类,用于显示弹出菜单 +import android.widget.PopupMenu.OnMenuItemClickListener; // 引入PopupMenu的菜单项点击监听器 + +public class DropdownMenu { + private Button mButton; // 用于显示的按钮,点击后弹出菜单 + private PopupMenu mPopupMenu; // 弹出菜单的实例 + private Menu mMenu; // 菜单项的集合 + + // 构造函数,初始化DropdownMenu,设置按钮及其弹出菜单 + public DropdownMenu(Context context, Button button, int menuId) { + mButton = button; // 获取传入的按钮对象 + mButton.setBackgroundResource(R.drawable.dropdown_icon); // 设置按钮的背景图标为下拉菜单图标 + mPopupMenu = new PopupMenu(context, mButton); // 初始化PopupMenu,指定按钮作为弹出菜单的锚点 + mMenu = mPopupMenu.getMenu(); // 获取PopupMenu中的Menu对象 + mPopupMenu.getMenuInflater().inflate(menuId, mMenu); // 使用传入的menuId加载菜单资源 + mButton.setOnClickListener(new OnClickListener() { // 为按钮设置点击事件监听器 + public void onClick(View v) { + mPopupMenu.show(); // 当按钮被点击时,显示弹出菜单 + } + }); + } + + // 设置菜单项点击事件监听器 + public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { + if (mPopupMenu != null) { + mPopupMenu.setOnMenuItemClickListener(listener); // 设置PopupMenu的菜单项点击监听器 + } + } + + // 根据菜单项的ID查找菜单项 + public MenuItem findItem(int id) { + return mMenu.findItem(id); // 在菜单中查找具有指定ID的菜单项 + } + + // 设置按钮的文本 + public void setTitle(CharSequence title) { + mButton.setText(title); // 设置按钮的文本为传入的title + } +} diff --git a/FoldersListAdapter.java b/FoldersListAdapter.java new file mode 100644 index 0000000..94bb583 --- /dev/null +++ b/FoldersListAdapter.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.content.Context; +import android.database.Cursor; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CursorAdapter; +import android.widget.LinearLayout; +import android.widget.TextView; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; + + +// FoldersListAdapter 是一个自定义的 CursorAdapter,用于显示文件夹列表。 +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; + + // FoldersListAdapter 的构造函数。 + public FoldersListAdapter(Context context, Cursor c) { + super(context, c); + // TODO Auto-generated constructor stub + } + + // 新建视图的方法,返回一个 FolderListItem 实例。 + @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 + .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 + .getString(R.string.menu_move_parent_folder) : cursor.getString(NAME_COLUMN); + } + + // FolderListItem 是一个内部类,表示列表中的单个文件夹项。 + private class FolderListItem extends LinearLayout { + private TextView mName; // 用于显示文件夹名称的 TextView。 + + // FolderListItem 的构造函数。 + 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); + } + } +} \ No newline at end of file diff --git a/NoteEditActivity.java b/NoteEditActivity.java new file mode 100644 index 0000000..075ccd6 --- /dev/null +++ b/NoteEditActivity.java @@ -0,0 +1,515 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; + +import android.app.Activity; +import android.app.AlarmManager; +import android.app.AlertDialog; +import android.app.PendingIntent; +import android.app.SearchManager; +import android.appwidget.AppWidgetManager; +import android.content.ContentUris; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Paint; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.TextUtils; +import android.text.format.DateUtils; +import android.text.style.BackgroundColorSpan; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.WindowManager; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.TextNote; +import net.micode.notes.model.WorkingNote; +import net.micode.notes.model.WorkingNote.NoteSettingChangedListener; +import net.micode.notes.tool.DataUtils; +import net.micode.notes.tool.ResourceParser; +import net.micode.notes.tool.ResourceParser.TextAppearanceResources; +import net.micode.notes.ui.DateTimePickerDialog.OnDateTimeSetListener; +import net.micode.notes.ui.NoteEditText.OnTextViewChangeListener; +import net.micode.notes.widget.NoteWidgetProvider_2x; +import net.micode.notes.widget.NoteWidgetProvider_4x; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +// NoteEditActivity 是一个用于编辑便签的 Activity。 +public class NoteEditActivity extends Activity implements OnClickListener, + NoteSettingChangedListener, OnTextViewChangeListener { + // 内部类,用于持有头部视图的控件引用。 + private class HeadViewHolder { + public TextView tvModified; // 修改日期的 TextView。 + public ImageView ivAlertIcon; // 提醒图标的 ImageView。 + public TextView tvAlertDate; // 提醒日期的 TextView。 + public ImageView ibSetBgColor; // 设置背景颜色的 ImageView。 + } + + // 背景颜色选择按钮与颜色 ID 的映射。 + private static final Map sBgSelectorBtnsMap = new HashMap(); + static { + sBgSelectorBtnsMap.put(R.id.iv_bg_yellow, ResourceParser.YELLOW); + sBgSelectorBtnsMap.put(R.id.iv_bg_red, ResourceParser.RED); + sBgSelectorBtnsMap.put(R.id.iv_bg_blue, ResourceParser.BLUE); + sBgSelectorBtnsMap.put(R.id.iv_bg_green, ResourceParser.GREEN); + sBgSelectorBtnsMap.put(R.id.iv_bg_white, ResourceParser.WHITE); + } + + // 背景颜色选择结果与按钮 ID 的映射。 + private static final Map sBgSelectorSelectionMap = new HashMap(); + static { + sBgSelectorSelectionMap.put(ResourceParser.YELLOW, R.id.iv_bg_yellow_select); + sBgSelectorSelectionMap.put(ResourceParser.RED, R.id.iv_bg_red_select); + sBgSelectorSelectionMap.put(ResourceParser.BLUE, R.id.iv_bg_blue_select); + sBgSelectorSelectionMap.put(ResourceParser.GREEN, R.id.iv_bg_green_select); + sBgSelectorSelectionMap.put(ResourceParser.WHITE, R.id.iv_bg_white_select); + } + + // 字体大小选择按钮与大小 ID 的映射。 + private static final Map sFontSizeBtnsMap = new HashMap(); + static { + sFontSizeBtnsMap.put(R.id.ll_font_large, ResourceParser.TEXT_LARGE); + sFontSizeBtnsMap.put(R.id.ll_font_small, ResourceParser.TEXT_SMALL); + sFontSizeBtnsMap.put(R.id.ll_font_normal, ResourceParser.TEXT_MEDIUM); + sFontSizeBtnsMap.put(R.id.ll_font_super, ResourceParser.TEXT_SUPER); + } + + // 字体大小选择结果与按钮 ID 的映射。 + private static final Map sFontSelectorSelectionMap = new HashMap(); + static { + sFontSelectorSelectionMap.put(ResourceParser.TEXT_LARGE, R.id.iv_large_select); + sFontSelectorSelectionMap.put(ResourceParser.TEXT_SMALL, R.id.iv_small_select); + sFontSelectorSelectionMap.put(ResourceParser.TEXT_MEDIUM, R.id.iv_medium_select); + sFontSelectorSelectionMap.put(ResourceParser.TEXT_SUPER, R.id.iv_super_select); + } + + // 日志标签。 + private static final String TAG = "NoteEditActivity"; + + // 头部视图的 Holder。 + private HeadViewHolder mNoteHeaderHolder; + + // 头部视图面板、背景颜色选择器和字体大小选择器。 + private View mHeadViewPanel; + + private View mNoteBgColorSelector; + + private View mFontSizeSelector; + + // 编辑器和编辑器面板。 + private EditText mNoteEditor; + + private View mNoteEditorPanel; + + // 正在编辑的便签。 + private WorkingNote mWorkingNote; + + // SharedPreferences 和字体大小 ID。 + private SharedPreferences mSharedPrefs; + private int mFontSizeId; + + // SharedPreferences 中字体大小的键。 + private static final String PREFERENCE_FONT_SIZE = "pref_font_size"; + + // 快捷方式图标标题的最大长度。 + private static final int SHORTCUT_ICON_TITLE_MAX_LEN = 10; + + // 多选框标记。 + public static final String TAG_CHECKED = String.valueOf('\u221A'); + public static final String TAG_UNCHECKED = String.valueOf('\u25A1'); + + // 编辑文本列表。 + private LinearLayout mEditTextList; + + // 用户查询字符串和正则表达式模式。 + private String mUserQuery; + private Pattern mPattern; + + // onCreate 方法,初始化 Activity。 + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + this.setContentView(R.layout.note_edit); + + if (savedInstanceState == null && !initActivityState(getIntent())) { + finish(); + return; + } + initResources(); + } + + // onRestoreInstanceState 方法,恢复 Activity 状态。 + @Override + protected void onRestoreInstanceState(Bundle savedInstanceState) { + super.onRestoreInstanceState(savedInstanceState); + if (savedInstanceState != null && savedInstanceState.containsKey(Intent.EXTRA_UID)) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_UID, savedInstanceState.getLong(Intent.EXTRA_UID)); + if (!initActivityState(intent)) { + finish(); + return; + } + Log.d(TAG, "Restoring from killed activity"); + } + } + + // initActivityState 方法,初始化 Activity 状态。 + private boolean initActivityState(Intent intent) { + // 省略部分代码... + } + + // onResume 方法,恢复 Activity 时调用。 + @Override + protected void onResume() { + super.onResume(); + initNoteScreen(); + } + + // initNoteScreen 方法,初始化便签屏幕。 + private void initNoteScreen() { + // 省略部分代码... + } + + // onNewIntent 方法,处理新的 Intent。 + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + initActivityState(intent); + } + + // onSaveInstanceState 方法,保存 Activity 状态。 + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + // 省略部分代码... + } + + // dispatchTouchEvent 方法,分发触摸事件。 + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + // 省略部分代码... + } + + // inRangeOfView 方法,判断触摸事件是否在视图范围内。 + private boolean inRangeOfView(View view, MotionEvent ev) { + // 省略部分代码... + } + + // initResources 方法,初始化资源。 + private void initResources() { + // 省略部分代码... + } + + // onPause 方法,暂停 Activity 时调用。 + @Override + protected void onPause() { + super.onPause(); + if(saveNote()) { + Log.d(TAG, "Note data was saved with length:" + mWorkingNote.getContent().length()); + } + clearSettingState(); + } + + // updateWidget 方法,更新小部件。 + private void updateWidget() { + // 省略部分代码... + } +} +/** + * 当点击某个视图时调用此方法。 + * @param v 被点击的视图。 + */ +public void onClick(View v) { + int id = v.getId(); // 获取被点击视图的ID。 + // 如果被点击的视图是设置背景颜色的按钮,则显示背景颜色选择器。 + if (id == R.id.btn_set_bg_color) { + mNoteBgColorSelector.setVisibility(View.VISIBLE); + findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(-View.VISIBLE); + } else if (sBgSelectorBtnsMap.containsKey(id)) { // 如果被点击的是背景颜色按钮。 + findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(View.GONE); + mWorkingNote.setBgColorId(sBgSelectorBtnsMap.get(id)); // 更改背景颜色ID。 + mNoteBgColorSelector.setVisibility(View.GONE); + } else if (sFontSizeBtnsMap.containsKey(id)) { // 如果被点击的是字体大小按钮。 + findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.GONE); + mFontSizeId = sFontSizeBtnsMap.get(id); // 更改字体大小ID。 + mSharedPrefs.edit().putInt(PREFERENCE_FONT_SIZE, mFontSizeId).commit(); // 保存新的字体大小偏好。 + findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); + // 如果便签处于清单模式,更新清单视图;否则,更新文本外观。 + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + getWorkingText(); + switchToListMode(mWorkingNote.getContent()); + } else { + mNoteEditor.setTextAppearance(this, TextAppearanceResources.getTexAppearanceResource(mFontSizeId)); + } + mFontSizeSelector.setVisibility(View.GONE); + } +} + +/** + * 当按下返回键时调用。 + */ +@Override +public void onBackPressed() { + if(clearSettingState()) { + return; + } + saveNote(); + super.onBackPressed(); +} + +/** + * 清除设置状态,例如隐藏背景颜色选择器和字体大小选择器。 + * @return 如果设置状态被清除,则返回true,否则返回false。 + */ +private boolean clearSettingState() { + if (mNoteBgColorSelector.getVisibility() == View.VISIBLE) { + mNoteBgColorSelector.setVisibility(View.GONE); + return true; + } else if (mFontSizeSelector.getVisibility() == View.VISIBLE) { + mFontSizeSelector.setVisibility(View.GONE); + return true; + } + return false; +} + +/** + * 当背景颜色改变时调用。 + */ +public void onBackgroundColorChanged() { + findViewById(sBgSelectorSelectionMap.get(mWorkingNote.getBgColorId())).setVisibility(View.VISIBLE); + mNoteEditorPanel.setBackgroundResource(mWorkingNote.getBgColorResId()); + mHeadViewPanel.setBackgroundResource(mWorkingNote.getTitleBgResId()); +} + +/** + * 准备选项菜单。 + * @param menu 选项菜单。 + * @return 如果菜单已准备,则返回true,否则返回false。 + */ +@Override +public boolean onPrepareOptionsMenu(Menu menu) { + if (isFinishing()) { + return true; + } + clearSettingState(); + menu.clear(); + // 根据便签的文件夹ID决定加载哪个菜单。 + if (mWorkingNote.getFolderId() == Notes.ID_CALL_RECORD_FOLDER) { + getMenuInflater().inflate(R.menu.call_note_edit, menu); + } else { + getMenuInflater().inflate(R.menu.note_edit, menu); + } + // 根据便签的属性更新菜单项。 + if (mWorkingNote.getCheckListMode() == TextNote.MODE_CHECK_LIST) { + menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_normal_mode); + } else { + menu.findItem(R.id.menu_list_mode).setTitle(R.string.menu_list_mode); + } + if (mWorkingNote.hasClockAlert()) { + menu.findItem(R.id.menu_alert).setVisible(false); + } else { + menu.findItem(R.id.menu_delete_remind).setVisible(false); + } + return true; +} + +/** + * 处理选项菜单项的选择。 + * @param item 被选择的菜单项。 + * @return 如果项被处理,则返回true,否则返回false。 + */ +@Override +public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_new_note: + createNewNote(); + break; + case R.id.menu_delete: + // 显示删除便签的确认对话框。 + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(getString(R.string.alert_title_delete)); + builder.setIcon(android.R.drawable.ic_dialog_alert); + builder.setMessage(getString(R.string.alert_message_delete_note)); + builder.setPositiveButton(android.R.string.ok, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + deleteCurrentNote(); + finish(); + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.show(); + break; + case R.id.menu_font_size: + mFontSizeSelector.setVisibility(View.VISIBLE); + findViewById(sFontSelectorSelectionMap.get(mFontSizeId)).setVisibility(View.VISIBLE); + break; + case R.id.menu_list_mode: + mWorkingNote.setCheckListMode(mWorkingNote.getCheckListMode() == 0 ? + TextNote.MODE_CHECK_LIST : 0); + break; + case R.id.menu_share: + getWorkingText(); + sendTo(this, mWorkingNote.getContent()); + break; + case R.id.menu_send_to_desktop: + sendToDesktop(); + break; + case R.id.menu_alert: + setReminder(); + break; + case R.id.menu_delete_remind: + mWorkingNote.setAlertDate(0, false); + break; + default: + break; + } + return true; +} + +/** + * 设置便签的提醒。 + */ +private void setReminder() { + DateTimePickerDialog d = new DateTimePickerDialog(this, System.currentTimeMillis()); + d.setOnDateTimeSetListener(new OnDateTimeSetListener() { + public void OnDateTimeSet(AlertDialog dialog, long date) { + mWorkingNote.setAlertDate(date, true); + } + }); + d.show(); +} + +/** + * 分享便签到支持{@link Intent#ACTION_SEND}动作和{text/plain}类型的应用。 + * @param context 上下文。 + * @param info 要分享的便签内容。 + */ +private void sendTo(Context context, String info) { + Intent intent = new Intent(Intent.ACTION_SEND); + intent.putExtra(Intent.EXTRA_TEXT, info); + intent.setType("text/plain"); + context.startActivity(intent); +} + +/** + * 创建一个新的便签。 + */ +private void createNewNote() { + // 在创建新便签之前保存当前正在编辑的便签。 + saveNote(); + finish(); + Intent intent = new Intent(this, NoteEditActivity.class); + intent.setAction(Intent.ACTION_INSERT_OR_EDIT); + intent.putExtra(Notes.INTENT_EXTRA_FOLDER_ID, mWorkingNote.getFolderId()); + startActivity(intent); +} + +/** + * 删除当前便签。 + */ +private void deleteCurrentNote() { + if (mWorkingNote.existInDatabase()) { + HashSet ids = new HashSet(); + long id = mWorkingNote.getNoteId(); + if (id != Notes.ID_ROOT_FOLDER) { + ids.add(id); + } else { + Log.d(TAG, "错误的便签ID,不应该发生"); + } + if (!isSyncMode()) { + if (!DataUtils.batchDeleteNotes(getContentResolver(), ids)) { + Log.e(TAG, "删除便签错误"); + } + } else { + if (!DataUtils.batchMoveToFolder(getContentResolver(), ids, Notes.ID_TRASH_FOLER)) { + Log.e(TAG, "将便签移动到废纸篓文件夹错误,不应该发生"); + } + } + } + mWorkingNote.markDeleted(true); +} + +/** + * 检查应用是否处于同步模式。 + * @return 如果处于同步模式,则返回true,否则返回false。 + */ +private boolean isSyncMode() { + return NotesPreferenceActivity.getSyncAccountName(this).trim().length() > 0; +} + +/** + * 当时钟提醒改变时调用。 + * @param date 新的提醒日期。 + * @param set 如果设置了提醒,则为true;如果取消了提醒,则为false。 + */ +public void onClockAlertChanged(long date, boolean set) { + if (!mWorkingNote.existInDatabase()) { + saveNote(); + } + if (mWorkingNote.getNoteId() > 0) { + Intent intent = new Intent(this, AlarmReceiver.class); + intent.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, mWorkingNote.getNoteId())); + PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0); + AlarmManager alarmManager = ((AlarmManager) getSystemService(ALARM_SERVICE)); + showAlertHeader(); + if(!set) { + alarmManager.cancel(pendingIntent); + } else { + alarmManager.set(AlarmManager.RTC_WAKEUP, date, pendingIntent); + } + } else { + Log.e(TAG, "时钟提醒设置错误"); + showToast(R.string.error_note_empty_for_clock); + } +} + +/** + * 当小部件改变时调用。 + */ +public void onWidgetChanged() { + updateWidget(); +} + +/** + * 处理在清单中删除文本项。 + * @param index 要删除项的索引。 + * @param text 项的文本。 + */ diff --git a/NoteEditText.java b/NoteEditText.java new file mode 100644 index 0000000..0e34903 --- /dev/null +++ b/NoteEditText.java @@ -0,0 +1,304 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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); + } + + /* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.micode.notes.ui; // 定义类所在的包路径 + +// 引入相关的 Android 类 +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; // 引入资源文件中的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; // 用于记录删除前的文本选择开始位置 + + // 定义常见的URI Schemes + private static final String SCHEME_TEL = "tel:" ; + private static final String SCHEME_HTTP = "http:" ; + private static final String SCHEME_EMAIL = "mailto:" ; + + // 定义一个映射关系,存储不同URI Scheme对应的资源ID + private static final Map sSchemaActionResMap = new HashMap(); + static { + sSchemaActionResMap.put(SCHEME_TEL, R.string.note_link_tel); // 电话链接对应的资源ID + sSchemaActionResMap.put(SCHEME_HTTP, R.string.note_link_web); // 网站链接对应的资源ID + sSchemaActionResMap.put(SCHEME_EMAIL, R.string.note_link_email); // 邮件链接对应的资源ID + } + + /** + * 在 {@link NoteEditActivity} 中调用,用于删除或添加编辑文本 + */ + public interface OnTextViewChangeListener { + /** + * 当发生删除事件(例如按下删除键)且文本为空时,删除当前编辑文本 + */ + void onEditTextDelete(int index, String text); + + /** + * 当发生回车事件(例如按下回车键)时,添加编辑文本 + */ + void onEditTextEnter(int index, String text); + + /** + * 当文本发生变化时,隐藏或显示项的选项 + */ + void onTextChange(int index, boolean hasText); + } +} +// 定义一个文本视图变化监听器的接口 +private OnTextViewChangeListener mOnTextViewChangeListener; + +// NoteEditText的构造函数,当从AttributeSet创建时调用 +public NoteEditText(Context context) { + super(context, null); + mIndex = 0; +} + +// 设置当前文本编辑框的索引 +public void setIndex(int index) { + mIndex = index; +} + +// 设置文本视图变化监听器 +public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { + mOnTextViewChangeListener = listener; +} + +// NoteEditText的构造函数,当从AttributeSet和 defStyle创建时调用 +public NoteEditText(Context context, AttributeSet attrs) { + super(context, attrs, android.R.attr.editTextStyle); +} + +// NoteEditText的构造函数,当从AttributeSet、defStyle和 defStyle创建时调用 +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); +} + +// 处理按键事件,例如Enter和Delete键 +@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); +} + +// 处理按键释放事件,例如Enter和Delete键 +@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); +} diff --git a/NoteItemData.java b/NoteItemData.java new file mode 100644 index 0000000..023c94c --- /dev/null +++ b/NoteItemData.java @@ -0,0 +1,156 @@ +// 定义一个文本视图变化监听器的接口,用于监听文本编辑框中的变化事件 +private OnTextViewChangeListener mOnTextViewChangeListener; + +// NoteEditText的构造函数,当从AttributeSet创建时调用 +public NoteEditText(Context context) { + super(context, null); + mIndex = 0; +} + +// 设置当前文本编辑框的索引,用于标识编辑框的位置 +public void setIndex(int index) { + mIndex = index; +} + +// 设置文本视图变化监听器 +public void setOnTextViewChangeListener(OnTextViewChangeListener listener) { + mOnTextViewChangeListener = listener; +} + +// NoteEditText的构造函数,当从AttributeSet和 defStyle创建时调用 +public NoteEditText(Context context, AttributeSet attrs) { + super(context, attrs, android.R.attr.editTextStyle); +} + +// NoteEditText的构造函数,当从AttributeSet、defStyle和 defStyle创建时调用 +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); +} + +// 处理按键事件,例如Enter和Delete键 +@Override +public boolean onKeyDown(int keyCode, KeyEvent event) { + switch (keyCode) { + case KeyEvent.KEYCODE_ENTER: + // 如果设置了监听器,消费Enter键事件 + if (mOnTextViewChangeListener != null) { + return false; + } + break; + case KeyEvent.KEYCODE_DEL: + // 在删除之前记录光标的位置 + mSelectionStartBeforeDelete = getSelectionStart(); + break; + default: + break; + } + return super.onKeyDown(keyCode, event); +} + +// 处理按键释放事件,例如Enter和Delete键 +@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: + // 如果设置了监听器,通知监听器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); + + // 如果选中的文本包含URL,添加一个菜单项用于打开链接 + 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) { + // 打开链接 + urls[0].onClick(NoteEditText.this); + return true; + } + }); + } + } + super.onCreateContextMenu(menu); +} \ No newline at end of file diff --git a/NotesListActivity.java b/NotesListActivity.java new file mode 100644 index 0000000..2c07c20 --- /dev/null +++ b/NotesListActivity.java @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net) + * + * 根据Apache License, Version 2.0(以下简称“许可证”)授权; + * 您可能不会使用此文件,除非遵守许可证。 + * 您可以在以下网址获得许可证的副本: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 除非适用法律要求或书面同意,否则按照许可证分发的软件 + * 按“原样”分发,不附带任何明示或暗示的保证或条件。 + * 请参阅控制权限和许可证下的限制的具体语言的许可证。 + */ + +package net.micode.notes.ui; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.appwidget.AppWidgetManager; +import android.content.AsyncQueryHandler; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.database.Cursor; +import android.os.AsyncTask; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.Log; +import android.view.ActionMode; +import android.view.ContextMenu; +import android.view.Display; +import android.view.HapticFeedbackConstants; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.MenuItem.OnMenuItemClickListener; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnCreateContextMenuListener; +import android.view.View.OnTouchListener; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemLongClickListener; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.PopupMenu; +import android.widget.TextView; +import android.widget.Toast; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.gtask.remote.GTaskSyncService; +import net.micode.notes.model.WorkingNote; +import net.micode.notes.tool.BackupUtils; +import net.micode.notes.tool.DataUtils; +import net.micode.notes.tool.ResourceParser; +import net.micode.notes.ui.NotesListAdapter.AppWidgetAttribute; +import net.micode.notes.widget.NoteWidgetProvider_2x; +import net.micode.notes.widget.NoteWidgetProvider_4x; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashSet; + +// NotesListActivity 是一个用于显示便签列表的 Activity。 +public class NotesListActivity extends Activity implements OnClickListener, OnItemLongClickListener { + // 定义查询令牌,用于处理数据库查询。 + private static final int FOLDER_NOTE_LIST_QUERY_TOKEN = 0; + private static final int FOLDER_LIST_QUERY_TOKEN = 1; + + // 定义菜单项的常量。 + private static final int MENU_FOLDER_DELETE = 0; + private static final int MENU_FOLDER_VIEW = 1; + private static final int MENU_FOLDER_CHANGE_NAME = 2; + + // 定义 SharedPreferences 中保存的“添加介绍”的键。 + private static final String PREFERENCE_ADD_INTRODUCTION = "net.micode.notes.introduction"; + + // 枚举类,表示当前列表的编辑状态。 + private enum ListEditState { + NOTE_LIST, SUB_FOLDER, CALL_RECORD_FOLDER + }; + + private ListEditState mState; + + // 后台查询处理器。 + private BackgroundQueryHandler mBackgroundQueryHandler; + // 便签列表适配器。 + private NotesListAdapter mNotesListAdapter; + // 便签列表视图。 + private ListView mNotesListView; + // 添加新便签的按钮。 + private Button mAddNewNote; + // 处理触摸事件的变量。 + private boolean mDispatch; + private int mOriginY; + private int mDispatchY; + // 标题栏。 + private TextView mTitleBar; + // 当前文件夹ID。 + private long mCurrentFolderId; + // 内容解析器。 + private ContentResolver mContentResolver; + // 模式回调。 + private ModeCallback mModeCallBack; + // 日志标签。 + private static final String TAG = "NotesListActivity"; + + // 便签列表视图滚动速率。 + public static final int NOTES_LISTVIEW_SCROLL_RATE = 30; + // 焦点便签数据项。 + private NoteItemData mFocusNoteDataItem; + // 普通选择和根文件夹选择的SQL语句。 + private static final String NORMAL_SELECTION = NoteColumns.PARENT_ID + "=?"; + private static final String ROOT_FOLDER_SELECTION = "(" + NoteColumns.TYPE + "<>" + + Notes.TYPE_SYSTEM + " AND " + NoteColumns.PARENT_ID + "=?)" + " OR (" + + NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER + " AND " + + NoteColumns.NOTES_COUNT + ">0)"; + + // 请求代码常量。 + private final static int REQUEST_CODE_OPEN_NODE = 102; + private final static int REQUEST_CODE_NEW_NODE = 103; + + // onCreate 方法,初始化 Activity。 + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.note_list); + initResources(); + + /** + * 当用户首次使用此应用程序时插入介绍。 + */ + setAppInfoFromRawRes(); + } + + // onActivityResult 方法,处理 Activity 结果。 + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (resultCode == RESULT_OK + && (requestCode == REQUEST_CODE_OPEN_NODE || requestCode == REQUEST_CODE_NEW_NODE)) { + mNotesListAdapter.changeCursor(null); + } else { + super.onActivityResult(requestCode, resultCode, data); + } + } + + // 从 raw 资源文件中设置应用程序信息。 + private void setAppInfoFromRawRes() { + SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); + if (!sp.getBoolean(PREFERENCE_ADD_INTRODUCTION, false)) { + StringBuilder sb = new StringBuilder(); + InputStream in = null; + try { + in = getResources().openRawResource(R.raw.introduction); + if (in != null) { + InputStreamReader isr = new InputStreamReader(in); + BufferedReader br = new BufferedReader(isr); + char [] buf = new char[1024]; + int len = 0; + while ((len = br.read(buf)) > 0) { + sb.append(buf, 0, len); + } + } else { + Log.e(TAG, "Read introduction file error"); + return; + } + } catch (IOException e) { + e.printStackTrace(); + return; + } finally { + if(in != null) { + try { + in.close(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + WorkingNote note = WorkingNote.createEmptyNote(this, Notes.ID_ROOT_FOLDER, + AppWidgetManager.INVALID_APPWIDGET_ID, Notes.TYPE_WIDGET_INVALIDE, + ResourceParser.RED); + note.setWorkingText(sb.toString()); + if (note.saveNote()) { + sp.edit().putBoolean(PREFERENCE_ADD_INTRODUCTION, true).commit(); + } else { + Log.e(TAG, "Save introduction note error"); + return; + } + } + } +} +/** + * 当 Activity 开始时调用此方法。 + */ +@Override +protected void onStart() { + super.onStart(); // 调用父类的 onStart 方法。 + startAsyncNotesListQuery(); // 开始异步查询便签列表。 +} + +/** + * 初始化资源的方法。 + */ +private void initResources() { + mContentResolver = this.getContentResolver(); // 获取内容解析器。 + mBackgroundQueryHandler = new BackgroundQueryHandler(this.getContentResolver()); // 创建后台查询处理器。 + mCurrentFolderId = Notes.ID_ROOT_FOLDER; // 设置当前文件夹ID为根文件夹。 + mNotesListView = (ListView) findViewById(R.id.notes_list); // 获取便签列表视图。 + mNotesListView.addFooterView(LayoutInflater.from(this).inflate(R.layout.note_list_footer, null), null, false); // 添加列表底部视图。 + mNotesListView.setOnItemClickListener(new OnListItemClickListener()); // 设置列表项点击事件监听器。 + mNotesListView.setOnItemLongClickListener(this); // 设置列表项长按事件监听器。 + mNotesListAdapter = new NotesListAdapter(this); // 创建便签列表适配器。 + mNotesListView.setAdapter(mNotesListAdapter); // 设置列表适配器。 + mAddNewNote = (Button) findViewById(R.id.btn_new_note); // 获取添加新便签的按钮。 + mAddNewNote.setOnClickListener(this); // 设置按钮点击事件监听器。 + mAddNewNote.setOnTouchListener(new NewNoteOnTouchListener()); // 设置按钮触摸事件监听器。 + mDispatch = false; // 初始化 dispatch 变量。 + mDispatchY = 0; // 初始化 dispatchY 变量。 + mOriginY = 0; // 初始化 originY 变量。 + mTitleBar = (TextView) findViewById(R.id.tv_title_bar); // 获取标题栏。 + mState = ListEditState.NOTE_LIST; // 初始化列表编辑状态。 + mModeCallBack = new ModeCallback(); // 创建模式回调对象。 +} + +/** + * ModeCallback 内部类,实现 ListView 多选模式监听器和菜单项点击事件。 + */ +private class ModeCallback implements ListView.MultiChoiceModeListener, OnMenuItemClickListener { + private DropdownMenu mDropDownMenu; // 下拉菜单。 + private ActionMode mActionMode; // 动作模式。 + private MenuItem mMoveMenu; // 移动菜单项。 + + /** + * 创建动作模式时调用。 + * @param mode 动作模式。 + * @param menu 菜单。 + * @return 是否创建成功。 + */ + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + // 省略部分代码... + } + + /** + * 更新菜单项。 + */ + private void updateMenu() { + // 省略部分代码... + } + + /** + * 准备动作模式时调用。 + * @param mode 动作模式。 + * @param menu 菜单。 + * @return 是否准备成功。 + */ + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + // TODO Auto-generated method stub + return false; + } + + /** + * 动作模式中的菜单项被点击时调用。 + * @param mode 动作模式。 + * @param item 菜单项。 + * @return 是否处理成功。 + */ + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + // TODO Auto-generated method stub + return false; + } + + /** + * 动作模式销毁时调用。 + * @param mode 动作模式。 + */ + public void onDestroyActionMode(ActionMode mode) { + // 省略部分代码... + } + + /** + * 结束动作模式。 + */ + public void finishActionMode() { + mActionMode.finish(); + } + + /** + * 列表项选中状态改变时调用。 + * @param mode 动作模式。 + * @param position 位置。 + * @param id ID。 + * @param checked 是否选中。 + */ + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { + // 省略部分代码... + } + + /** + * 菜单项点击事件。 + * @param item 菜单项。 + * @return 是否处理成功。 + */ + public boolean onMenuItemClick(MenuItem item) { + // 省略部分代码... + } +} + +/** + * NewNoteOnTouchListener 内部类,处理添加新便签按钮的触摸事件。 + */ +private class NewNoteOnTouchListener implements OnTouchListener { + /** + * 触摸事件处理。 + * @param v 视图。 + * @param event 触摸事件。 + * @return 是否处理成功。 + */ + public boolean onTouch(View v, MotionEvent event) { + // 省略部分代码... + } +} + +/** + * 开始异步查询便签列表。 + */ +private void startAsyncNotesListQuery() { + // 省略部分代码... +} + +/** + * BackgroundQueryHandler 内部类,继承自 AsyncQueryHandler,处理后台查询。 + */ +private final class BackgroundQueryHandler extends AsyncQueryHandler { + /** + * 构造函数。 + * @param contentResolver 内容解析器。 + */ + public BackgroundQueryHandler(ContentResolver contentResolver) { + super(contentResolver); + } + + /** + * 查询完成时调用。 + * @param token 令牌。 + * @param cookie 饼干。 + * @param cursor 游标。 + */ + @Override + protected void onQueryComplete(int token, Object cookie, Cursor cursor) { + // 省略部分代码... + } +} \ No newline at end of file