diff --git a/src/ui(s)/AlarmAlertActivity.java b/src/ui(s)/AlarmAlertActivity.java new file mode 100644 index 0000000..dc4b618 --- /dev/null +++ b/src/ui(s)/AlarmAlertActivity.java @@ -0,0 +1,174 @@ +/* + * 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.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,用于处理闹钟提醒的UI和逻辑。 +public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { + // 用于存储笔记ID和笔记预览文本 + private long mNoteId; + private String mSnippet; + // 定义预览文本的最大长度 + private static final int SNIPPET_PREW_MAX_LEN = 60; + // 用于播放闹钟声音的MediaPlayer对象 + 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); + } + + // 获取启动该Activity的Intent对象 + Intent intent = getIntent(); + + try { + // 从Intent中解析笔记ID + mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); + // 根据笔记ID获取笔记预览文本 + mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); + // 如果预览文本超过最大长度,则截断并添加省略号 + mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, + SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) + : mSnippet; + } catch (IllegalArgumentException e) { + e.printStackTrace(); + return; + } + + // 初始化MediaPlayer对象 + 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 + Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); + + // 获取受影响的静音模式流类型 + int silentModeStreams = Settings.System.getInt(getContentResolver(), + Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); + + // 设置MediaPlayer的音频流类型 + if ((silentModeStreams & (1 << AudioManager.STREAM_ALARM)) != 0) { + mPlayer.setAudioStreamType(silentModeStreams); + } else { + mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); + } + try { + // 设置MediaPlayer的数据源为闹钟铃声,并准备播放 + mPlayer.setDataSource(this, url); + mPlayer.prepare(); + mPlayer.setLooping(true); // 设置循环播放 + mPlayer.start(); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (SecurityException e) { + e.printStackTrace(); + } catch (IllegalStateException e) { + e.printStackTrace(); + } catch (IOException e) { + 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); // 显示对话框并设置对话框消失监听器 + } + + // DialogInterface.OnClickListener接口的实现,处理对话框按钮点击事件 + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_NEGATIVE: // 如果点击编辑按钮 + Intent intent = new Intent(this, NoteEditActivity.class); // 创建Intent,跳转到编辑笔记界面 + intent.setAction(Intent.ACTION_VIEW); + intent.putExtra(Intent.EXTRA_UID, mNoteId); // 传递笔记ID + startActivity(intent); // 启动编辑笔记界面 + break; + default: + break; + } + } + + // DialogInterface.OnDismissListener接口的实现,处理对话框消失事件 + public void onDismiss(DialogInterface dialog) { + stopAlarmSound(); // 停止闹钟声音 + finish(); // 结束当前Activity + } + + // 停止闹钟声音 + private void stopAlarmSound() { + if (mPlayer != null) { + mPlayer.stop(); // 停止播放 + mPlayer.release(); // 释放资源 + mPlayer = null; // 置空MediaPlayer对象 + } + } +} \ No newline at end of file diff --git a/src/ui(s)/AlarmInitReceiver.java b/src/ui(s)/AlarmInitReceiver.java new file mode 100644 index 0000000..6f99ea7 --- /dev/null +++ b/src/ui(s)/AlarmInitReceiver.java @@ -0,0 +1,81 @@ +/* + * 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; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; + + +// AlarmInitReceiver类继承自BroadcastReceiver,用于初始化闹钟提醒。 +public class AlarmInitReceiver extends BroadcastReceiver { + + // 定义查询数据库时需要返回的列 + private static final String [] PROJECTION = new String [] { + NoteColumns.ID, // 笔记ID + NoteColumns.ALERTED_DATE // 笔记的已提醒日期 + }; + + // 定义PROJECTION数组中各列的索引 + private static final int COLUMN_ID = 0; // 笔记ID索引 + private static final int COLUMN_ALERTED_DATE = 1; // 已提醒日期索引 + + // onReceive方法在接收到广播时被调用,这里用于设置闹钟提醒。 + @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,用于触发闹钟提醒 + Intent sender = new Intent(context, AlarmReceiver.class); + // 为Intent设置数据,标识具体的笔记 + sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); + // 创建PendingIntent,用于AlarmManager设置闹钟 + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); + // 获取AlarmManager实例 + AlarmManager alermManager = (AlarmManager) context + .getSystemService(Context.ALARM_SERVICE); + // 设置闹钟,使用RTC_WAKEUP模式在指定时间唤醒设备 + alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); + } while (c.moveToNext()); + } + // 关闭游标 + c.close(); + } + } +} \ No newline at end of file diff --git a/src/ui(s)/AlarmReceiver.java b/src/ui(s)/AlarmReceiver.java new file mode 100644 index 0000000..6356bd5 --- /dev/null +++ b/src/ui(s)/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方法在接收到广播时被调用,这里用于启动闹钟提醒的Activity。 + @Override + public void onReceive(Context context, Intent intent) { + // 设置intent的目标类为AlarmAlertActivity,即闹钟提醒的Activity。 + intent.setClass(context, AlarmAlertActivity.class); + // 为intent添加标志,表示需要创建一个新的任务栈。 + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // 使用context启动AlarmAlertActivity,展示闹钟提醒界面。 + context.startActivity(intent); + } +} \ No newline at end of file diff --git a/src/ui(s)/DateTimePicker.java b/src/ui(s)/DateTimePicker.java new file mode 100644 index 0000000..3744de1 --- /dev/null +++ b/src/ui(s)/DateTimePicker.java @@ -0,0 +1,511 @@ +/** + * 文件名称:DateTimePicker.java + * 版权声明:版权所有 (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 java.text.DateFormatSymbols; // 用于日期时间格式化的符号 +import java.util.Calendar; // 用于日期时间操作的日历类 + +import net.micode.notes.R; // 导入资源文件R,用于访问资源 + +import android.content.Context; // Android上下文环境 +import android.text.format.DateFormat; // 用于日期时间格式化 +import android.view.View; // 用于视图操作 +import android.widget.FrameLayout; // 布局容器FrameLayout +import android.widget.NumberPicker; // 自定义视图,用于选择数字 + +/** + * DateTimePicker 类是一个自定义视图组件,用于在Android应用中选择日期和时间。 + */ +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; + // NumberPicker组件的值范围常量 + private static final int DATE_SPINNER_MIN_VAL = 0; + private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1; + 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; + + // 控件声明 + private final NumberPicker mDateSpinner; // 用于选择日期的NumberPicker + private final NumberPicker mHourSpinner; // 用于选择小时的NumberPicker + private final NumberPicker mMinuteSpinner; // 用于选择分钟的NumberPicker + private final NumberPicker mAmPmSpinner; // 用于选择上午/下午的NumberPicker + private Calendar mDate; // 用于管理日期和时间的Calendar实例 + + // 日期显示值数组 + 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; + + // NumberPicker值变化监听器 + 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) { + // 当小时值变化时,更新日历并通知监听器 + boolean isDateChanged = false; + Calendar cal = Calendar.getInstance(); + if (!mIs24HourView) { + // 处理12小时制逻辑 + if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) { + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, 1); + isDateChanged = true; + } else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { + 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 { + // 处理24小时制逻辑 + 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); + + mDateSpinner = (NumberPicker) findViewById(R.id.date); + mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL); + mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL); + mDateSpinner.setOnValueChangedListener(mOnDateChangedListener); + + mHourSpinner = (NumberPicker) findViewById + + String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); + mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm); + mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL); + mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL); + mAmPmSpinner.setDisplayedValues(stringsForAmPm); + mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener); + + // 初始化控件状态 + updateDateControl(); + updateHourControl(); + updateAmPmControl(); + + set24HourView(is24HourView); + + // 设置当前时间 + setCurrentDate(date); + + setEnabled(isEnabled()); + + // 设置内容描述 + mInitialising = false; +} + +@Override +public void setEnabled(boolean enabled) { + // 设置控件是否可用 + if (mIsEnabled == enabled) { + return; + } + super.setEnabled(enabled); + mDateSpinner.setEnabled(enabled); + mMinuteSpinner.setEnabled(enabled); + mHourSpinner.setEnabled(enabled); + mAmPmSpinner.setEnabled(enabled); + mIsEnabled = enabled; +} + +@Override +public boolean isEnabled() { + // 获取控件是否可用 + return mIsEnabled; +} + +/** + * 获取当前日期的毫秒值 + * + * @return 当前日期的毫秒值 + */ +public long getCurrentDateInTimeMillis() { + return mDate.getTimeInMillis(); +} + +/** + * 设置当前日期 + * + * @param date 当前日期的毫秒值 + */ +public void setCurrentDate(long date) { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(date); + setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), + cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE)); +} + +/** + * 设置当前日期 + * + * @param year 当前年 + * @param month 当前月 + * @param dayOfMonth 当前日 + * @param hourOfDay 当前小时 + * @param minute 当前分钟 + */ +public void setCurrentDate(int year, int month, + int dayOfMonth, int hourOfDay, int minute) { + setCurrentYear(year); + setCurrentMonth(month); + setCurrentDay(dayOfMonth); + setCurrentHour(hourOfDay); + setCurrentMinute(minute); +} + +/** + * 获取当前年份 + * + * @return 当前年份 + */ +public int getCurrentYear() { + return mDate.get(Calendar.YEAR); +} + +/** + * 设置当前年份 + * + * @param year 当前年份 + */ +public void setCurrentYear(int year) { + if (!mInitialising && year == getCurrentYear()) { + return; + } + mDate.set(Calendar.YEAR, year); + updateDateControl(); + onDateTimeChanged(); +} + +/** + * 获取当前月份 + * + * @return 当前月份 + */ +public int getCurrentMonth() { + return mDate.get(Calendar.MONTH); +} + +/** + * 设置当前月份 + * + * @param month 月份 + */ +public void setCurrentMonth(int month) { + if (!mInitialising && month == getCurrentMonth()) { + return; + } + mDate.set(Calendar.MONTH, month); + updateDateControl(); + onDateTimeChanged(); +} + +/** + * 获取当前月份中的日期 + * + * @return 月份中的日期 + */ +public int getCurrentDay() { + return mDate.get(Calendar.DAY_OF_MONTH); +} + +/** + * 设置当前月份中的日期 + * + * @param dayOfMonth 月份中的日期 + */ +public void setCurrentDay(int dayOfMonth) { + if (!mInitialising && dayOfMonth == getCurrentDay()) { + return; + } + mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); + updateDateControl(); + onDateTimeChanged(); +} + +/** + * 获取24小时制的当前小时 + * + * @return 24小时制的当前小时 + */ +public int getCurrentHourOfDay() { + return mDate.get(Calendar.HOUR_OF_DAY); +} + +private int getCurrentHour() { + if (mIs24HourView){ + return getCurrentHourOfDay(); + } else { + int hour = getCurrentHourOfDay(); + if (hour > HOURS_IN_HALF_DAY) { + return hour - HOURS_IN_HALF_DAY; + } else { + return hour == 0 ? HOURS_IN_HALF_DAY : hour; + } + } +} + +/** + * 设置24小时制的当前小时 + * + * @param hourOfDay 24小时制的小时 + */ +public void setCurrentHour(int hourOfDay) { + if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { + return; + } + mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); + if (!mIs24HourView) { + if (hourOfDay >= HOURS_IN_HALF_DAY) { + mIsAm = false; + if (hourOfDay > HOURS_IN_HALF_DAY) { + hourOfDay -= HOURS_IN_HALF_DAY; + } + } else { + mIsAm = true; + if (hourOfDay == 0) { + hourOfDay = HOURS_IN_HALF_DAY; + } + } + updateAmPmControl(); + } + mHourSpinner.setValue(hourOfDay); + onDateTimeChanged(); +} + +/** + * 获取当前分钟 + * + * @return 当前分钟 + */ +public int getCurrentMinute() { + return mDate.get(Calendar.MINUTE); +} + +/** + * 设置当前分钟 + */ +public void setCurrentMinute(int minute) { + if (!mInitialising && minute == getCurrentMinute()) { + return; + } + mMinuteSpinner.setValue(minute); + mDate.set(Calendar.MINUTE, minute); + onDateTimeChanged(); +} + +/** + * 判断是否是24小时视图 + * + * @return 如果是24小时视图返回true,否则返回false。 + */ +public boolean is24HourView () { + return mIs24HourView; +} + +/** + * 设置是否是24小时视图或AM/PM模式 + * + * @param is24HourView 如果是24小时视图传入true,否则传入false。 + */ +public void set24HourView(boolean is24HourView) { + if (mIs24HourView == is24HourView) { + return; + } + mIs24HourView = is24HourView; + mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE); + int hour = getCurrentHourOfDay(); + updateHourControl(); + setCurrentHour(hour); + updateAmPmControl(); +} + +/** + * 更新日期控制 + */ +private void updateDateControl() { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(mDate.getTimeInMillis()); + cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1); + mDateSpinner.setDisplayedValues(null); + for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) { + cal.add(Calendar.DAY_OF_YEAR, 1); + mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal); + } + mDateSpinner.setDisplayedValues(mDateDisplayValues); + mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); + mDateSpinner.invalidate(); +} + +/** + * 更新上午/下午控制 + */ +private void updateAmPmControl() { + if (mIs24HourView) { + mAmPmSpinner.setVisibility(View.GONE); // 在24小时视图中隐藏AM/PM选择器 + } else { + int index = mIsAm ? Calendar.AM : Calendar.PM; // 根据当前时间是上午还是下午设置索引 + mAmPmSpinner.setValue(index); // 设置AM/PM选择器的值 + mAmPmSpinner.setVisibility(View.VISIBLE); // 显示AM/PM选择器 + } +} + +/** + * 更新小时控制 + */ +private void updateHourControl() { + if (mIs24HourView) { + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); // 设置24小时视图的小时最小值 + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW); // 设置24小时视图的小时最大值 + } else { + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW); // 设置12小时视图的小时最小值 + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW); // 设置12小时视图的小时最大值 + } +} + +/** + * 设置日期时间改变的监听器 + * +/** + * 当日期时间改变时调用此方法 + */ +private void onDateTimeChanged() { + if (mOnDateTimeChangedListener != null) { + mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), + getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute()); + } +} \ No newline at end of file diff --git a/src/ui(s)/DateTimePickerDialog.java b/src/ui(s)/DateTimePickerDialog.java new file mode 100644 index 0000000..ba22a76 --- /dev/null +++ b/src/ui(s)/DateTimePickerDialog.java @@ -0,0 +1,122 @@ +/** + * 文件名称:DateTimePickerDialog.java + * 版权声明:版权所有 (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 java.util.Calendar; // 导入日历类 + +import net.micode.notes.R; // 导入资源文件R,用于访问资源 +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; // 导入点击事件监听器接口 +import android.text.format.DateFormat; // 导入日期格式化类 +import android.text.format.DateUtils; // 导入日期工具类 + +/** + * DateTimePickerDialog 类是一个继承自AlertDialog的对话框,用于选择日期和时间。 + */ +public class DateTimePickerDialog extends AlertDialog implements OnClickListener { + + // 成员变量 + private Calendar mDate = Calendar.getInstance(); // 当前日期时间的Calendar实例 + private boolean mIs24HourView; // 是否使用24小时制 + private OnDateTimeSetListener mOnDateTimeSetListener; // 日期时间设置监听器 + private DateTimePicker mDateTimePicker; // DateTimePicker组件 + + /** + * 日期时间设置监听器接口 + */ + public interface OnDateTimeSetListener { + void OnDateTimeSet(AlertDialog dialog, long date); // 当日期时间设置时的回调方法 + } + + /** + * DateTimePickerDialog构造函数 + * @param context 上下文环境 + * @param date 初始日期时间的毫秒值 + */ + public DateTimePickerDialog(Context context, long date) { + super(context); // 调用AlertDialog的构造函数 + mDateTimePicker = new DateTimePicker(context); // 创建DateTimePicker实例 + 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); // 设置秒为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); + // 设置24小时制视图 + set24HourView(DateFormat.is24HourFormat(this.getContext())); + updateTitle(mDate.getTimeInMillis()); // 更新对话框标题 + } + + /** + * 设置是否使用24小时制视图 + * @param is24HourView 是否使用24小时制 + */ + public void set24HourView(boolean is24HourView) { + mIs24HourView = is24HourView; + // 根据是否使用24小时制更新DateTimePicker + mDateTimePicker.set24HourView(is24HourView); + } + + /** + * 设置日期时间设置监听器 + * @param callBack 监听器实例 + */ + public void setOnDateTimeSetListener(OnDateTimeSetListener callBack) { + mOnDateTimeSetListener = callBack; // 设置监听器 + } + + /** + * 更新对话框标题 + * @param date 毫秒值表示的日期时间 + */ + private void updateTitle(long date) { + int flag = DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME; + // 根据是否使用24小时制设置格式化标志 + flag |= mIs24HourView ? DateUtils.FORMAT_24HOUR : DateUtils.FORMAT_12HOUR; + // 设置对话框标题为日期时间字符串 + setTitle(DateUtils.formatDateTime(this.getContext(), date, flag)); + } + + /** + * 点击事件处理 + * @param arg0 对话框接口 + * @param arg1 按钮标识 + */ + 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/ui(s)/DropdownMenu.java b/src/ui(s)/DropdownMenu.java new file mode 100644 index 0000000..46f6399 --- /dev/null +++ b/src/ui(s)/DropdownMenu.java @@ -0,0 +1,82 @@ +/** + * 文件名称:DropdownMenu.java + * 版权声明:版权所有 (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.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; // 导入OnMenuItemClickListener接口,用于设置菜单项点击事件 + +import net.micode.notes.R; // 导入R类,用于访问资源文件 + +/** + * DropdownMenu 类用于创建一个下拉菜单(Dropdown Menu)。 + */ +public class DropdownMenu { + // 成员变量 + private Button mButton; // 用于触发下拉菜单的按钮 + private PopupMenu mPopupMenu; // 弹出菜单对象 + private Menu mMenu; // 菜单对象 + + /** + * DropdownMenu 构造函数。 + * @param context 上下文环境,用于获取系统资源和服务。 + * @param button 用于触发下拉菜单的按钮。 + * @param menuId 菜单资源ID,用于从XML资源文件中加载菜单项。 + */ + 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); // 从XML资源文件中加载菜单项 + mButton.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + mPopupMenu.show(); // 设置按钮点击事件,显示PopupMenu + } + }); + } + + /** + * 设置下拉菜单项点击事件监听器。 + * @param listener 点击事件监听器。 + */ + public void setOnDropdownMenuItemClickListener(OnMenuItemClickListener listener) { + if (mPopupMenu != null) { + mPopupMenu.setOnMenuItemClickListener(listener); // 设置PopupMenu的菜单项点击事件 + } + } + + /** + * 查找具有指定ID的菜单项。 + * @param id 菜单项ID。 + * @return 返回找到的MenuItem对象,如果没有找到则返回null。 + */ + public MenuItem findItem(int id) { + return mMenu.findItem(id); // 在Menu中查找指定ID的菜单项 + } + + /** + * 设置触发下拉菜单的按钮文本。 + * @param title 按钮文本。 + */ + public void setTitle(CharSequence title) { + mButton.setText(title); // 设置按钮文本 + } +} \ No newline at end of file diff --git a/src/ui(s)/FoldersListAdapter.java b/src/ui(s)/FoldersListAdapter.java new file mode 100644 index 0000000..800d1cc --- /dev/null +++ b/src/ui(s)/FoldersListAdapter.java @@ -0,0 +1,118 @@ +/** + * 文件名称:FoldersListAdapter.java + * 版权声明:版权所有 (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.content.Context; // 导入Context类,用于获取系统资源和服务 +import android.database.Cursor; // 导入Cursor类,用于访问数据库查询结果 +import android.view.View; // 导入View类,用于操作视图 +import android.view.ViewGroup; // 导入ViewGroup类,用于操作视图组 +import android.widget.CursorAdapter; // 导入CursorAdapter类,用于适配器 +import android.widget.LinearLayout; // 导入LinearLayout类,用于线性布局 +import android.widget.TextView; // 导入TextView类,用于文本视图 + +import net.micode.notes.R; // 导入R类,用于访问资源文件 +import net.micode.notes.data.Notes; // 导入Notes类,用于访问笔记数据 +import net.micode.notes.data.Notes.NoteColumns; // 导入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 构造函数。 + * @param context 上下文环境,用于获取系统资源和服务。 + * @param c 数据库查询结果的Cursor对象。 + */ + public FoldersListAdapter(Context context, Cursor c) { + super(context, c); // 调用父类的构造函数 + } + + /** + * 创建新的视图。 + * @param context 上下文环境。 + * @param cursor Cursor对象。 + * @param parent 父视图组。 + * @return 返回新的视图对象。 + */ + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return new FolderListItem(context); // 创建FolderListItem视图 + } + + /** + * 绑定视图和数据。 + * @param view 视图对象。 + * @param context 上下文环境。 + * @param cursor Cursor对象。 + */ + @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); // 绑定数据到视图 + } + } + + /** + * 获取文件夹名称。 + * @param context 上下文环境。 + * @param position 位置。 + * @return 返回文件夹名称。 + */ + public String getFolderName(Context context, int position) { + Cursor cursor = (Cursor) getItem(position); // 获取对应位置的Cursor对象 + 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 构造函数。 + * @param context 上下文环境。 + */ + public FolderListItem(Context context) { + super(context); // 调用父类的构造函数 + inflate(context, R.layout.folder_list_item, this); // 从XML布局文件中加载视图 + mName = (TextView) findViewById(R.id.tv_folder_name); // 获取TextView对象 + } + + /** + * 绑定文件夹名称数据到视图。 + * @param name 文件夹名称。 + */ + public void bind(String name) { + mName.setText(name); // 设置TextView的文本 + } + } + +} \ No newline at end of file