|
|
/*
|
|
|
* 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 |