cordova-plugin-datepicker 这个cordova 官网的日期选择的插件,里面有几个模式,分别是date 日期选择,time 时间选择,还有datetime 日期时间都选的模式。但是有时候会有这样的需求,只显示选择年和月,或者只显示选择月和日。这时候就需要我们来改造下这个插件,以下下只显示选择年和月为例。
首先,我们去扒下这个官方的插件的代码,我们可以看到,里面在调用日期和时间选择的时候是直接调用系统的DatePickerDialog 和TimePickerDialog 这个对话框,看来下代码,感觉DatePickerDialog这个对话框和我们需要有点像,就是要把DatePickerDialog里面的年的选择或者日的选择隐藏掉就可以。然后在修改下,改变选择器时对应的抬头的变化和返回的值就可以了。大概思路就是这样了。开始搞。
写个CustomerDatePickerDialog 来集成DatePickerDialog,重写当滑轮变化的时候title的显示。
class CustomerDatePickerDialog extends DatePickerDialog {
public CustomerDatePickerDialog(Context context,
OnDateSetListener callBack, int year, int monthOfYear,
int dayOfMonth) {
super(context, callBack, year, monthOfYear, dayOfMonth);
}
@Override
public void onDateChanged(DatePicker view, int year, int month, int day) {
super.onDateChanged(view, year, month, day);
mDialog.setTitle(year+"年"+(month + 1) + "月" );
}
}
显示选择器,然后通过获取界面的内容来获取DatePicker这个组件,我们要改变的年月日的滑轮都在这个里面。
获取DatePicker:
final DateSetListener dateSetListener = new DateSetListener(datePickerPlugin, theme, callbackContext, jsonDate);
mDialog = new CustomerDatePickerDialog(currentCtx, dateSetListener, jsonDate.year,
jsonDate.month, jsonDate.day);
mDialog.show();
界面上有显示这个对话框后,我们就要获取DatePicker这个组件,然后隐藏掉想隐藏的东西。
DatePicker dp = findDatePicker((ViewGroup) mDialog.getWindow().getDecorView());
if (dp != null) {
((ViewGroup)((ViewGroup) dp.getChildAt(0)).getChildAt(0)).getChildAt(2).setVisibility(View.GONE); //隐藏掉日滚轮,一般最后一个的子组件0是年,1是月,2是日
}
从界面中获取DatePicker:
private DatePicker findDatePicker(ViewGroup group) {
if (group != null) {
for (int i = 0, j = group.getChildCount(); i < j; i++) {
View child = group.getChildAt(i);
if (child instanceof DatePicker) {
return (DatePicker) child;
} else if (child instanceof ViewGroup) {
DatePicker result = findDatePicker((ViewGroup) child);
if (result != null)
return result;
}
}
}
return null;
}
完整修改后插件的DatePickerPlugin.java文件如下,调用的时候mode的类型用YearAndMonth 就可以了,用法基本和date的一样:
/**
* @author Bikas Vaibhav (http://bikasv.com) 2013
* Rewrote the plug-in at https://github.com/phonegap/phonegap-plugins/tree/master/Android/DatePicker
* It can now accept `min` and `max` dates for DatePicker.
*
* @author Andre Moraes (https://github.com/andrelsmoraes)
* Refactored code, changed default mode to show date and time dialog.
* Added options `okText`, `cancelText`, `todayText`, `nowText`, `is24Hour`.
*
* @author Diego Silva (https://github.com/diego-silva)
* Added option `titleText`.
*/
package com.plugin.datepicker;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.Random;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.annotation.SuppressLint;
import android.app.DatePickerDialog;
import android.app.DatePickerDialog.OnDateSetListener;
import android.app.TimePickerDialog;
import android.app.TimePickerDialog.OnTimeSetListener;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Build;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.DatePicker;
import android.widget.DatePicker.OnDateChangedListener;
import android.widget.NumberPicker;
import android.widget.TimePicker;
@SuppressLint("NewApi")
public class DatePickerPlugin extends CordovaPlugin {
private static final String ACTION_DATE = "date";
private static final String ACTION_TIME = "time";
private static final String ACTION_MONTH = "YearAndMonth";
private static final String RESULT_ERROR = "error";
private static final String RESULT_CANCEL = "cancel";
private final String pluginName = "DatePickerPlugin";
private CustomerDatePickerDialog mDialog;
// On some devices, onDateSet or onTimeSet are being called twice
private boolean called = false;
private boolean canceled = false;
@Override
public boolean execute(final String action, final JSONArray data, final CallbackContext callbackContext) {
Log.d(pluginName, "DatePicker called with options: " + data);
called = false;
canceled = false;
boolean result = false;
this.show(data, callbackContext);
result = true;
return result;
}
public synchronized void show(final JSONArray data, final CallbackContext callbackContext) {
DatePickerPlugin datePickerPlugin = this;
Context currentCtx = cordova.getActivity();
Runnable runnable;
JsonDate jsonDate = new JsonDate().fromJson(data);
// Retrieve Android theme
JSONObject options = data.optJSONObject(0);
int theme = options.optInt("androidTheme", 1);
if (ACTION_TIME.equalsIgnoreCase(jsonDate.action)) {
runnable = runnableTimeDialog(datePickerPlugin, theme, currentCtx,
callbackContext, jsonDate, Calendar.getInstance(TimeZone.getDefault()));
} else if(ACTION_MONTH.equalsIgnoreCase(jsonDate.action)){ //2016/12/5 添加只选择年和月
runnable = runnableDatePicker(datePickerPlugin, theme, currentCtx, callbackContext, jsonDate);
}else {
runnable = runnableDatePicker(datePickerPlugin, theme, currentCtx, callbackContext, jsonDate);
}
cordova.getActivity().runOnUiThread(runnable);
}
private TimePicker timePicker;
private int timePickerHour = 0;
private int timePickerMinute = 0;
private Runnable runnableTimeDialog(final DatePickerPlugin datePickerPlugin,
final int theme, final Context currentCtx, final CallbackContext callbackContext,
final JsonDate jsonDate, final Calendar calendarDate) {
return new Runnable() {
@Override
public void run() {
final TimeSetListener timeSetListener = new TimeSetListener(datePickerPlugin, callbackContext, calendarDate);
final TimePickerDialog timeDialog = new TimePickerDialog(currentCtx, theme, timeSetListener, jsonDate.hour,
jsonDate.minutes, jsonDate.is24Hour) {
public void onTimeChanged(TimePicker view, int hourOfDay, int minute) {
timePicker = view;
timePickerHour = hourOfDay;
timePickerMinute = minute;
}
};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
timeDialog.setCancelable(true);
timeDialog.setCanceledOnTouchOutside(false);
if (!jsonDate.titleText.isEmpty()){
timeDialog.setTitle(jsonDate.titleText);
}
if (!jsonDate.nowText.isEmpty()){
timeDialog.setButton(DialogInterface.BUTTON_NEUTRAL, jsonDate.nowText, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (timePicker != null) {
Calendar now = Calendar.getInstance();
timeSetListener.onTimeSet(timePicker, now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE));
}
}
});
}
String labelCancel = jsonDate.cancelText.isEmpty() ? currentCtx.getString(android.R.string.cancel) : jsonDate.cancelText;
timeDialog.setButton(DialogInterface.BUTTON_NEGATIVE, labelCancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
canceled = true;
callbackContext.error(RESULT_CANCEL);
}
});
String labelOk = jsonDate.okText.isEmpty() ? currentCtx.getString(android.R.string.ok) : jsonDate.okText;
timeDialog.setButton(DialogInterface.BUTTON_POSITIVE, labelOk, timeDialog);
}
timeDialog.show();
timeDialog.updateTime(new Random().nextInt(23), new Random().nextInt(59));
timeDialog.updateTime(jsonDate.hour, jsonDate.minutes);
}
};
}
private Runnable runnableDatePicker(
final DatePickerPlugin datePickerPlugin,
final int theme, final Context currentCtx,
final CallbackContext callbackContext, final JsonDate jsonDate) {
return new Runnable() {
@Override
public void run() {
if(ACTION_MONTH.equalsIgnoreCase(jsonDate.action)){ //只选择年月
final DateSetListener dateSetListener = new DateSetListener(datePickerPlugin, theme, callbackContext, jsonDate);
mDialog = new CustomerDatePickerDialog(currentCtx, dateSetListener, jsonDate.year,
jsonDate.month, jsonDate.day);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
prepareDialog(mDialog, dateSetListener, callbackContext, currentCtx, jsonDate);
}
else {
prepareDialogPreHoneycomb(mDialog, callbackContext, currentCtx, jsonDate);
}
mDialog.show();
DatePicker dp = findDatePicker((ViewGroup) mDialog.getWindow().getDecorView());
if (dp != null) {
((ViewGroup)((ViewGroup) dp.getChildAt(0)).getChildAt(0)).getChildAt(2).setVisibility(View.GONE); //隐藏掉日滚轮
}
}else{
final DateSetListener dateSetListener = new DateSetListener(datePickerPlugin, theme, callbackContext, jsonDate);
final DatePickerDialog dateDialog = new DatePickerDialog(currentCtx, theme, dateSetListener, jsonDate.year,
jsonDate.month, jsonDate.day);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
prepareDialog(dateDialog, dateSetListener, callbackContext, currentCtx, jsonDate);
}
else {
prepareDialogPreHoneycomb(dateDialog, callbackContext, currentCtx, jsonDate);
}
dateDialog.show();
}
}
};
}
private void prepareDialog(final DatePickerDialog dateDialog, final OnDateSetListener dateListener,
final CallbackContext callbackContext, Context currentCtx, JsonDate jsonDate) {
dateDialog.setCancelable(true);
dateDialog.setCanceledOnTouchOutside(false);
if (!jsonDate.titleText.isEmpty()){
dateDialog.setTitle(jsonDate.titleText);
}
if (!jsonDate.todayText.isEmpty()){
dateDialog.setButton(DialogInterface.BUTTON_NEUTRAL, jsonDate.todayText, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Calendar now = Calendar.getInstance();
DatePicker datePicker = dateDialog.getDatePicker();
dateListener.onDateSet(datePicker, now.get(Calendar.YEAR), now.get(Calendar.MONTH), now.get(Calendar.DAY_OF_MONTH));
}
});
}
String labelCancel = jsonDate.cancelText.isEmpty() ? currentCtx.getString(android.R.string.cancel) : jsonDate.cancelText;
dateDialog.setButton(DialogInterface.BUTTON_NEGATIVE, labelCancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
canceled = true;
callbackContext.error(RESULT_CANCEL);
}
});
String labelOk = jsonDate.okText.isEmpty() ? currentCtx.getString(android.R.string.ok) : jsonDate.okText;
dateDialog.setButton(DialogInterface.BUTTON_POSITIVE, labelOk, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
DatePicker datePicker = dateDialog.getDatePicker();
datePicker.clearFocus();
dateListener.onDateSet(datePicker, datePicker.getYear(), datePicker.getMonth(), datePicker.getDayOfMonth());
}
});
DatePicker dp = dateDialog.getDatePicker();
if(jsonDate.minDate > 0) {
dp.setMinDate(jsonDate.minDate);
}
if(jsonDate.maxDate > 0 && jsonDate.maxDate > jsonDate.minDate) {
dp.setMaxDate(jsonDate.maxDate);
}
}
private void prepareDialogPreHoneycomb(DatePickerDialog dateDialog,
final CallbackContext callbackContext, Context currentCtx, final JsonDate jsonDate){
java.lang.reflect.Field mDatePickerField = null;
try {
mDatePickerField = dateDialog.getClass().getDeclaredField("mDatePicker");
} catch (NoSuchFieldException e) {
callbackContext.error(RESULT_ERROR);
}
mDatePickerField.setAccessible(true);
DatePicker pickerView = null;
try {
pickerView = (DatePicker) mDatePickerField.get(dateDialog);
} catch (IllegalArgumentException e) {
callbackContext.error(RESULT_ERROR);
} catch (IllegalAccessException e) {
callbackContext.error(RESULT_ERROR);
}
final Calendar startDate = Calendar.getInstance();
startDate.setTimeInMillis(jsonDate.minDate);
final Calendar endDate = Calendar.getInstance();
endDate.setTimeInMillis(jsonDate.maxDate);
final int minYear = startDate.get(Calendar.YEAR);
final int minMonth = startDate.get(Calendar.MONTH);
final int minDay = startDate.get(Calendar.DAY_OF_MONTH);
final int maxYear = endDate.get(Calendar.YEAR);
final int maxMonth = endDate.get(Calendar.MONTH);
final int maxDay = endDate.get(Calendar.DAY_OF_MONTH);
if(startDate !=null || endDate != null) {
pickerView.init(jsonDate.year, jsonDate.month, jsonDate.day, new OnDateChangedListener() {
@Override
public void onDateChanged(DatePicker view, int year, int month, int day) {
if(jsonDate.maxDate > 0 && jsonDate.maxDate > jsonDate.minDate) {
if(year > maxYear || month > maxMonth && year == maxYear || day > maxDay && year == maxYear && month == maxMonth){
view.updateDate(maxYear, maxMonth, maxDay);
}
}
if(jsonDate.minDate > 0) {
if(year < minYear || month < minMonth && year == minYear || day < minDay && year == minYear && month == minMonth) {
view.updateDate(minYear, minMonth, minDay);
}
}
}
});
}
}
private final class DateSetListener implements OnDateSetListener {
private JsonDate jsonDate;
private final DatePickerPlugin datePickerPlugin;
private final CallbackContext callbackContext;
private final int theme;
private DateSetListener(DatePickerPlugin datePickerPlugin, int theme, CallbackContext callbackContext, JsonDate jsonDate) {
this.datePickerPlugin = datePickerPlugin;
this.callbackContext = callbackContext;
this.jsonDate = jsonDate;
this.theme = theme;
}
/**
* Return a string containing the date in the format YYYY/MM/DD or call TimeDialog if action != date
*/
@Override
public void onDateSet(final DatePicker view, final int year, final int monthOfYear, final int dayOfMonth) {
if (canceled || called) {
return;
}
called = true;
canceled = false;
Log.d("onDateSet", "called: " + called);
Log.d("onDateSet", "canceled: " + canceled);
Log.d("onDateSet", "mode: " + jsonDate.action);
if (ACTION_DATE.equalsIgnoreCase(jsonDate.action)) {
String returnDate = year + "/" + (monthOfYear + 1) + "/" + dayOfMonth;
Log.d("onDateSet", "returnDate: " + returnDate);
callbackContext.success(returnDate);
}else if(ACTION_MONTH.equalsIgnoreCase(jsonDate.action)){ //只返回年月
String returnDate = year + "/" + (monthOfYear + 1) ;
Log.d("onDateSet", "returnDate: " + returnDate);
callbackContext.success(returnDate);
} else {
// Open time dialog
Calendar selectedDate = Calendar.getInstance();
selectedDate.set(Calendar.YEAR, year);
selectedDate.set(Calendar.MONTH, monthOfYear);
selectedDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
cordova.getActivity().runOnUiThread(runnableTimeDialog(datePickerPlugin, theme, cordova.getActivity(),
callbackContext, jsonDate, selectedDate));
}
}
}
private final class TimeSetListener implements OnTimeSetListener {
private Calendar calendarDate;
private final CallbackContext callbackContext;
private TimeSetListener(DatePickerPlugin datePickerPlugin, CallbackContext callbackContext, Calendar selectedDate) {
this.callbackContext = callbackContext;
this.calendarDate = selectedDate != null ? selectedDate : Calendar.getInstance();
}
/**
* Return the current date with the time modified as it was set in the
* time picker.
*/
@Override
public void onTimeSet(final TimePicker view, final int hourOfDay, final int minute) {
if (canceled) {
return;
}
calendarDate.set(Calendar.HOUR_OF_DAY, hourOfDay);
calendarDate.set(Calendar.MINUTE, minute);
calendarDate.set(Calendar.SECOND, 0);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
String toReturn = sdf.format(calendarDate.getTime());
callbackContext.success(toReturn);
}
}
private final class JsonDate {
private String action = ACTION_DATE;
private String titleText = "";
private String okText = "";
private String cancelText = "";
private String todayText = "";
private String nowText = "";
private long minDate = 0;
private long maxDate = 0;
private int month = 0;
private int day = 0;
private int year = 0;
private int hour = 0;
private int minutes = 0;
private boolean is24Hour = false;
public JsonDate() {
reset(Calendar.getInstance());
}
private void reset(Calendar c) {
year = c.get(Calendar.YEAR);
month = c.get(Calendar.MONTH);
day = c.get(Calendar.DAY_OF_MONTH);
hour = c.get(Calendar.HOUR_OF_DAY);
minutes = c.get(Calendar.MINUTE);
}
public JsonDate fromJson(JSONArray data) {
try {
JSONObject obj = data.getJSONObject(0);
action = isNotEmpty(obj, "mode") ? obj.getString("mode")
: ACTION_DATE;
minDate = isNotEmpty(obj, "minDate") ? obj.getLong("minDate") : 0l;
maxDate = isNotEmpty(obj, "maxDate") ? obj.getLong("maxDate") : 0l;
titleText = isNotEmpty(obj, "titleText") ? obj.getString("titleText") : "";
okText = isNotEmpty(obj, "okText") ? obj.getString("okText") : "";
cancelText = isNotEmpty(obj, "cancelText") ? obj
.getString("cancelText") : "";
todayText = isNotEmpty(obj, "todayText") ? obj
.getString("todayText") : "";
nowText = isNotEmpty(obj, "nowText") ? obj.getString("nowText")
: "";
is24Hour = isNotEmpty(obj, "is24Hour") ? obj.getBoolean("is24Hour")
: false;
String optionDate = obj.getString("date");
String[] datePart = optionDate.split("/");
month = Integer.parseInt(datePart[0]) - 1;
day = Integer.parseInt(datePart[1]);
year = Integer.parseInt(datePart[2]);
hour = Integer.parseInt(datePart[3]);
minutes = Integer.parseInt(datePart[4]);
} catch (JSONException e) {
reset(Calendar.getInstance());
}
return this;
}
public boolean isNotEmpty(JSONObject object, String key)
throws JSONException {
return object.has(key)
&& !object.isNull(key)
&& object.get(key).toString().length() > 0
&& !JSONObject.NULL.toString().equals(
object.get(key).toString());
}
}
//只选择年月情况添加代码//
/**
* 从当前Dialog中查找DatePicker子控件
*
* @param group
* @return
*/
private DatePicker findDatePicker(ViewGroup group) {
if (group != null) {
for (int i = 0, j = group.getChildCount(); i < j; i++) {
View child = group.getChildAt(i);
if (child instanceof DatePicker) {
return (DatePicker) child;
} else if (child instanceof ViewGroup) {
DatePicker result = findDatePicker((ViewGroup) child);
if (result != null)
return result;
}
}
}
return null;
}
class CustomerDatePickerDialog extends DatePickerDialog {
public CustomerDatePickerDialog(Context context,
OnDateSetListener callBack, int year, int monthOfYear,
int dayOfMonth) {
super(context, callBack, year, monthOfYear, dayOfMonth);
}
@Override
public void onDateChanged(DatePicker view, int year, int month, int day) {
super.onDateChanged(view, year, month, day);
mDialog.setTitle(year+"年"+(month + 1) + "月" );
}
}
//只选择年月情况添加代码//
}