You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
git-test/src/ui/DateTimePicker.java

606 lines
23 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* 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;
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;
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;
private final NumberPicker mHourSpinner;
private final NumberPicker mMinuteSpinner;
private final NumberPicker mAmPmSpinner;
private Calendar mDate;
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
private boolean mIsAm;
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的日历字段根据新值和旧值之间的差异
mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal);
// 更新与日期选择相关的用户界面控件,以反映新的日期值
updateDateControl();
// 可能用于通知日期时间的整体变化具体实现需要查看onDateTimeChanged()方法的定义
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(); // 创建一个日历对象,用于处理日期操作
// 如果不是24小时制
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小时制
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; // 如果跨越了分钟的边界将小时偏移量增加1
} else if (oldVal == minValue && newVal == maxValue) {
offset -= 1; // 如果跨越了分钟的边界将小时偏移量减少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); // 如果是上午减少12小时
} else {
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); // 如果是下午增加12小时
}
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()); // 调用另一个构造函数并传入当前系统时间
}
// 构造函数用于创建DateTimePicker实例传入上下文和日期时间参数
public DateTimePicker(Context context, long date) {
this(context, date, DateFormat.is24HourFormat(context)); // 调用另一个构造函数并传入日期时间和是否为24小时制的参数
}
// 构造函数用于创建DateTimePicker实例传入上下文、日期时间和是否为24小时制的参数
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(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();
set24HourView(is24HourView); // 设置是否为24小时制
// 设置当前日期时间
setCurrentDate(date);
setEnabled(isEnabled()); // 设置控件是否可用
// 设置内容描述,用于辅助功能
mInitialising = false; // 初始化过程结束
}
/**
* 设置控件是否启用
*
* @param enabled 控件是否启用的标志
*/
@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; // 更新控件的启用状态标志
}
/**
* 检查控件是否启用
*
* @return 控件是否启用的标志
*/
@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); // 将传入的日期毫秒值设置到Calendar对象中
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小时制下的当前小时取值范围为(0~23)。
*
* @return 24小时制下的当前小时。
*/
public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY);
}
/**
* 根据时间格式获取当前小时。
*
* @return 当前小时。
*/
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小时制表示取值范围为(0~23)。
*
* @param hourOfDay 要设置的小时。
*/
public void setCurrentHour(int hourOfDay) {
// 如果不是在初始化状态并且设置的小时与当前小时一致,则不执行任何操作。
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
return;
}
// 设置日历对象中的小时字段为指定值。
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
// 如果不是24小时制根据小时值来确定上午或下午。
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小时制或上午/下午制。
*
* @param is24HourView 如果为true表示使用24小时制否则使用上午/下午制。
*/
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 对象用于日期操作
Calendar cal = Calendar.getInstance();
// 设置 Calendar 对象的时间戳为用户选择的日期
cal.setTimeInMillis(mDate.getTimeInMillis());
// 向前调整日期以确保在日期控件中显示一周的日期
cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1);
// 清空日期控件的显示值
mDateSpinner.setDisplayedValues(null);
// 循环生成一周中的日期并存储在 mDateDisplayValues 数组中
for (int i = 0; i < DAYS_IN_ALL_WEEK; ++i) {
cal.add(Calendar.DAY_OF_YEAR, 1);
// 格式化日期为 "MM.dd EEEE" 的字符串形式并存储在 mDateDisplayValues 数组中
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) {
// 如果应用使用24小时制则隐藏上午/下午控件
mAmPmSpinner.setVisibility(View.GONE);
} else {
// 如果应用使用12小时制根据 mIsAm 的值确定上午或下午,并设置对应的值
int index = mIsAm ? Calendar.AM : Calendar.PM;
mAmPmSpinner.setValue(index);
// 显示上午/下午控件
mAmPmSpinner.setVisibility(View.VISIBLE);
}
}
// 更新小时控件的方法
private void updateHourControl() {
if (mIs24HourView) {
// 如果应用使用24小时制设置小时控件的最小和最大值
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);
} else {
// 如果应用使用12小时制设置小时控件的最小和最大值
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW);
}
}
/**
* 设置在用户按下“设置”按钮时的回调
* @param callback 回调接口,如果为 null则不执行任何操作
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
// 设置日期时间变化监听器
mOnDateTimeChangedListener = callback;
}
// 当日期时间发生变化时调用的方法
private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) {
// 如果监听器不为空,触发监听器的回调方法并传递当前选择的年、月、日、小时和分钟作为参数
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}
}
}