/* * 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. */ // 包声明,表明该类所在的包名为net.micode.notes.ui 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,它是一个自定义的视图组件,用于实现日期和时间的选择功能,用户可以通过该组件方便地设置年、月、日、时、分以及选择12/24小时制等。 public class DateTimePicker extends FrameLayout { // 定义默认的启用状态,默认为启用(true),用于控制整个DateTimePicker组件及其内部子控件的可交互状态。 private static final boolean DEFAULT_ENABLE_STATE = true; // 定义半天的小时数,用于12小时制相关的时间计算和判断,例如区分上午和下午的时间范围。 private static final int HOURS_IN_HALF_DAY = 12; // 定义一天的总小时数,用于24小时制相关的操作和判断。 private static final int HOURS_IN_ALL_DAY = 24; // 定义一周的天数,用于日期选择器(如显示一周内的日期选项等)相关的操作。 private static final int DAYS_IN_ALL_WEEK = 7; // 定义日期选择器(NumberPicker)的最小值,通常从0开始,表示一周内日期选项的最小索引。 private static final int DATE_SPINNER_MIN_VAL = 0; // 定义日期选择器(NumberPicker)的最大值,根据一周7天,最大值为6(索引从0开始)。 private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1; // 定义24小时制视图下小时选择器(NumberPicker)的最小值,即0点。 private static final int HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW = 0; // 定义24小时制视图下小时选择器(NumberPicker)的最大值,即23点。 private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; // 定义12小时制视图下小时选择器(NumberPicker)的最小值,通常为1(12小时制习惯从1开始计数)。 private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; // 定义12小时制视图下小时选择器(NumberPicker)的最大值,即12点。 private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12; // 定义分钟选择器(NumberPicker)的最小值,即0分钟。 private static final int MINUT_SPINNER_MIN_VAL = 0; // 定义分钟选择器(NumberPicker)的最大值,即59分钟。 private static final int MINUT_SPINNER_MAX_VAL = 59; // 定义上午/下午(AM/PM)选择器(NumberPicker)的最小值,通常为0(对应AM)。 private static final int AMPM_SPINNER_MIN_VAL = 0; // 定义上午/下午(AM/PM)选择器(NumberPicker)的最大值,通常为1(对应PM)。 private static final int AMPM_SPINNER_MAX_VAL = 1; // 用于显示日期的NumberPicker控件,用户可以通过它选择具体的日期(以一周内的某一天来表示)。 private final NumberPicker mDateSpinner; // 用于显示小时的NumberPicker控件,根据设置的12/24小时制,显示相应范围的小时选项供用户选择。 private final NumberPicker mHourSpinner; // 用于显示分钟的NumberPicker控件,提供0到59分钟的选项供用户选择。 private final NumberPicker mMinuteSpinner; // 用于显示上午/下午(AM/PM)的NumberPicker控件,在12小时制下用于区分时间段,仅在非24小时制时可见。 private final NumberPicker mAmPmSpinner; // Calendar对象,用于存储和操作当前选择的日期和时间信息,方便进行各种日期时间的计算和设置。 private Calendar mDate; // 用于存储一周内各天显示名称的字符串数组,例如“周一”“周二”等,会根据系统设置进行本地化显示。 private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; // 用于标记当前时间是否处于上午(AM),根据小时数等情况进行更新,用于12小时制下的显示和逻辑判断。 private boolean mIsAm; // 用于标记是否处于24小时制视图,true表示24小时制,false表示12小时制,决定了小时选择器和上午/下午选择器的显示及相关逻辑。 private boolean mIs24HourView; // 用于存储组件的启用状态,初始化为默认的启用状态,通过相应方法可以修改并控制组件及其内部控件的可操作性。 private boolean mIsEnabled = DEFAULT_ENABLE_STATE; // 用于标记是否处于初始化阶段,在初始化过程中一些逻辑处理可能与正常使用阶段有所不同,避免不必要的重复操作或异常情况。 private boolean mInitialising; // 定义一个接口类型的变量,用于设置日期时间改变时的回调监听器,外部类可以实现该接口来监听用户在DateTimePicker上操作导致的日期时间变化情况。 private OnDateTimeChangedListener mOnDateTimeChangedListener; // 内部类,实现了NumberPicker.OnValueChangeListener接口,用于监听日期选择器(mDateSpinner)的值变化事件。 // 当用户在日期选择器上选择了不同的日期时,会触发该监听器的onValueChange方法,进而更新内部的日期信息(mDate)以及相关的显示控件,并通知外部监听器日期时间已改变。 private NumberPicker.OnValueChangeListener mOnDateChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { // 根据新选择的日期与旧日期的差值,调整内部的Calendar对象(mDate)的日期信息,实现日期的变更。 mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal); // 更新日期显示相关的控件,确保界面上显示的日期信息是最新的。 updateDateControl(); // 调用onDateTimeChanged方法,通知外部监听器日期时间已发生改变,触发相应的回调逻辑(如果有设置监听器的话)。 onDateTimeChanged(); } }; // 内部类,实现了NumberPicker.OnValueChangeListener接口,用于监听小时选择器(mHourSpinner)的值变化事件。 // 当用户在小时选择器上选择了不同的小时值时,会触发该监听器的onValueChange方法,根据12/24小时制等不同情况进行日期、时间以及相关显示控件的更新,并通知外部监听器日期时间已改变。 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小时制下,如果当前是下午(!mIsAm)且从11点(HOURS_IN_HALF_DAY - 1)切换到12点(HOURS_IN_HALF_DAY),则日期需要加一天,标记日期已改变。 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; } // 在12小时制下,如果当前是上午(mIsAm)且从12点(HOURS_IN_HALF_DAY)切换到11点(HOURS_IN_HALF_DAY - 1),则日期需要减一天,标记日期已改变。 else if (mIsAm && oldVal == HOURS_IN_HALF_DAY && newVal == HOURS_IN_HALF_DAY - 1) { cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, -1); isDateChanged = true; } // 如果是从11点切换到12点或者从12点切换到11点,还需要切换上午/下午(AM/PM)的标记,并更新对应的显示控件。 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小时制下,如果从23点(HOURS_IN_ALL_DAY - 1)切换到0点,则日期需要加一天,标记日期已改变。 if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) { cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, 1); isDateChanged = true; } // 在24小时制下,如果从0点切换到23点,则日期需要减一天,标记日期已改变。 else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { cal.setTimeInMillis(mDate.getTimeInMillis()); cal.add(Calendar.DAY_OF_YEAR, -1); isDateChanged = true; } } // 根据当前选择的小时值以及上午/下午标记(在12小时制下),计算出用于设置到内部Calendar对象(mDate)的小时数,并进行设置。 int newHour = mHourSpinner.getValue() % HOURS_IN_HALF_DAY + (mIsAm? 0 : HOURS_IN_HALF_DAY); mDate.set(Calendar.HOUR_OF_DAY, newHour); // 通知外部监听器日期时间已发生改变,触发相应的回调逻辑(如果有设置监听器的话)。 onDateTimeChanged(); // 如果日期发生了改变,更新当前的年、月、日信息到组件内部的Calendar对象(mDate)中,确保整体日期时间信息的一致性。 if (isDateChanged) { setCurrentYear(cal.get(Calendar.YEAR)); setCurrentMonth(cal.get(Calendar.MONTH)); setCurrentDay(cal.get(Calendar.DAY_OF_MONTH)); } } }; // 内部类,实现了NumberPicker.OnValueChangeListener接口,用于监听分钟选择器(mMinuteSpinner)的值变化事件。 // 当用户在分钟选择器上选择了不同的分钟值时,会触发该监听器的onValueChange方法,根据分钟值的变化情况进行日期、时间以及相关显示控件的更新,并通知外部监听器日期时间已改变。 private NumberPicker.OnValueChangeListener mOnMinuteChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { int minValue = mMinuteSpinner.getMinValue(); int maxValue = mMinuteSpinner.getMaxValue(); int offset = 0; // 如果从最大分钟值(59)切换到最小分钟值(0),则小时数需要加1,表示时间向后推移了一小时。 if (oldVal == maxValue && newVal == minValue) { offset += 1; } // 如果从最小分钟值(0)切换到最大分钟值(59),则小时数需要减1,表示时间向前推移了一小时。 else if (oldVal == minValue && newVal == maxValue) { offset -= 1; } if (offset!= 0) { // 根据分钟值变化导致的小时数偏移,调整内部的Calendar对象(mDate)的小时信息,实现时间的变更。 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(); } } // 设置内部的Calendar对象(mDate)的分钟信息为用户选择的新分钟值。 mDate.set(Calendar.MINUTE, newVal); // 通知外部监听器日期时间已发生改变,触发相应的回调逻辑(如果有设置监听器的话)。 onDateTimeChanged(); } }; // 内部类,实现了NumberPicker.OnValueChangeListener接口,用于监听上午/下午(AM/PM)选择器(mAmPmSpinner)的值变化事件。 // 当用户在上午/下午选择器上切换了选项时,会触发该监听器的onValueChange方法,相应地调整内部的日期时间信息以及更新相关显示控件,并通知外部监听器日期时间已改变。 private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() { @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { mIsAm =!mIsAm; // 如果切换到上午(AM),则将小时数减去12小时(从下午时间转换到上午时间对应的小时调整)。 if (mIsAm) { mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); } // 如果切换到下午(PM),则将小时数加上12小时(从上午时间转换到下午时间对应的小时调整)。 else { mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); } // 更新上午/下午显示相关的控件,确保界面上显示的信息与内部状态一致。 updateAmPmControl(); // 通知外部监听器日期时间已发生改变,触发相应的回调逻辑(如果有设置监听器的话)。 onDateTimeChanged(); } }; // 定义一个接口,用于外部类实现,以便在DateTimePicker的日期时间发生改变时接收到通知并进行相应的处理。 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()); } // 构造函数,创建一个DateTimePicker实例,并使用指定的日期时间(以毫秒为单位的时间戳)作为初始时间进行初始化。 public DateTimePicker(Context context, long date) { this(context, date, DateFormat.is24HourFormat(context)); } // 构造函数,创建一个DateTimePicker实例,使用指定的日期时间(以毫秒为单位的时间戳)和是否为24小时制作为参数进行初始化,这是最完整的初始化构造函数。 public DateTimePicker(Context context, long date, boolean is24HourView) { super(context); // 获取一个Calendar实例,用于存储和操作日期时间信息,初始化为当前时间(如果没有传入特定日期时间的话)。 mDate = Calendar.getInstance(); mInitialising = true; // 根据当前小时数判断是否处于上午(AM),用于初始化上午/下午标记(如果当前小时大于等于12,则为下午,即!mIsAm)。 mIsAm = getCurrentHourOfDay() >= HOURS