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.
MiNote/ui/DateTimePicker.java

480 lines
20 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)
*
* 版权声明本文件由MiCode开源社区开发遵循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;
import android.content.Context;
import android.text.format.DateFormat;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.NumberPicker;
/**
* 日期时间选择器组件(自定义视图)
* 功能提供日期星期、小时、分钟、上下午的选择功能支持24小时制和12小时制切换
* 组件构成包含四个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; // 日期选择器最小值(周一至周日的索引起点)
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; // 24小时制小时选择器最小值
private static final int HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW = 23; // 24小时制小时选择器最大值
private static final int HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW = 1; // 12小时制小时选择器最小值
private static final int HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW = 12; // 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; // 上下午选择器最小值AM
private static final int AMPM_SPINNER_MAX_VAL = 1; // 上下午选择器最大值PM
// 组件实例
private final NumberPicker mDateSpinner; // 日期选择器(显示一周内的日期)
private final NumberPicker mHourSpinner; // 小时选择器
private final NumberPicker mMinuteSpinner; // 分钟选择器
private final NumberPicker mAmPmSpinner; // 上下午选择器仅12小时制可见
private Calendar mDate; // 当前选择的日期时间Calendar对象
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; // 日期选择器显示的字符串数组(如"MM.dd EEEE"格式)
private boolean mIsAm; // 是否为上午仅12小时制有效
private boolean mIs24HourView; // 是否为24小时制模式
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) {
// 根据新旧值差调整日期如从周一滚动到周二日期加1天
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小时制处理
// 处理跨半天的情况如12点AM→PM或反之
if (!mIsAm && oldVal == HOURS_IN_HALF_DAY - 1 && newVal == HOURS_IN_HALF_DAY) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1); // 日期加1天PM转次日AM
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); // 日期减1天AM转前一日PM
isDateChanged = true;
}
// 切换上下午状态12点切换时
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) { // 23点→0点日期加1天
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
} else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) { // 0点→23点日期减1天
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
}
// 计算实际小时12小时制转换为24小时制存储
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;
// 处理分钟循环如59→0时小时加10→59时小时减1
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();
// 根据新小时更新上下午状态12小时制
if (newHour >= HOURS_IN_HALF_DAY) {
mIsAm = false;
} else {
mIsAm = true;
}
updateAmPmControl(); // 更新上下午选择器显示
}
mDate.set(Calendar.MINUTE, newVal); // 设置分钟
onDateTimeChanged(); // 触发变更回调
}
};
// 上下午选择器值变更监听器处理AM/PM切换
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mIsAm = !mIsAm; // 切换上下午状态
// 调整小时12小时制下AM/PM切换时小时加减12
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);
}
// 构造方法:使用当前时间初始化选择器
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis());
}
// 构造方法:使用指定时间戳初始化选择器
public DateTimePicker(Context context, long date) {
this(context, date, DateFormat.is24HourFormat(context)); // 自动判断是否为24小时制
}
/**
* 完整构造方法
* @param context 上下文
* @param date 初始化时间戳
* @param is24HourView 是否为24小时制模式
*/
public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context);
mDate = Calendar.getInstance(); // 初始化日历对象
mInitialising = true; // 设置初始化标志(防止回调触发)
mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; // 初始化上下午状态
// 加载布局文件R.layout.datetime_picker
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);
// 初始化上下午选择器加载AM/PM字符串
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);
// 分解时间组件并设置
setCurrentYear(cal.get(Calendar.YEAR));
setCurrentMonth(cal.get(Calendar.MONTH));
setCurrentDay(cal.get(Calendar.DAY_OF_MONTH));
setCurrentHour(cal.get(Calendar.HOUR_OF_DAY));
setCurrentMinute(cal.get(Calendar.MINUTE));
}
/**
* 设置当前时间(通过年月日时分)
* @param year 年份
* @param month 月份0-11
* @param dayOfMonth 日期1-31
* @param hourOfDay 小时24小时制0-23
* @param minute 分钟0-59
*/
public void setCurrentDate(int year, int month,
int dayOfMonth, int hourOfDay, int minute) {
setCurrentYear(year);
setCurrentMonth(month);
setCurrentDay(dayOfMonth);
setCurrentHour(hourOfDay);
setCurrentMinute(minute);
}
// 获取当前年份
public int getCurrentYear() {
return mDate.get(Calendar.YEAR);
}
// 设置当前年份
public void setCurrentYear(int year) {
if (!mInitialising && year == getCurrentYear()) {
return;
}
mDate.set(Calendar.YEAR, year);
updateDateControl(); // 更新日期选择器显示
onDateTimeChanged(); // 触发变更回调
}
// 获取当前月份0-11
public int getCurrentMonth() {
return mDate.get(Calendar.MONTH);
}
// 设置当前月份0-11
public void setCurrentMonth(int month) {
if (!mInitialising && month == getCurrentMonth()) {
return;
}
mDate.set(Calendar.MONTH, month);
updateDateControl(); // 更新日期选择器显示
onDateTimeChanged(); // 触发变更回调
}
// 获取当前日期1-31
public int getCurrentDay() {
return mDate.get(Calendar.DAY_OF_MONTH);
}
// 设置当前日期1-31
public void setCurrentDay(int dayOfMonth) {
if (!mInitialising && dayOfMonth == getCurrentDay()) {
return;
}
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
updateDateControl(); // 更新日期选择器显示
onDateTimeChanged(); // 触发变更回调
}
// 获取当前小时24小时制0-23
public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY);
}
// 获取当前小时(根据显示模式转换后的小时值)
private int getCurrentHour() {
if (mIs24HourView) {
return getCurrentHourOfDay();
} else {
int hour = getCurrentHourOfDay();
// 12小时制转换0点→12 AM13点→1 PM依此类推
return hour == 0 ? HOURS_IN_HALF_DAY : (hour > HOURS_IN_HALF_DAY ? hour - HOURS_IN_HALF_DAY : hour);
}
}
/**
* 设置当前小时24小时制0-23
* @param hourOfDay 小时24小时制
*/
public void setCurrentHour(int hourOfDay) {
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
return;
}
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
if (!mIs24HourView) { // 12小时制下调整显示值
if (hourOfDay >= HOURS_IN_HALF_DAY) {
mIsAm = false;
if (hourOfDay > HOURS_IN_HALF_DAY) {
hourOfDay -= HOURS_IN_HALF_DAY; // 转换为12小时制显示值
}
} else {
mIsAm = true;
if (hourOfDay == 0) {
hourOfDay = HOURS_IN_HALF_DAY; // 0点显示为12 AM
}
}
updateAmPmControl(); // 更新上下午选择器显示
}
mHourSpinner.setValue(hourOfDay); // 设置小时选择器值
onDateTimeChanged(); // 触发变更回调
}
// 获取当前分钟0-59
public int getCurrentMinute() {
return mDate.get(Calendar.MINUTE);
}
// 设置当前分钟0-59
public void setCurrentMinute(int minute) {
if (!mInitialising && minute == getCurrentMinute()) {
return;
}
mMinuteSpinner.setValue(minute); // 设置分钟选择器值
mDate.set(Calendar.MINUTE, minute); // 更新日历对象
onDateTimeChanged(); // 触发变更回调
}
// 获取是否为24小时制模式
public boolean is24HourView() {
return mIs24HourView;
}
/**
* 设置时间显示模式24小时制或12小时制
* @param is24HourView true为24小时制false为12小时制
*/
public void set24HourView(boolean is24HourView) {
if (mIs24HourView == is24HourView) {
return;
}
mIs24HourView = is24HourView;
// 控制上下午选择器的可见性
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);
int hour = getCurrentHourOfDay(); // 获取当前小时24小时制
updateHourControl(); // 更新小时选择器的范围
setCurrentHour(hour); // 重新设置小时(触发显示更新)
updateAmPmControl(); // 更新上下午选择器状态
}
/**
* 更新日期选择器的显示内容(显示一周内的日期)
* 逻辑以当前日期为中心显示前3天、当前天、后3天
*/
private void updateDateControl() {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(mDate.getTimeInMillis());
// 计算起始日期当前日期前3天
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); // 逐日递增
// 格式化为"MM.dd EEEE"(如"10.25 星期二"
mDateDisplayValues[i] = (String) DateFormat.format("MM.dd EEEE", cal);
}
mDateSpinner.setDisplayedValues(mDateDisplayValues); // 设置显示值数组
mDateSpinner.setValue(DAYS_IN_ALL_WEEK / 2); // 设置当前日期居中显示索引3
mDateSpinner.invalidate(); // 刷新选择器显示
}
// 更新上下午选择器的状态同步显示AM/PM
private void updateAmPmControl() {
if (mIs24HourView) {
mAmPmSpinner.setVisibility(View.GONE); // 隐藏上下午选择器
} else {
int index = mIsAm ? Calendar.AM : Calendar.PM; // 根据状态获取AM/PM索引
mAmPmSpinner.setValue(index); // 设置选择器值
mAmPmSpinner.setVisibility(View.VISIBLE); // 显示上下午选择器
}
}
// 更新小时选择器的范围根据24小时制/12小时制模式
private void updateHourControl() {
if (mIs24HourView) {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);
} else {
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_12_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_12_HOUR_VIEW);
}
}
// 设置日期时间变更监听器
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
mOnDateTimeChangedListener = callback;
}
// 触发日期时间变更回调(通知监听器)
private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) {
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(), getCurrentMinute());
}
}
}