時間日期選擇控制元件DateTimePicker
阿新 • • 發佈:2019-01-10
專案中需要使用一個日期時間選擇彈窗。因為系統的日期選擇和時間選擇是分開的,而且顯示效果與要求不一致,因此自定義時間日期彈窗。
思路:這種時間日期選擇控制元件,與系統的有些差別,因此,需要重新設計。不論是年月日,還是時分秒
的單個選擇的控制元件,都是一個數字滾輪的模式。因此基於系統的NumberPicker元件的基礎上進行開發。
1、NumberPicker。系統的數字選擇器沒有開放設定分割線顏色,選擇文字的顏色,文字大小等屬性的
定製,因此採用一個繼承類開放這些屬性設定。
public JDNumberPicker(Context context) { super(context, null); } public JDNumberPicker(Context context, AttributeSet attrs) { super(context, attrs); //檢視初始化 initView(context, attrs); } public JDNumberPicker(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); //檢視初始化 initView(context, attrs); } /** * 檢視初始化 */ private void initView(Context context, AttributeSet attrs) { if(null != attrs) { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.JDNumberPicker, 0, 0); try{ if (a.hasValue(R.styleable.JDNumberPicker_jdSelectionDividerHeight)) { mSelectionDividerHeight = a.getDimensionPixelSize(R.styleable.JDNumberPicker_jdSelectionDividerHeight, UNSCALED_DEFAULT_SELECTION_DIVIDER_HEIGHT); setDividerHeight(mSelectionDividerHeight); } if (a.hasValue(R.styleable.JDNumberPicker_jdSelectionDividersDistance)) { mSelectionDividersDistance = a.getDimensionPixelSize(R.styleable.JDNumberPicker_jdSelectionDividersDistance, UNSCALED_DEFAULT_SELECTION_DIVIDERS_DISTANCE); setDividersDistance(mSelectionDividersDistance); } if (a.hasValue(R.styleable.JDNumberPicker_jdSelectDividersColor)) { mSelectionDividersColor = a.getColor(R.styleable.JDNumberPicker_jdSelectDividersColor, Color.GRAY); setDividerColor(mSelectionDividersColor); } if (a.hasValue(R.styleable.JDNumberPicker_jdSelectTextColor)) { mSelectionTextColor = a.getColor(R.styleable.JDNumberPicker_jdSelectTextColor, Color.WHITE); setSelectionTextColor(mSelectionTextColor); } if (a.hasValue(R.styleable.JDNumberPicker_jdSelectTextSize)) { mSelectionTextSize = a.getDimensionPixelSize(R.styleable.JDNumberPicker_jdSelectTextSize, UNSCALED_DEFAULT_SELECTION_TEXTSIZE); setSelectionTextSize(mSelectionTextSize); } } finally { a.recycle(); } } }
屬性的設定方法,通過反射來實現。
/**
* 設定分割線顏色
*
* @param color : 顏色值
*/
public void setDividerColor(int color) {
try {
Field pf = NumberPicker.class.getDeclaredField("mSelectionDivider");
if(null != pf) {
pf.setAccessible(true);
try {
//設定分割線的顏色值
pf.set(this, new ColorDrawable(color));
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
同理,可以設定分割線的高度,分割線之間的間距等。
2)日期選擇元件。
日期選擇元件可以通過三個數字滾輪選擇元件實現,即三個JDNumberPicker。
/**
* 日期選擇器
*/
public class JDDatePicker extends LinearLayout {
private static final String LOG_TAG = JDDatePicker.class.getSimpleName();
/**
* The callback used to indicate the user changes\d the date.
*/
public interface OnDateChangedListener {
/**
* Called upon a date change.
*
* @param view The view associated with this listener.
* @param year The year that was set.
* @param monthOfYear The month that was set (0-11) for compatibility
* with {@link java.util.Calendar}.
* @param dayOfMonth The day of the month that was set.
*/
void onDateChanged(JDDatePicker view, int year, int monthOfYear, int dayOfMonth);
}
private static final int DEFAULT_START_YEAR = 1900;
private static final int DEFAULT_END_YEAR = 2100;
/**
* 日期樣式
*/
private static final String DATE_FORMAT = "MM/dd/yyyy";
/**
* 上下文
*/
private Context mContext;
private View root;
private String[] mShortMonths;
private java.text.DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
private int mNumberOfMonths;
private Calendar mTempDate;
private Calendar mMinDate;
private Calendar mMaxDate;
private Calendar mCurrentDate;
/**
* 日期變化監聽
*/
private OnDateChangedListener mOnDateChangedListener;
/**
* 年份選擇器
*/
private JDNumberPicker mYearSpinner;
/**
* 月份選擇器
*/
private JDNumberPicker mMonthSpinner;
/**
* 日選擇器
*/
private JDNumberPicker mDaySpinner;
/**
* 本地時區
*/
private Locale mCurrentLocale;
public JDDatePicker(Context context) {
this(context, null);
}
public JDDatePicker(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
//檢視初始化
initView(context, attrs);
}
/**
* 控制元件初始化
*
* @param context
* @param attrs
*/
private void initView(Context context, AttributeSet attrs) {
// initialization based on locale
setCurrentLocale(Locale.getDefault());
//讀取屬性
int startYear = DEFAULT_START_YEAR;
int endYear = DEFAULT_END_YEAR;
String minDate = "";
String maxDate = "";
if(null != attrs) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.JDDatePicker, 0, 0);
try{
if(a.hasValue(R.styleable.JDDatePicker_jdStartYear)) {
startYear = a.getInt(R.styleable.JDDatePicker_jdStartYear,
DEFAULT_START_YEAR);
}
if(a.hasValue(R.styleable.JDDatePicker_jdEndYear)) {
endYear = a.getInt(R.styleable.JDDatePicker_jdEndYear,
DEFAULT_END_YEAR);
}
if(a.hasValue(R.styleable.JDDatePicker_jdMinDate)) {
minDate = a.getString(R.styleable.JDDatePicker_jdMinDate);
}
if(a.hasValue(R.styleable.JDDatePicker_jdMaxDate)) {
maxDate = a.getString(R.styleable.JDDatePicker_jdMaxDate);
}
} finally {
a.recycle();
}
}
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.layout_date_picker, this,true);
mYearSpinner = (JDNumberPicker) findViewById(R.id.yearPicker);
mMonthSpinner = (JDNumberPicker) findViewById(R.id.monthPicker);
mDaySpinner = (JDNumberPicker) findViewById(R.id.dayPicker);
mYearSpinner.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS);
mMonthSpinner.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS);
mDaySpinner.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS);
JDNumberPicker.OnValueChangeListener onChangeListener = new NumberPicker.OnValueChangeListener() {
public void onValueChange(NumberPicker picker, int oldVal, int newVal) {
mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
// take care of wrapping of days and months to update greater fields
if (picker == mDaySpinner) {
int maxDayOfMonth = mTempDate.getActualMaximum(Calendar.DAY_OF_MONTH);
if (oldVal == maxDayOfMonth && newVal == 1) {
mTempDate.add(Calendar.DAY_OF_MONTH, 1);
} else if (oldVal == 1 && newVal == maxDayOfMonth) {
mTempDate.add(Calendar.DAY_OF_MONTH, -1);
} else {
mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal);
}
} else if (picker == mMonthSpinner) {
if (oldVal == 11 && newVal == 0) {
mTempDate.add(Calendar.MONTH, 1);
} else if (oldVal == 0 && newVal == 11) {
mTempDate.add(Calendar.MONTH, -1);
} else {
mTempDate.add(Calendar.MONTH, newVal - oldVal);
}
} else if (picker == mYearSpinner) {
mTempDate.set(Calendar.YEAR, newVal);
} else {
throw new IllegalArgumentException();
}
// now set the date to the adjusted one
setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH),
mTempDate.get(Calendar.DAY_OF_MONTH));
updateSpinners();
notifyDateChanged();
}
};
mDaySpinner.setFormatter(JDNumberPicker.getTwoDigitFormatter(mDaySpinner));
mDaySpinner.setOnLongPressUpdateInterval(100);
mDaySpinner.setOnValueChangedListener(onChangeListener);
// month
mMonthSpinner.setMinValue(0);
mMonthSpinner.setMaxValue(mNumberOfMonths - 1);
mMonthSpinner.setDisplayedValues(mShortMonths);
mMonthSpinner.setOnLongPressUpdateInterval(200);
mMonthSpinner.setOnValueChangedListener(onChangeListener);
// year
mYearSpinner.setMinValue(startYear);
mYearSpinner.setMaxValue(endYear);
mYearSpinner.setOnLongPressUpdateInterval(100);
mYearSpinner.setOnValueChangedListener(onChangeListener);
// set the min date giving priority of the minDate over startYear
mTempDate.clear();
if (!TextUtils.isEmpty(minDate)) {
if (!parseDate(minDate, mTempDate)) {
mTempDate.set(startYear, 0, 1);
}
} else {
mTempDate.set(startYear, 0, 1);
}
setMinDate(mTempDate.getTimeInMillis());
// set the max date giving priority of the maxDate over endYear
mTempDate.clear();
if (!TextUtils.isEmpty(maxDate)) {
if (!parseDate(maxDate, mTempDate)) {
mTempDate.set(endYear, 11, 31);
}
} else {
mTempDate.set(endYear, 11, 31);
}
setMaxDate(mTempDate.getTimeInMillis());
// initialize to current date
mCurrentDate.setTimeInMillis(System.currentTimeMillis());
init(mCurrentDate.get(Calendar.YEAR), mCurrentDate.get(Calendar.MONTH), mCurrentDate
.get(Calendar.DAY_OF_MONTH), null);
// If not explicitly specified this view is important for accessibility.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
}
}
}
@Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
onPopulateAccessibilityEvent(event);
return true;
}
@Override
public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
super.onPopulateAccessibilityEvent(event);
final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR;
String selectedDateUtterance = DateUtils.formatDateTime(mContext,
mCurrentDate.getTimeInMillis(), flags);
event.getText().add(selectedDateUtterance);
}
@Override
public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(event);
event.setClassName(DatePicker.class.getName());
}
@Override
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(info);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
info.setClassName(JDDatePicker.class.getName());
}
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
setCurrentLocale(newConfig.locale);
}
// Override so we are in complete control of save / restore for this widget.
@Override
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
dispatchThawSelfOnly(container);
}
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
return new SavedState(superState, getYear(), getMonth(), getDayOfMonth());
}
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setDate(ss.mYear, ss.mMonth, ss.mDay);
updateSpinners();
}
/**
* Class for managing state storing/restoring.
*/
private static class SavedState extends BaseSavedState {
private final int mYear;
private final int mMonth;
private final int mDay;
/**
* Constructor called from {@link DatePicker#onSaveInstanceState()}
*/
private SavedState(Parcelable superState, int year, int month, int day) {
super(superState);
mYear = year;
mMonth = month;
mDay = day;
}
/**
* Constructor called from {@link #CREATOR}
*/
private SavedState(Parcel in) {
super(in);
mYear = in.readInt();
mMonth = in.readInt();
mDay = in.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(mYear);
dest.writeInt(mMonth);
dest.writeInt(mDay);
}
@SuppressWarnings("all")
// suppress unused and hiding
public static final Parcelable.Creator<SavedState> CREATOR = new Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
/**
* 設定選中項Text顏色
*
* @param color : 顏色值
*/
public void setSelectionTextColor(int color) {
mYearSpinner.setSelectionTextColor(color);
mMonthSpinner.setSelectionTextColor(color);
mDaySpinner.setSelectionTextColor(color);
}
/**
* 設定選中項分割線高度
*
* @param height : 選中項分割線高度
*/
public void setDividerHeight(int height) {
mYearSpinner.setDividerHeight(height);
mMonthSpinner.setDividerHeight(height);
mDaySpinner.setDividerHeight(height);
}
/**
* 設定分割線顏色
*
* @param color : 顏色值
*/
public void setDividerColor(int color) {
mYearSpinner.setDividerColor(color);
mMonthSpinner.setDividerColor(color);
mDaySpinner.setDividerColor(color);
}
/**
* 設定選項文字大小
*
* @param textSize : 文字大小
*/
public void setSelectionTextSize(int textSize) {
mYearSpinner.setSelectionTextSize(textSize);
mMonthSpinner.setSelectionTextSize(textSize);
mDaySpinner.setSelectionTextSize(textSize);
}
/**
* 設定選中項分割線之間距離
*
* @param distance : 選中項分割線之間距離
*/
public void setDividersDistance(int distance) {
mYearSpinner.setDividersDistance(distance);
mMonthSpinner.setDividersDistance(distance);
mDaySpinner.setDividersDistance(distance);
}
/**
* Updates the current date.
*
* @param year The year.
* @param month The month which is <strong>starting from zero</strong>.
* @param dayOfMonth The day of the month.
*/
public void updateDate(int year, int month, int dayOfMonth) {
if (!isNewDate(year, month, dayOfMonth)) {
return;
}
setDate(year, month, dayOfMonth);
updateSpinners();
notifyDateChanged();
}
/**
* Initialize the state. If the provided values designate an inconsistent
* date the values are normalized before updating the spinners.
*
* @param year The initial year.
* @param monthOfYear The initial month <strong>starting from zero</strong>.
* @param dayOfMonth The initial day of the month.
* @param onDateChangedListener How user is notified date is changed by
* user, can be null.
*/
public void init(int year, int monthOfYear, int dayOfMonth,
OnDateChangedListener onDateChangedListener) {
setDate(year, monthOfYear, dayOfMonth);
updateSpinners();
mOnDateChangedListener = onDateChangedListener;
}
/**
* 獲取當前的日期
*
* @return
*/
public Calendar getSelectionDate() {
return mCurrentDate;
}
/**
* 設定日期選擇監聽
*
* @param onDateChangedListener How user is notified date is changed by
* user, can be null.
*/
public void setOnDateChangedListener(OnDateChangedListener onDateChangedListener) {
this.mOnDateChangedListener = onDateChangedListener;
}
private void updateSpinners() {
// set the spinner ranges respecting the min and max dates
if (mCurrentDate.equals(mMinDate)) {
mDaySpinner.setMinValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
mDaySpinner.setWrapSelectorWheel(false);
mMonthSpinner.setDisplayedValues(null);
mMonthSpinner.setMinValue(mCurrentDate.get(Calendar.MONTH));
mMonthSpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.MONTH));
mMonthSpinner.setWrapSelectorWheel(false);
} else if (mCurrentDate.equals(mMaxDate)) {
mDaySpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.DAY_OF_MONTH));
mDaySpinner.setMaxValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
mDaySpinner.setWrapSelectorWheel(false);
mMonthSpinner.setDisplayedValues(null);
mMonthSpinner.setMinValue(mCurrentDate.getActualMinimum(Calendar.MONTH));
mMonthSpinner.setMaxValue(mCurrentDate.get(Calendar.MONTH));
mMonthSpinner.setWrapSelectorWheel(false);
} else {
mDaySpinner.setMinValue(1);
mDaySpinner.setMaxValue(mCurrentDate.getActualMaximum(Calendar.DAY_OF_MONTH));
mDaySpinner.setWrapSelectorWheel(true);
mMonthSpinner.setDisplayedValues(null);
mMonthSpinner.setMinValue(0);
mMonthSpinner.setMaxValue(11);
mMonthSpinner.setWrapSelectorWheel(true);
}
// make sure the month names are a zero based array
// with the months in the month spinner
String[] displayedValues = Arrays.copyOfRange(mShortMonths,
mMonthSpinner.getMinValue(), mMonthSpinner.getMaxValue() + 1);
mMonthSpinner.setDisplayedValues(displayedValues);
// year spinner range does not change based on the current date
mYearSpinner.setMinValue(mMinDate.get(Calendar.YEAR));
mYearSpinner.setMaxValue(mMaxDate.get(Calendar.YEAR));
mYearSpinner.setWrapSelectorWheel(false);
// set the spinner values
mYearSpinner.setValue(mCurrentDate.get(Calendar.YEAR));
mMonthSpinner.setValue(mCurrentDate.get(Calendar.MONTH));
mDaySpinner.setValue(mCurrentDate.get(Calendar.DAY_OF_MONTH));
}
/**
* @return The selected year.
*/
public int getYear() {
return mCurrentDate.get(Calendar.YEAR);
}
/**
* @return The selected month.
*/
public int getMonth() {
return mCurrentDate.get(Calendar.MONTH);
}
/**
* @return The selected day of month.
*/
public int getDayOfMonth() {
return mCurrentDate.get(Calendar.DAY_OF_MONTH);
}
/**
* Notifies the listener, if such, for a change in the selected date.
*/
private void notifyDateChanged() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED);
if (mOnDateChangedListener != null) {
mOnDateChangedListener.onDateChanged(this, getYear(), getMonth(), getDayOfMonth());
}
}
/**
* Sets the maximal date supported by this {@link DatePicker} in
* milliseconds since January 1, 1970 00:00:00 in
* {@link TimeZone#getDefault()} time zone.
*
* @param maxDate The maximal supported date.
*/
public void setMaxDate(long maxDate) {
mTempDate.setTimeInMillis(maxDate);
if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
&& mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
return;
}
mMaxDate.setTimeInMillis(maxDate);
if (mCurrentDate.after(mMaxDate)) {
mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
}
updateSpinners();
}
/**
* Sets the minimal date supported by this {@link NumberPicker} in
* milliseconds since January 1, 1970 00:00:00 in
* {@link TimeZone#getDefault()} time zone.
*
* @param minDate The minimal supported date.
*/
public void setMinDate(long minDate) {
mTempDate.setTimeInMillis(minDate);
if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
&& mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
return;
}
mMinDate.setTimeInMillis(minDate);
if (mCurrentDate.before(mMinDate)) {
mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
}
updateSpinners();
}
/**
* 設定日期(年月日)
*
* @param year : 年
* @param month : 月
* @param dayOfMonth : 月天數
*/
private void setDate(int year, int month, int dayOfMonth) {
mCurrentDate.set(year, month, dayOfMonth);
if (mCurrentDate.before(mMinDate)) {
mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
} else if (mCurrentDate.after(mMaxDate)) {
mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
}
}
/**
* Parses the given <code>date</code> and in case of success sets the result
* to the <code>outDate</code>.
*
* @return True if the date was parsed.
*/
private boolean parseDate(String date, Calendar outDate) {
try {
outDate.setTime(mDateFormat.parse(date));
return true;
} catch (ParseException e) {
Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
return false;
}
}
/**
* 是否是一個新的日期
*
* @param year : 年
* @param month : 月
* @param dayOfMonth : 月天數
* @return
*/
private boolean isNewDate(int year, int month, int dayOfMonth) {
return (mCurrentDate.get(Calendar.YEAR) != year
|| mCurrentDate.get(Calendar.MONTH) != dayOfMonth
|| mCurrentDate.get(Calendar.DAY_OF_MONTH) != month);
}
/**
* Sets the current locale.
*
* @param locale The current locale.
*/
private void setCurrentLocale(Locale locale) {
if (locale.equals(mCurrentLocale)) {
return;
}
mCurrentLocale = locale;
mTempDate = getCalendarForLocale(mTempDate, locale);
mMinDate = getCalendarForLocale(mMinDate, locale);
mMaxDate = getCalendarForLocale(mMaxDate, locale);
mCurrentDate = getCalendarForLocale(mCurrentDate, locale);
mNumberOfMonths = mTempDate.getActualMaximum(Calendar.MONTH) + 1;
mShortMonths = new DateFormatSymbols().getShortMonths();
if (usingNumericMonths()) {
// We're in a locale where a date should either be all-numeric, or all-text.
// All-text would require custom NumberPicker formatters for day and year.
mShortMonths = new String[mNumberOfMonths];
for (int i = 0; i < mNumberOfMonths; ++i) {
mShortMonths[i] = String.format("%d", i + 1);
}
}
}
/**
* Tests whether the current locale is one where there are no real month names,
* such as Chinese, Japanese, or Korean locales.
*/
private boolean usingNumericMonths() {
return Character.isDigit(mShortMonths[Calendar.JANUARY].charAt(0));
}
/**
* Gets a calendar for locale bootstrapped with the value of a given calendar.
*
* @param oldCalendar The old calendar.
* @param locale The locale.
*/
private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
if (oldCalendar == null) {
return Calendar.getInstance(locale);
} else {
final long currentTimeMillis = oldCalendar.getTimeInMillis();
Calendar newCalendar = Calendar.getInstance(locale);
newCalendar.setTimeInMillis(currentTimeMillis);
return newCalendar;
}
}
}
其中,最重要的是根據年份和月份的變化,日期選擇器要想贏得調整最大值,例如閏年2月份有29天,大月份(例如1月,3月等)有31天。這個聯動變化在控制元件的初始化JDNumberPicker.OnValueChangeListener onChangeListener = new NumberPicker.OnValueChangeListener()進行實現的。
init:初始化方法設定初始化的日期和選擇監聽。
updateDate: 更新日期
3)時間選擇器,同樣是三個JDNumberPicker組成。採用24小時的模式計時。
4)時間日期選擇器。通過AlertDialog,顯示時間日期選擇。參考JDDateTimePickerDialogUtil。
原專案程式碼: