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.
xiaomi_notes_reading/src/notes/ui/DateTimePicker.java

604 lines
22 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;
/**
* 自定义日期时间选择器控件
* 提供日期、小时、分钟和AM/PM的选择功能
* 支持12小时制和24小时制两种显示模式
*/
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/PM选择器最小值
private static final int AMPM_SPINNER_MAX_VAL = 1; // AM/PM选择器最大值
// UI组件
private final NumberPicker mDateSpinner; // 日期选择器
private final NumberPicker mHourSpinner; // 小时选择器
private final NumberPicker mMinuteSpinner; // 分钟选择器
private final NumberPicker mAmPmSpinner; // AM/PM选择器
// 数据状态
private Calendar mDate; // 当前选择的日期时间
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK]; // 日期显示文本数组
private boolean mIsAm; // 是否为上午AM
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) {
// 根据新旧值差异调整日期
mDate.add(Calendar.DAY_OF_YEAR, newVal - oldVal);
updateDateControl(); // 更新日期显示
onDateTimeChanged(); // 触发日期时间变化回调
}
};
/**
* 小时变化监听器
* 处理小时选择器的值变化包括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小时制下的特殊处理
// 下午11点->12点需要增加一天
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点->11点需要减少一天
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切换11点<->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小时制下的特殊处理
// 23点->0点需要增加一天
if (oldVal == HOURS_IN_ALL_DAY - 1 && newVal == 0) {
cal.setTimeInMillis(mDate.getTimeInMillis());
cal.add(Calendar.DAY_OF_YEAR, 1);
isDateChanged = true;
}
// 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小时制需要转换为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需要增加1小时0->59需要减少1小时
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(); // 更新日期显示(可能因小时调整而跨天)
// 检查并更新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选择器的值变化调整12小时
*/
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mIsAm = !mIsAm; // 切换AM/PM状态
// 根据AM/PM切换调整12小时
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(); // 触发回调
}
};
/**
* 日期时间变化回调接口
*/
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);
}
/**
* 构造函数 - 使用当前时间初始化
* @param context 上下文环境
*/
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis());
}
/**
* 构造函数 - 使用指定时间初始化
* @param context 上下文环境
* @param date 初始时间(毫秒时间戳)
*/
public DateTimePicker(Context context, long date) {
this(context, date, DateFormat.is24HourFormat(context));
}
/**
* 主构造函数 - 完整初始化
* @param context 上下文环境
* @param date 初始时间(毫秒时间戳)
* @param is24HourView 是否为24小时制显示
*/
public DateTimePicker(Context context, long date, boolean is24HourView) {
super(context);
mDate = Calendar.getInstance(); // 创建日历实例
mInitialising = true; // 标记开始初始化
// 根据当前时间判断初始AM/PM状态
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);
// 初始化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);
// 更新各控件到初始状态
updateDateControl(); // 更新日期显示
updateHourControl(); // 更新小时显示范围
updateAmPmControl(); // 更新AM/PM显示
set24HourView(is24HourView); // 设置时间显示格式
setCurrentDate(date); // 设置初始时间
setEnabled(isEnabled()); // 设置启用状态
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();
// 12小时制转换0点显示为1213-23点转换为1-11
if (hour > HOURS_IN_HALF_DAY) {
return hour - HOURS_IN_HALF_DAY; // 下午时间13-23 -> 1-11
} else {
return hour == 0 ? HOURS_IN_HALF_DAY : hour; // 0点显示为121-11点不变
}
}
}
/**
* 设置当前小时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; // 下午
if (hourOfDay > HOURS_IN_HALF_DAY) {
hourOfDay -= HOURS_IN_HALF_DAY; // 13-23点转换为1-11点
}
} else {
mIsAm = true; // 上午
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小时制显示
* @return true为24小时制false为12小时制
*/
public boolean is24HourView () {
return mIs24HourView;
}
/**
* 设置时间显示格式
* @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天的日期范围共7天
*/
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); // 清空显示值
// 生成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小时制隐藏AM/PM
} else {
int index = mIsAm ? Calendar.AM : Calendar.PM; // 计算AM/PM索引
mAmPmSpinner.setValue(index); // 设置AM/PM选择器值
mAmPmSpinner.setVisibility(View.VISIBLE); // 显示AM/PM选择器
}
}
/**
* 更新小时显示控制
* 根据12/24小时制设置小时选择器的数值范围
*/
private void updateHourControl() {
if (mIs24HourView) {
// 24小时制0-23
mHourSpinner.setMinValue(HOUR_SPINNER_MIN_VAL_24_HOUR_VIEW);
mHourSpinner.setMaxValue(HOUR_SPINNER_MAX_VAL_24_HOUR_VIEW);
} else {
// 12小时制1-12
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());
}
}
}