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/java/net/micode/notes/ui/DateTimePicker.java

680 lines
25 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.
*/
// DateTimePicker.java - 日期时间选择器自定义控件
// 主要功能:提供便签闹钟设置所需的日期和时间选择界面
package net.micode.notes.ui;
// ======================= 导入区域 =======================
// Java日期时间相关
import java.text.DateFormatSymbols; // 日期格式符号用于获取AM/PM字符串
import java.util.Calendar; // 日历类,用于日期时间计算
// 应用内部资源
import net.micode.notes.R; // 资源文件R类
// Android相关
import android.content.Context; // 上下文
import android.text.format.DateFormat; // 日期格式化工具
import android.view.View; // 视图基类
import android.widget.FrameLayout; // 帧布局容器
import android.widget.NumberPicker; // 数字选择器控件
// ======================= 日期时间选择器控件 =======================
/**
* DateTimePicker - 自定义日期时间选择器
* 继承自FrameLayout包含多个NumberPicker控件
* 功能日期近7天、小时、分钟、AM/PM选择
* 支持12小时制和24小时制两种显示模式
*/
public class DateTimePicker extends FrameLayout {
// ======================= 常量定义 =======================
/** 默认启用状态 - 控件初始化时的默认可用状态 */
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;
// 日期选择器范围常量
private static final int DATE_SPINNER_MIN_VAL = 0; // 日期选择器最小值
private static final int DATE_SPINNER_MAX_VAL = DAYS_IN_ALL_WEEK - 1; // 日期选择器最大值
// 24小时制小时选择器范围常量
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小时制最大值
// 12小时制小时选择器范围常量
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; // 分钟最大值
// AM/PM选择器范围常量
private static final int AMPM_SPINNER_MIN_VAL = 0; // AM索引
private static final int AMPM_SPINNER_MAX_VAL = 1; // PM索引
// ======================= 控件成员变量 =======================
/** 日期选择器 - 显示近7天的日期 */
private final NumberPicker mDateSpinner;
/** 小时选择器 - 显示小时12/24小时制 */
private final NumberPicker mHourSpinner;
/** 分钟选择器 - 显示分钟 */
private final NumberPicker mMinuteSpinner;
/** AM/PM选择器 - 显示上午/下午12小时制时显示 */
private final NumberPicker mAmPmSpinner;
/** 当前日期时间 - Calendar对象保存当前选择的时间 */
private Calendar mDate;
/** 日期显示值数组 - 存储近7天的格式化字符串 */
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
// ======================= 状态标志 =======================
/** AM/PM标志 - true: 上午; false: 下午 */
private boolean mIsAm;
/** 24小时制标志 - true: 24小时制; false: 12小时制 */
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) {
// 计算日期变化量(新值-旧值并更新Calendar
mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal);
// 更新日期控件显示
updateDateControl();
// 触发日期时间变化回调
onDateTimeChanged();
}
};
// ======================= 小时选择器值变化监听器 =======================
/**
* 小时选择器值变化监听器
* 处理小时变化时的特殊逻辑跨天、AM/PM切换
*/
private NumberPicker.OnValueChangeListener mOnHourChangedListener =
new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
boolean isDateChanged = false; // 日期是否变化标志
Calendar cal = Calendar.getInstance(); // 临时Calendar用于计算
if (!mIs24HourView) {
// ===== 12小时制处理逻辑 =====
// 情况1PM 11点 -> PM 12点需要加1天
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;
}
// 情况2AM 12点 -> AM 11点需要减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;
}
// 处理AM/PM切换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; // 切换AM/PM标志
updateAmPmControl(); // 更新AM/PM控件
}
} else {
// ===== 24小时制处理逻辑 =====
// 情况123点 -> 0点需要加1天
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
}
// 情况20点 -> 23点需要减1天
else if (oldVal == 0 && newVal == HOURS_IN_ALL_DAY - 1) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, -1);
isDateChanged = true;
}
}
// 计算实际的小时值12小时制需要转换
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));
}
}
};
// ======================= 分钟选择器值变化监听器 =======================
/**
* 分钟选择器值变化监听器
* 处理分钟滚动时的特殊逻辑59分到0分0分到59分
*/
private NumberPicker.OnValueChangeListener mOnMinuteChangedListener =
new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
int minValue = mMinuteSpinner.getMinValue(); // 分钟最小值0
int maxValue = mMinuteSpinner.getMaxValue(); // 分钟最大值59
int offset = 0; // 小时偏移量
// 情况159分 -> 0分小时加1
if (oldVal == maxValue && newVal == minValue) {
offset += 1;
}
// 情况20分 -> 59分小时减1
else if (oldVal == minValue && newVal == maxValue) {
offset -= 1;
}
// 如果需要调整小时
if (offset != 0) {
mDate.add(Calendar.HOUR_OF_DAY, offset); // 调整小时
mHourSpinner.setValue(getCurrentHour()); // 更新小时选择器
updateDateControl(); // 更新日期控件
// 更新AM/PM状态
int newHour = getCurrentHourOfDay();
if (newHour >= HOURS_IN_HALF_DAY) {
mIsAm = false;
updateAmPmControl();
} else {
mIsAm = true;
updateAmPmControl();
}
}
// 设置新的分钟值
mDate.set(Calendar.MINUTE, newVal);
onDateTimeChanged(); // 触发回调
}
};
// ======================= AM/PM选择器值变化监听器 =======================
/**
* AM/PM选择器值变化监听器
* 处理上午/下午切换时的逻辑
*/
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener =
new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mIsAm = !mIsAm; // 切换AM/PM标志
// 根据AM/PM调整小时
if (mIsAm) {
mDate.add(Calendar.HOUR_OF_DAY, -HOURS_IN_HALF_DAY); // PM转AM减12小时
} else {
mDate.add(Calendar.HOUR_OF_DAY, HOURS_IN_HALF_DAY); // AM转PM加12小时
}
updateAmPmControl(); // 更新AM/PM控件
onDateTimeChanged(); // 触发回调
}
};
// ======================= 回调接口定义 =======================
/**
* OnDateTimeChangedListener - 日期时间变化监听器接口
* 当用户修改日期时间时回调
*/
public interface OnDateTimeChangedListener {
/**
* 日期时间变化回调方法
* @param view 触发变化的DateTimePicker控件
* @param year 年
* @param month 月0-11
* @param dayOfMonth 日1-31
* @param hourOfDay 小时0-23
* @param minute 分钟0-59
*/
void onDateTimeChanged(DateTimePicker view, int year, int month,
int dayOfMonth, int hourOfDay, int minute);
}
// ======================= 构造函数 =======================
/**
* 构造函数1 - 使用当前时间
* @param context 上下文
*/
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis()); // 调用构造函数2
}
/**
* 构造函数2 - 指定时间戳
* @param context 上下文
* @param date 时间戳(毫秒)
*/
public DateTimePicker(Context context, long date) {
this(context, date, DateFormat.is24HourFormat(context)); // 调用构造函数3
}
/**
* 构造函数3 - 完整参数
* @param context 上下文
* @param date 时间戳(毫秒)
* @param is24HourView 是否24小时制
*/
public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context);
// 1. 初始化成员变量
mDate = Calendar.getInstance(); // 创建Calendar实例
mInitialising = true; // 标记为初始化中
mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY; // 根据当前时间判断AM/PM
// 2. 加载布局文件
inflate(context, R.layout.datetime_picker, this);
// 3. 初始化日期选择器
mDateSpinner = (NumberPicker) findViewById(R.id.date);
mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL);
mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL);
mDateSpinner.setOnValueChangedListener(mOnDateChangedListener);
// 4. 初始化小时选择器
mHourSpinner = (NumberPicker) findViewById(R.id.hour);
mHourSpinner.setOnValueChangedListener(mOnHourChangedListener);
// 5. 初始化分钟选择器
mMinuteSpinner = (NumberPicker) findViewById(R.id.minute);
mMinuteSpinner.setMinValue(MINUT_SPINNER_MIN_VAL);
mMinuteSpinner.setMaxValue(MINUT_SPINNER_MAX_VAL);
mMinuteSpinner.setOnLongPressUpdateInterval(100); // 长按滚动间隔100ms
mMinuteSpinner.setOnValueChangedListener(mOnMinuteChangedListener);
// 6. 初始化AM/PM选择器
String[] stringsForAmPm = new DateFormatSymbols().getAmPmStrings(); // 获取AM/PM本地化字符串
mAmPmSpinner = (NumberPicker) findViewById(R.id.amPm);
mAmPmSpinner.setMinValue(AMPM_SPINNER_MIN_VAL);
mAmPmSpinner.setMaxValue(AMPM_SPINNER_MAX_VAL);
mAmPmSpinner.setDisplayedValues(stringsForAmPm); // 设置显示值
mAmPmSpinner.setOnValueChangedListener(mOnAmPmChangedListener);
// 7. 更新控件初始状态
updateDateControl(); // 更新日期显示
updateHourControl(); // 更新小时范围
updateAmPmControl(); // 更新AM/PM显示
// 8. 设置24小时制模式
set24HourView(is24HourView);
// 9. 设置当前时间
setCurrentDate(date);
// 10. 设置启用状态
setEnabled(isEnabled());
// 11. 初始化完成
mInitialising = false;
}
// ======================= 控件状态管理 =======================
/**
* 设置控件启用状态
* @param enabled true: 启用; 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;
}
/**
* 获取控件启用状态
* @return true: 启用; false: 禁用
*/
@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);
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 月0-11
* @param dayOfMonth 日1-31
* @param hourOfDay 小时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);
}
// ======================= 年、月、日操作方法 =======================
/**
* 获取当前年
* @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 月0-11
*/
public int getCurrentMonth() {
return mDate.get(Calendar.MONTH);
}
/**
* 设置当前月
* @param month 月0-11
*/
public void setCurrentMonth(int month) {
if (!mInitialising && month == getCurrentMonth()) {
return;
}
mDate.set(Calendar.MONTH, month);
updateDateControl();
onDateTimeChanged();
}
/**
* 获取当前日
* @return 日1-31
*/
public int getCurrentDay() {
return mDate.get(Calendar.DAY_OF_MONTH);
}
/**
* 设置当前日
* @param dayOfMonth 日1-31
*/
public void setCurrentDay(int dayOfMonth) {
if (!mInitialising && dayOfMonth == getCurrentDay()) {
return;
}
mDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
updateDateControl();
onDateTimeChanged();
}
// ======================= 小时操作方法 =======================
/**
* 获取当前小时24小时制
* @return 小时0-23
*/
public int getCurrentHourOfDay() {
return mDate.get(Calendar.HOUR_OF_DAY);
}
/**
* 获取当前小时根据12/24小时制转换
* @return 小时12小时制1-1224小时制0-23
*/
private int getCurrentHour() {
if (mIs24HourView){
return getCurrentHourOfDay(); // 24小时制直接返回
} else {
int hour = getCurrentHourOfDay();
if (hour > HOURS_IN_HALF_DAY) {
return hour - HOURS_IN_HALF_DAY; // PM13-23转换为1-11
} else {
return hour == 0 ? HOURS_IN_HALF_DAY : hour; // AM0点转为12点
}
}
}
/**
* 设置当前小时24小时制
* @param hourOfDay 小时0-23
*/
public void setCurrentHour(int hourOfDay) {
if (!mInitialising && hourOfDay == getCurrentHourOfDay()) {
return;
}
mDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
// 12小时制时需要额外处理AM/PM
if (!mIs24HourView) {
if (hourOfDay >= HOURS_IN_HALF_DAY) {
mIsAm = false; // PM
if (hourOfDay > HOURS_IN_HALF_DAY) {
hourOfDay -= HOURS_IN_HALF_DAY; // 13-23转换为1-11
}
} else {
mIsAm = true; // AM
if (hourOfDay == 0) {
hourOfDay = HOURS_IN_HALF_DAY; // 0点转为12点
}
}
updateAmPmControl(); // 更新AM/PM控件
}
mHourSpinner.setValue(hourOfDay); // 设置小时选择器值
onDateTimeChanged(); // 触发回调
}
// ======================= 分钟操作方法 =======================
/**
* 获取当前分钟
* @return 分钟0-59
*/
public int getCurrentMinute() {
return mDate.get(Calendar.MINUTE);
}
/**
* 设置当前分钟
* @param minute 分钟0-59
*/
public void setCurrentMinute(int minute) {
if (!mInitialising && minute == getCurrentMinute()) {
return;
}
mMinuteSpinner.setValue(minute); // 设置分钟选择器
mDate.set(Calendar.MINUTE, minute);
onDateTimeChanged();
}
// ======================= 24小时制管理 =======================
/**
* 判断是否为24小时制
* @return true: 24小时制; false: 12小时制
*/
public boolean is24HourView () {
return mIs24HourView;
}
/**
* 设置24小时制模式
* @param is24HourView true: 24小时制; false: 12小时制
*/
public void set24HourView(boolean is24HourView) {
if (mIs24HourView == is24HourView) {
return; // 模式未变化
}
mIs24HourView = is24HourView;
// 显示/隐藏AM/PM选择器
mAmPmSpinner.setVisibility(is24HourView ? View.GONE : View.VISIBLE);
// 获取当前小时并更新控件
int hour = getCurrentHourOfDay();
updateHourControl(); // 更新小时选择器范围
setCurrentHour(hour); // 重新设置小时
updateAmPmControl(); // 更新AM/PM状态
}
// ======================= 控件更新方法 =======================
/**
* 更新日期控件显示
* 计算并显示当前日期前后3天的日期
*/
private void updateDateControl() {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(mDate.getTimeInMillis());
// 从当前日期向前推4天-3-1 = -4
cal.add(Calendar.DAY_OF_YEAR, -DAYS_IN_ALL_WEEK / 2 - 1);
mDateSpinner.setDisplayedValues(null); // 清空显示值
// 生成7天的日期显示字符串
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(); // 重绘控件
}
/**
* 更新AM/PM控件
* 根据24小时制标志显示/隐藏根据AM/PM标志设置值
*/
private void updateAmPmControl() {
if (mIs24HourView) {
mAmPmSpinner.setVisibility(View.GONE); // 24小时制隐藏
} else {
int index = mIsAm ? Calendar.AM : Calendar.PM; // AM=0, PM=1
mAmPmSpinner.setValue(index); // 设置AM/PM选择器值
mAmPmSpinner.setVisibility(View.VISIBLE); // 12小时制显示
}
}
/**
* 更新小时控件范围
* 根据12/24小时制设置不同的数值范围
*/
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);
}
}
// ======================= 回调管理 =======================
/**
* 设置日期时间变化监听器
* @param callback 监听器实例
*/
public void setOnDateTimeChangedListener(OnDateTimeChangedListener callback) {
mOnDateTimeChangedListener = callback;
}
/**
* 触发日期时间变化回调
* 当日期时间发生变化时调用
*/
private void onDateTimeChanged() {
if (mOnDateTimeChangedListener != null) {
mOnDateTimeChangedListener.onDateTimeChanged(this, getCurrentYear(),
getCurrentMonth(), getCurrentDay(), getCurrentHourOfDay(),
getCurrentMinute());
}
}
}