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

321 lines
17 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;
// DateTimePicker类继承自FrameLayout它是一个自定义的视图组件用于展示日期和时间选择的交互界面
// 支持24小时制和12小时制含AM/PM标识用户可以通过该组件方便地选择具体的日期和时间并能响应相应的变化事件。
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;
// 日期选择器NumberPicker的最小值通常对应一周的第一天索引从0开始
private static final int DATE_SPINNER_MIN_VAL = 0;
// 日期选择器NumberPicker的最大值对应一周的最后一天一周共7天索引从0到6
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的最小值对应AM通常用0表示
private static final int AMPM_SPINNER_MIN_VAL = 0;
// AM/PM选择器NumberPicker的最大值对应PM通常用1表示
private static final int AMPM_SPINNER_MAX_VAL = 1;
// 用于显示日期的NumberPicker组件用户可通过它选择具体的日期以周为范围展示
private final NumberPicker mDateSpinner;
// 用于显示小时的NumberPicker组件根据设置的视图模式24小时制或12小时制展示不同范围的小时值
private final NumberPicker mHourSpinner;
// 用于显示分钟的NumberPicker组件可选择0到59分钟
private final NumberPicker mMinuteSpinner;
// 用于显示AM/PM的NumberPicker组件仅在12小时制下可见且用于切换上午或下午标识
private final NumberPicker mAmPmSpinner;
// 用于存储和操作当前选择的日期和时间信息基于Calendar类实现方便进行日期和时间的计算与设置
private Calendar mDate;
// 用于存储一周内各天要展示给用户的显示值(例如格式化后的"MM.dd EEEE"格式的字符串)
private String[] mDateDisplayValues = new String[DAYS_IN_ALL_WEEK];
// 用于标识当前时间是上午true还是下午false在12小时制下使用
private boolean mIsAm;
// 用于标识当前是否处于24小时制视图模式true表示24小时制false表示12小时制
private boolean mIs24HourView;
// 用于存储组件当前的启用状态,初始化为默认启用状态
private boolean mIsEnabled = DEFAULT_ENABLE_STATE;
// 用于标记是否处于初始化阶段,在初始化过程中某些逻辑可能会有不同处理,避免不必要的重复操作等
private boolean mInitialising;
// 定义一个接口类型的成员变量,用于设置当日期和时间发生变化时的回调监听器,外部类可以实现该接口来响应变化事件
private OnDateTimeChangedListener mOnDateTimeChangedListener;
// 内部类实现的日期选择器NumberPicker的值变化监听器当用户在日期选择器上选择不同日期时触发
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();
}
};
// 内部类实现的小时选择器NumberPicker的值变化监听器当用户在小时选择器上选择不同小时时触发
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;
}
// 如果是上午11时与下午12时中午之间切换还需要更新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时切换到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小时制转换情况设置Calendar对象中的小时信息
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));
}
}
};
// 内部类实现的分钟选择器NumberPicker的值变化监听器当用户在分钟选择器上选择不同分钟时触发
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;
}
// 反之从最小分钟值切换到最大分钟值小时需要减少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();
// 根据新的小时值判断是否切换AM/PM标识并更新相关显示控制仅在12小时制下相关
if (newHour >= HOURS_IN_HALF_DAY) {
mIsAm = false;
updateAmPmControl();
} else {
mIsAm = true;
updateAmPmControl();
}
}
mDate.set(Calendar.MINUTE, newVal);
onDateTimeChanged();
}
};
// 内部类实现的AM/PM选择器NumberPicker的值变化监听器当用户在AM/PM选择器上切换时触发
private NumberPicker.OnValueChangeListener mOnAmPmChangedListener = new NumberPicker.OnValueChangeListener() {
@Override
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mIsAm =!mIsAm;
// 根据切换后的AM/PM标识相应地调整Calendar对象中的小时信息增加或减少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);
}
// 构造函数使用当前系统时间作为默认时间初始化DateTimePicker组件默认根据系统设置判断是否采用24小时制视图
public DateTimePicker(Context context) {
this(context, System.currentTimeMillis());
}
// 构造函数使用指定的时间以毫秒为单位的时间戳初始化DateTimePicker组件默认根据系统设置判断是否采用24小时制视图
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/PM标识仅在12小时制下相关
mIsAm = getCurrentHourOfDay() >= HOURS_IN_HALF_DAY;
// 加载布局文件到该组件中布局文件中应该包含了各个NumberPicker组件等用于展示和交互的视图元素
inflate(context, R.layout.datetime_picker, this);
// 获取布局文件中定义的日期选择器NumberPicker组件并进行相关初始化设置
mDateSpinner = (NumberPicker) findViewById(R.id.date);
mDateSpinner.setMinValue(DATE_SPINNER_MIN_VAL);
mDateSpinner.setMaxValue(DATE_SPINNER_MAX_VAL);
mDateSpinner.setOnValueChangedListener(mOnDateChangedListener);
// 获取小时选择器NumberPicker组件并设置其值变化监听器
mHourSpinner = (NumberPicker) findViewById(R.id.hour);
mHourSpinner.setOnValueChangedListener(mOnHourChangedListener);
// 获取分钟选择器NumberPicker组件设置其最小值、最大值以及长按更新间隔等属性并设置值变化监听器
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的NumberPicker组件设置其最小值、最大值并设置显示的字符串值从DateFormatSymbols获取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();
// 根据传入的参数设置是否采用24小时制视图模式并相应更新控件显示
set24HourView(is24HourView);
// 设置当前日期时间为传入的指定时间(如果有传入的话),或者保持默认的当前时间
setCurrentDate(date);
// 设置组件的初始启用状态
setEnabled(isEnabled());
// 初始化完成将初始化标记设置为false后续操作不再按照初始化阶段处理
mInitialising = false;
}
// 重写父类的setEnabled方法用于设置组件及其包含的各个子控件NumberPicker组件的启用状态同时更新内部的启用状态记录变量
@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;
}
// 重写父类的isEnabled方法返回组件当前的启用状态
@Override
public boolean isEnabled() {
return mIsEnabled;
}
/**
* 获取当前日期对应的时间戳以毫秒为单位通过Calendar对象获取其内部存储的时间信息并返回
*
* @return the current date in millis当前日期的毫秒时间戳
*/
public long getCurrentDateInTimeMillis() {
return mDate.getTimeInMillis();
}
/**
* 设置当前日期传入以毫秒为单位的时间戳内部会解析并设置对应的年、月、日、时、分等信息到Calendar对象中
*
* @param date The current date in millis要设置的日期的毫秒时间戳
*/
public void setCurrentDate(long date) {
Calendar cal = Calendar.getInstance();
cal.setTimeInMillis(date);
setCurrentDate(cal.get();
}
}
}