diff --git a/src/ui/AlarmAlertActivity.java b/src/ui/AlarmAlertActivity.java new file mode 100644 index 0000000..6662469 --- /dev/null +++ b/src/ui/AlarmAlertActivity.java @@ -0,0 +1,182 @@ +/* + * 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. + */ + +// 220340038 毛彦翔 2024/4/14 + +package net.micode.notes.ui; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.DialogInterface.OnDismissListener; +import android.content.Intent; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.PowerManager; +import android.provider.Settings; +import android.view.Window; +import android.view.WindowManager; + +import net.micode.notes.R; +import net.micode.notes.data.Notes; +import net.micode.notes.tool.DataUtils; + +import java.io.IOException; + + +public class AlarmAlertActivity extends Activity implements OnClickListener, OnDismissListener { + private long mNoteId; //便签在库中的检索ID + private String mSnippet; //便签提醒时显示的文本 + private static final int SNIPPET_PREW_MAX_LEN = 60; + MediaPlayer mPlayer; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState);//通过savedInstanceState获取状态信息 + requestWindowFeature(Window.FEATURE_NO_TITLE); + //通过requestWindowFeature指定活动窗口的特性(无标题) + + final Window win = getWindow(); + /* + *获取当前活动的window对象,并赋值给名为win的final变量 + *由于final变量 在赋值后不能再改变, + *可以确保后续引用该变量时不会被意外修改 提高代码的可维护性 + */ + + win.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + //使window对象win可以在锁屏后仍然显示 即可以覆盖在锁屏界面之上 + + if (!isScreenOn()) { //检查屏幕是否开启 + win.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + //保持屏幕唤醒 防止自动息屏 + | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON + //唤醒屏幕并显示窗口 不明白与66行保持屏幕唤醒的区别 + | WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON + //允许屏幕在保持唤醒的情况下手动锁定屏幕 猜测与66行FLAG_KEEP_SCREEN_ON有关 + | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); + //控制边距 确保布局和内容正常显示 + } + + Intent intent = getIntent(); + + try { + mNoteId = Long.valueOf(intent.getData().getPathSegments().get(1)); + mSnippet = DataUtils.getSnippetById(this.getContentResolver(), mNoteId); + //通过检索ID从库中获取便签内容 + mSnippet = mSnippet.length() > SNIPPET_PREW_MAX_LEN ? mSnippet.substring(0, + SNIPPET_PREW_MAX_LEN) + getResources().getString(R.string.notelist_string_info) + : mSnippet; + } catch (IllegalArgumentException e) { + e.printStackTrace(); + return; + }//捕获异常信息 打印异常堆栈并直接返回 避免因错误信息导致程序崩溃 + + mPlayer = new MediaPlayer(); //创建一个MediaPlayer对象用于播放音频 + if (DataUtils.visibleInNoteDatabase/*便签是否可见*/(getContentResolver(), mNoteId, Notes.TYPE_NOTE)) { + showActionDialog(); //笔记存在可见则显示操作对话框 + playAlarmSound(); //同时播放警示音 + } else { + finish(); //笔记不存在或不可见则调用finish函数结束当前活动 关闭页面 + } + } + + private boolean isScreenOn() { + PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + return pm.isScreenOn(); + } //一个通过访问系统电源管理服务检查屏幕是否开启 并返回布尔值的私有方法 + + private void playAlarmSound() { + Uri url = RingtoneManager.getActualDefaultRingtoneUri(this, RingtoneManager.TYPE_ALARM); + //获取当前默认铃声的uri并存储在Uri类型的变量url中 Uri是什么类型?写完再查 + int silentModeStreams = Settings.System.getInt(getContentResolver(), + Settings.System.MODE_RINGER_STREAMS_AFFECTED, 0); + //从系统设置中获取当前静音模式下受铃声模式影响的流的信息,并存储在一个整数变量中 + //作用是什么?没想明白 + if ((silentModeStreams/*处于静音模式*/ & (1 << AudioManager.STREAM_ALARM)) != 0) { + mPlayer.setAudioStreamType(silentModeStreams); + /*这下看懂了 + *使用位运算判断静音模式下是否受到铃声模式影响 + *与运算结果不为0则表示静音模式下受到铃声模式影响 + *受铃声模式影响则直接调用setAudioStreamType(silentModeStreams)设置音频流类型 + */ + } else { + mPlayer.setAudioStreamType(AudioManager.STREAM_ALARM); + } //否则使用AudioManager.STREAM_ALARM作为银趴留类型 总之 确保播放的铃声会受系统静音模式控制 + try { + mPlayer.setDataSource(this, url); + mPlayer.prepare(); + mPlayer.setLooping(true); + mPlayer.start(); + //设置MediaPlayer的数据源 准备播放铃声并循环播放 不一一注释了 + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalStateException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + //用于捕获可能抛出的异常 发生以上异常则打印异常信息并继续执行 + //看不懂上面的注释 查找后发现是IDE自动生成的catch块 + } + + private void showActionDialog() { //显示操作对话框的私有方法 + AlertDialog.Builder dialog = new AlertDialog.Builder(this); //构建对话框 在当前活动窗口中显示 + dialog.setTitle(R.string.app_name); //设置对话框标题 文本来自app_name 这是什么 + dialog.setMessage(mSnippet); //对话框的内容 mSnippet在第46行被定义 这是什么来着 + dialog.setPositiveButton(R.string.notealert_ok, this); //某个按钮?没懂 positive按钮是什么 + if (isScreenOn()) { + dialog.setNegativeButton(R.string.notealert_enter, this); //negative按钮又是什么 晕了 这俩玩意的功能写在哪了 + } + dialog.show().setOnDismissListener(this); //头好痛 要长脑子了 + } +DialogInterface.OnClickListener + public void onClick(DialogInterface dialog, int which) { //对话框被点击时调用 DialogInterface.OnClickListener接口的实现 + switch (which) { // 用Switch分值判断哪个按钮被点击了 为什么不用if-else呢 + case DialogInterface.BUTTON_NEGATIVE: //negative按钮被按了! + Intent intent = new Intent(this, NoteEditActivity.class); //新建intent对象 调用NoteEditActivity类 + intent.setAction(Intent.ACTION_VIEW); //把当前活动状态设置为action_view + intent.putExtra(Intent.EXTRA_UID, mNoteId); //加了个什么东西 键为EXTRA_UID,值为mNoteID 检索ID?是把这对键值赋给新建的便签吗 + startActivity(intent); //启动。 + break; + default: //按的不是negative按钮则停止活动 为什么startActivity按的是negative按钮不是positive按钮 + break; + } + } + + public void onDismiss(DialogInterface dialog) { + stopAlarmSound(); + finish(); + } //最简单的一集 对话框消失则调用这两个函数 停止播放警示音并关闭当前活动 + + private void stopAlarmSound() { + if (mPlayer != null) { //就爱你差对象是否为空 是为了防止空指针吗 + mPlayer.stop(); + mPlayer.release(); //释放MediaPlayer使用的系统资源 避免资源浪费 + mPlayer = null; //将mPlayer设置为空 回收内存空间防止泄露 + } + } +} diff --git a/src/ui/AlarmInitReceiver.java b/src/ui/AlarmInitReceiver.java new file mode 100644 index 0000000..533f0f3 --- /dev/null +++ b/src/ui/AlarmInitReceiver.java @@ -0,0 +1,72 @@ +/* + * 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. + */ + +//220340038 毛彦翔 2024/4/14 22:57 + +package net.micode.notes.ui; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; + + +public class AlarmInitReceiver extends BroadcastReceiver { //继承了BroadcastReceiver类 这个类在哪呢? + + private static final String [] PROJECTION = new String [] { //名为PROJECTION的私有 final类型的静态字符串数组 好安全 + NoteColumns.ID, //某个ID 是AlarmActivity中的同款检索ID吗 + NoteColumns.ALERTED_DATE //似乎是闹钟提醒的日期。 + }; + + private static final int COLUMN_ID = 0; + private static final int COLUMN_ALERTED_DATE = 1; //变量初始化 + + @Override + public void onReceive(Context context, Intent intent) { //名为onReceive的公有类 + long currentDate = System.currentTimeMillis(); //currentDate 初始化为 当前系统时间 + Cursor c = context.getContentResolver().query(Notes.CONTENT_NOTE_URI/*指定了查询的数据源*/, + PROJECTION, //33行中定义的数组 返回ID和ALERTED_DATE + NoteColumns.ALERTED_DATE + ">? AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE, + //筛选条件 找到ALERTED_DATE大于当前时间并且TYPE等于TYPE_NOTE的便签。type是什么啊。 + new String[] { String.valueOf(currentDate) }, //强制转换?把long类型的currentDate转换成String + null); //干嘛用的null + + if (c != null) { //c不为空 + if (c.moveToFirst()) { + /*将光标移动到结果集中的第一行。如果结果集为空,则此方法将返回 false + *如果结果集非空,则将光标移到第一行,并返回 true + *来自百度 + */ + do { + long alertDate = c.getLong(COLUMN_ALERTED_DATE); + Intent sender = new Intent(context, AlarmReceiver.class); + sender.setData(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, c.getLong(COLUMN_ID))); + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, sender, 0); + AlarmManager alermManager = (AlarmManager) context + .getSystemService(Context.ALARM_SERVICE); + alermManager.set(AlarmManager.RTC_WAKEUP, alertDate, pendingIntent); + } while (c.moveToNext()); //遍历查询到的便签挨个设闹钟 + } + c.close(); //释放 + } + } +} //好困 睡觉 diff --git a/src/ui/AlarmReceiver.java b/src/ui/AlarmReceiver.java new file mode 100644 index 0000000..2c6c58a --- /dev/null +++ b/src/ui/AlarmReceiver.java @@ -0,0 +1,33 @@ +/* + * 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; + +public class AlarmReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + intent.setClass(context, AlarmAlertActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } +} +//似乎是负责启动AlarmAlertActivity的玩意 diff --git a/src/ui/DateTimePicker.java b/src/ui/DateTimePicker.java new file mode 100644 index 0000000..f247ce9 --- /dev/null +++ b/src/ui/DateTimePicker.java @@ -0,0 +1,487 @@ +/* + * 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. 220340038 毛彦翔 2024/4/15 18:10 + */ + +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; + +public class DateTimePicker extends FrameLayout { + + private static final boolean DEFAULT_ENABLE_STATE = true; //某个布尔变量 暂时不知道干啥用的 + + private static final int HOURS_IN_HALF_DAY = 12; //12小时制? + private static final int HOURS_IN_ALL_DAY = 24; //24小时制? + private static final int DAYS_IN_ALL_WEEK = 7; //众所周知一周有七天 + private static final int DATE_SPINNER_MIN_VAL = 0; //日期选择器(?)的最小值 初始化为0 + private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1; //日期选择器(?)的最大值 设定为一周的天数-1 为什么不直接初始化为6? + 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; //上午AM下午PM。嗯 + //好多静态最终变量 + //它们应该是用来设置闹钟的日期。0~6表示星期几。可以切换12和24小时制。如何区分这周一和下周一? + + private final NumberPicker mDateSpinner; //四个NumberPicker的成员变量。 + private final NumberPicker mHourSpinner; + private final NumberPicker mMinuteSpinner; + private final NumberPicker mAmPmSpinner; //显然是日期和时间选择器 + private Calendar mDate; //定义一个Calendar类成员 存储日期用的吗 + + private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; + + private boolean mIsAm; //是否上午 + + private boolean mIs24HourView; //是否24小时制 + + private boolean mIsEnabled = DEFAULT_ENABLE_STATE; //没看懂 表示状态的私有布尔变量 初始化为DEFAULT_ENABLE_STATE + + private boolean mInitialising; //判断是否初始化? + + private OnDateTimeChangedListener mOnDateTimeChangedListener; //OnDateTimeChangedListener类的私有成员变量 似乎是用来监测日期和时间变化 + + 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) { + 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(); //好长 总之就是根据是否是24小时值和AM/PM来调整小时的计算 + } + } 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; //超出一天24小时则日期+1并将DateChanged更新为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(); + } //分钟发生变化则调用updateDateControl()和updateAmPmControl()更新小时和日期 + }; //多态确实是个好东西 不管用户调整日期 小时 还是分钟 只需要调用同一个函数名 大大增加代码的可读性和可维护性 + + private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { //12小时制下 AM PM选择器的值发生变化时 该方法将被调用 + @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(); + } //在AM PM选择器的值发生变化时更新相关的日期和时间信息 + }; + + public interface OnDateTimeChangedListener { + void onDateTimeChanged(DateTimePicker view, int year, int month, + int dayOfMonth, int hourOfDay, int minute); + } //定义了一个接口 OnDateTimeChangedListener,其中包含一个方法 onDateTimeChanged,用于在日期时间发生变化时通知监听器 + + public DateTimePicker(Context context) { //重载构造函数 + this(context, System.currentTimeMillis()); //调用另一个重载构造函数,以当前系统时间来初始化DateTimePicker,即将当前系统时间作为日期时间的初始值 + } + + public DateTimePicker(Context context, long date) { + this(context, date, DateFormat.is24HourFormat(context)); //调用了另一个重载构造函数DateTimePicker(Context context, long date, boolean is24HourView),并传入了这两个参数以及一个额外的参数DateFormat.is24HourFormat(context)。它会以提供的日期时间值作为初始值来初始化DateTimePicker,并根据设备的时间设置来决定是否采用 24 小时制。 + } + + public DateTimePicker(Context context, long date, boolean is24HourView) { + super(context); //调用父类构造函数 来初始化DateTimePicker + mDate = Calendar.getInstance(); + mInitialising = true; + mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; + inflate(context, R.layout.datetime_picker, this); //使用inflate()方法加载了布局文件R.layout.datetime_picker,并将其作为该自定义控件的布局。 + + 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); //初始化了日期、小时、分钟和上午/下午选择器,并设置相应和监听器 + + 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() 方法,将控件更新到初始状态。 + + // update controls to initial state + updateDateControl(); + updateHourControl(); + updateAmPmControl(); + + set24HourView(is24HourView); + + // set to current time + setCurrentDate(date); + + setEnabled(isEnabled()); + + // set the content descriptions + mInitialising = false; + } + + @Override + public void setEnabled(boolean enabled) { + if (mIsEnabled == enabled) { + return; //避免重复设置相同状态 减少占用 + } + super.setEnabled(enabled); //调用父类的setEnabled()方法,以设置整个控件的可用状态。 + mDateSpinner.setEnabled(enabled); + mMinuteSpinner.setEnabled(enabled); + mHourSpinner.setEnabled(enabled); + mAmPmSpinner.setEnabled(enabled); + mIsEnabled = enabled; //分别设置日期、分钟、小时和上午/下午选择器的可用状态,以保持与整个控件状态一致 + } + + @Override + public boolean isEnabled() { + return mIsEnabled; + } //获取该自定义控件的当前可用状态。返回一个布尔值,表示控件是否可用 + + /** + * Get the current date in millis + * + * @return the current date in millis + */ + public long getCurrentDateInTimeMillis() { + return mDate.getTimeInMillis(); + } + + /** + * Set the current date + * + * @param date The current date in millis + */ + public void setCurrentDate(long date) { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(date); + setCurrentDate(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), + cal.get(Calendar.HOUR_OF_DAY), cal.get(Calendar.MINUTE)); + } + + /** + * Set the current date + * + * @param year The current year + * @param month The current month + * @param dayOfMonth The current dayOfMonth + * @param hourOfDay The current hourOfDay + * @param minute The current minute + */ + public void setCurrentDate(int year, int month, + int dayOfMonth, int hourOfDay, int minute) { + setCurrentYear(year); + setCurrentMonth(month); + setCurrentDay(dayOfMonth); + setCurrentHour(hourOfDay); + setCurrentMinute(minute); //调用其他方法来把当前时间设置为传入的时间。避免在实际使用时连续调用多个方法来设置。提高代码的可读性和可维护性 + } + + /** + * Get current year + * + * @return The current year + */ + public int getCurrentYear() { + return mDate.get(Calendar.YEAR); + } + + /** + * Set current year + * + * @param year The current year + */ + public void setCurrentYear(int year) { + if (!mInitialising && year == getCurrentYear()) { + return; + } + mDate.set(Calendar.YEAR, year); + updateDateControl(); + onDateTimeChanged(); + } + + /** + * Get current month in the year + * + * @return The current month in the year + */ + public int getCurrentMonth() { + return mDate.get(Calendar.MONTH); + } + + /** + * Set current month in the year + * + * @param month The month in the year + */ + public void setCurrentMonth(int month) { + if (!mInitialising && month == getCurrentMonth()) { + return; + } + mDate.set(Calendar.MONTH, month); + updateDateControl(); + onDateTimeChanged(); + } + + /** + * Get current day of the month + * + * @return The day of the month + */ + public int getCurrentDay() { + return mDate.get(Calendar.DAY_OF_MONTH); + } + + /** + * Set current day of the month + * + * @param dayOfMonth The day of the month + */ + public void setCurrentDay(int dayOfMonth) { + if (!mInitialising && dayOfMonth == getCurrentDay()) { + return; + } + mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth); + updateDateControl(); + onDateTimeChanged(); + } + + /** + * Get current hour in 24 hour mode, in the range (0~23) + * @return The current hour in 24 hour mode + */ + public int getCurrentHourOfDay() { + return mDate.get(Calendar.HOUR_OF_DAY); + } + + private int getCurrentHour() { + if (mIs24HourView){ + return getCurrentHourOfDay(); + } else { + int hour = getCurrentHourOfDay(); + if (hour > HOURS_IN_HALF_DAY) { + return hour - HOURS_IN_HALF_DAY; + } else { + return hour == 0 ? HOURS_IN_HALF_DAY : hour; + } + } + } + + /** + * Set current hour in 24 hour mode, in the range (0~23) + * + * @param hourOfDay + */ + public void setCurrentHour(int hourOfDay) { + if (!mInitialising && hourOfDay == getCurrentHourOfDay()) { + return; + } + mDate.set(Calendar.HOUR_OF_DAY, hourOfDay); + if (!mIs24HourView) { + if (hourOfDay >= HOURS_IN_HALF_DAY) { + mIsAm = false; + if (hourOfDay > HOURS_IN_HALF_DAY) { + hourOfDay -= HOURS_IN_HALF_DAY; + } + } else { + mIsAm = true; + if (hourOfDay == 0) { + hourOfDay = HOURS_IN_HALF_DAY; + } + } + updateAmPmControl(); + } + mHourSpinner.setValue(hourOfDay); + onDateTimeChanged(); + } + + /** + * Get currentMinute + * + * @return The Current Minute + */ + public int getCurrentMinute() { + return mDate.get(Calendar.MINUTE); + } + + /** + * Set current minute + */ + public void setCurrentMinute(int minute) { + if (!mInitialising && minute == getCurrentMinute()) { + return; + } + mMinuteSpinner.setValue(minute); + mDate.set(Calendar.MINUTE, minute); + onDateTimeChanged(); + } + + /** + * @return true if this is in 24 hour view else false. + */ + public boolean is24HourView () { + return mIs24HourView; + } + + /** + * Set whether in 24 hour or AM/PM mode. + * + * @param is24HourView True for 24 hour mode. False for AM/PM mode. + */ + public void set24HourView(boolean is24HourView) { + if (mIs24HourView == is24HourView) { + return; //避免重复设置相同日期格式 + } + mIs24HourView = is24HourView; //记录当前时间格式 + mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);//如果采用24小时制的时间显示格式,则将上午/下午选择器设置为不可见;如果采用非24小时制的时间显示格式,则将上午/下午选择器设置为可见。 + 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(); //调用了mDateSpinner.invalidate()方法使控件的显示状态无效,从而强制刷新显示。 + } //该方法确保日期选择器的控件显示包含了当前日期的前一周以及后一周的日期,使用户可以方便地选择所需的日期 + + private void updateAmPmControl() { //用于更新上午/下午选择器的控件显示 + if (mIs24HourView) { + mAmPmSpinner.setVisibility(View.GONE); //如果控件采用24小时制的时间显示格式,则将上午/下午选择器设置为不可见,并直接返回。确保在24小时制的时间显示格式下不显示上午/下午选择器。 + } else { + int index = mIsAm ? Calendar.AM : Calendar.PM; + mAmPmSpinner.setValue(index); + mAmPmSpinner.setVisibility(View.VISIBLE); + } + } + + private void updateHourControl() { //根据当前的时间显示格式更新小时选择器的范围 + if (mIs24HourView) { + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW); + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW); //采用24小时制则设置为0~23 + } else { + mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW); + mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW); //采用12小时制则设置为0~11 + } + } + + /** + * Set the callback that indicates the 'Set' button has been pressed. + * @param callback the callback, if null will do nothing + */ + public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) { //用于设置日期时间变化监听器,即设置回调函数,当日期时间发生变化时,会触发相应的操作 + mOnDateTimeChangedListener = callback; //保存要设置的回调函数 + } + + private void onDateTimeChanged() { //用于在日期时间发生变化时触发回调函数 + if (mOnDateTimeChangedListener != null) { //如果日期时间变化监听器不为null则执行后续的操作 + mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(), + getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute()); // //将当前DateTimePicker以及当前的年、月、日、小时和分钟作为参数传递给监听器 + } + } +}