前言
喔时间是一种很奇妙的东西,数字亦如是。
在Android开发中,肯定会有一些需求是针对于选择器的处理,甚至会有一些Limit的处理需求,重复的复用、重写相关的Picker,然后在需求变化时再重写一个......这是一件很Disgusting的事情。于是,就自己想办法抽出一个公共的Util吧。
这里针对于DatePicker和NumberPicker结合了AlertDialog自定义了该控件,先看一下其继承结构:
java.lang.Object
↳ android.view.View
↳ android.view.ViewGroup
↳ android.widget.FrameLayout
↳ android.widget.TimePicker/DatePicker/TimePicker
由上可知,都是继承自FrameLayout,会有一种层层覆盖的感觉。
自定义:DateTimePickDialogUtil
先来看看源码中给出的points:
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(DatePicker view, int year, int monthOfYear, int dayOfMonth);
}
public DatePicker(Context context) {
this(context, null);
}
public DatePicker(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.datePickerStyle);
}
public DatePicker(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
由源码可知,调用DatePicker( )的构造方法传入的参数需要有Context的对象,可选的有关于属性和自定义样式的参数。需要注意的是其提供的回调方法:即选择时的每次滚动都会回调这个方法,这和NumberPicker一致。此外,由@param可知,其月份是0-11,所以我们在使用定义月份时需要+1;下面来看看具体的实现步骤:
-
** String,Date,Calender格式的解析。**
一般情况下,我们都是先传一个后台返回的String格式的日期,这里我们需要先将其转为Date型的值进行操作,主要是使用了DateFormat进行操作:【dateFormat.format( )是将Date转String,dateFormat.parse( )是将String转Date】private Date mInitDate; // 转换的初始化Date日期 private String mInitDateTime; // 传入的初始化String日期 // TODO...... DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); try { // 直接调用get...()方法时间会出现错误 mInitDate = dateFormat.parse(mInitDateTime); } catch (ParseException e) { e.printStackTrace(); }
这里可们就可以将parse后的Date型变量进行操作了,需要注意的是mInitDate.getTime( )之类的方法不建议直接处理,因为会有一些需要转换的问题,这里我们可以使用Calender进行操作:
Calendar calendar = Calendar.getInstance();
calendar.setTime(mInitDate); // set Calendar
** DatePicker + AlertDialog进行设置**
下一步,就是将datePicker进行初始化了,即初始化时传入参数:
datePicker.init(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH), this);
// 设置不可编辑
datePicker.setDescendantFocusability(DatePicker.FOCUS_BLOCK_DESCENDANTS);
针对于其init( )方法,源码中定义如下,其中包含了所需回调的onDateChangedListener:
public void init(int year, int monthOfYear, int dayOfMonth, OnDateChangedListener onDateChangedListener);
由于项目需求,这里有个很奇怪的需求,就是对日期的上限/下限加了限制,其中需要使用getTimeInMillis( )方法将其转为long型变量,然后传入setMin( ),setMax( )中:
// 设置min日期为初始日期
long minDate = calendar.getTimeInMillis();
datePicker.setMinDate(minDate);
// 设置max日期为7天后
calendar.add(Calendar.DAY_OF_YEAR, 7);
long maxDate = calendar.getTimeInMillis();
datePicker.setMaxDate(maxDate);
初始化完DatePicker后,便可将其放在AlertDialog上,通过dialog的回调方法将结果返回即可。当然,不要忘了对刚初始化完成的DatePicker添加回调:
// 初始化后自动添加onDateChanged监听
onDateChanged(null, 0, 0, 0);-
** 附上源码**
public class DateTimePickDialogUtil implements OnDateChangedListener, OnTimeChangedListener {private Activity mActivity; private DatePicker mDatePicker; private Date mInitDate; private Date mChooseDate; private String mDateTime; private String mInitDateTime; private String mResultDate; private OnDateTimePickDialogListener mListener; public DateTimePickDialogUtil(Activity activity, String initDateTime) { this.mActivity = activity; this.mInitDateTime = initDateTime; } public interface OnDateTimePickDialogListener { void onDateTimePickDialog(String mResultDate); } public void setOnDateTimePickDialogListener(OnDateTimePickDialogListener listener) { this.mListener = listener; } public void initDate(DatePicker datePicker) { DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); try { mInitDate = dateFormat.parse(mInitDateTime); // 直接调用get...()方法时间会出现错误 } catch (ParseException e) { e.printStackTrace(); } Calendar calendar = Calendar.getInstance(); calendar.setTime(mInitDate); // 设置初始化日期为当前日期 datePicker.init(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH), this); Logger.e("initDate", calendar.get(Calendar.YEAR) + "," + (calendar.get(Calendar.MONTH) + 1) + "," + calendar.get(Calendar.DAY_OF_MONTH)); // 设置不可编辑 datePicker.setDescendantFocusability(DatePicker.FOCUS_BLOCK_DESCENDANTS); // 设置min日期为初始日期 long minDate = calendar.getTimeInMillis(); datePicker.setMinDate(minDate); // 设置max日期为7天后 calendar.add(Calendar.DAY_OF_YEAR, 7); long maxDate = calendar.getTimeInMillis(); datePicker.setMaxDate(maxDate); } public AlertDialog dateTimePicKerDialog() { LinearLayout dateTimeLayout = (LinearLayout) mActivity.getLayoutInflater().inflate(R.layout.dialog_datetimepicker, null); mDatePicker = (DatePicker) dateTimeLayout.findViewById(R.id.dp_datepicker); initDate(mDatePicker); AlertDialog alertDialog = new AlertDialog.Builder(mActivity) .setTitle("选择时间") .setView(dateTimeLayout) .setPositiveButton("确定", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); try { mChooseDate = dateFormat.parse(mDateTime); if (mInitDate.getTime() > mChooseDate.getTime()) { UiUtil.toast("开始时间需在当前时间之后!"); } else if (mChooseDate.getTime() - mInitDate.getTime() > 7000 * 60 * 60 * 24) { UiUtil.toast("开始时间需在当前时间的7天之内!"); } else { // TODO 返回选择的时间 mResultDate = mDatePicker.getTag(R.id.date_picker_dialog).toString(); Logger.e("resultData", mResultDate); if (mListener == null) return; mListener.onDateTimePickDialog(mResultDate); } } catch (Exception e) { e.printStackTrace(); } } }) .setNegativeButton("取消", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { } }).show(); // 初始化后自动添加onDateChanged监听 onDateChanged(null, 0, 0, 0); return alertDialog; } @Override public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth) { // 获得日历实例 Calendar calendar = Calendar.getInstance(); calendar.set(mDatePicker.getYear(), mDatePicker.getMonth(), mDatePicker.getDayOfMonth()); SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); mDateTime = simpleDateFormat.format(calendar.getTime()); mDatePicker.setTag(R.id.date_picker_dialog, mDateTime); Logger.e("dateTime", mDateTime); } @Override public void onTimeChanged(TimePicker view, int hourOfDay, int minute) { onDateChanged(null, 0, 0, 0); } }
-
实例中的调用
使用时,调用起来很方便,即实现该接口,通过构造方法将需要初始化的日期字符串传入,再通过回调接口进行设置即可。
private String mInitStartDateTime; // 初始化开始时间// TODO...... private void picker() { DateTimePickDialogUtil dateTimePickDialogUtil = new DateTimePickDialogUtil(this, mInitStartDateTime); dateTimePickDialogUtil.setOnDateTimePickDialogListener(this); dateTimePickDialogUtil.dateTimePicKerDialog(); } @Override public void onDateTimePickDialog(String mResultDate) { // TODO 自定义的设置 }
自定义:NumberPickerUtil
同理,先来看看源码中给出的points:
public interface OnValueChangeListener {
void onValueChange(NumberPicker picker, int oldVal, int newVal);
}
public interface OnScrollListener {
public void onScrollStateChange(NumberPicker view, int scrollState);
}
public NumberPicker(Context context) {
this(context, null);
}
public NumberPicker(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.numberPickerStyle);
}
public NumberPicker(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO......
}
总体的实现方法与DatePickerDialogUtil大体一致,通过构造方法与回调接口的方式引入引出,有一个细节的地方可以注意一下,就是对最大最小值以及初始值的设置:
this.mPicker.setMinValue(minValue);
this.mPicker.setMaxValue(maxValue);
// 此处有坑!setValue需在min和max之后!
this.mPicker.setValue(initValue);
-
** 实现的接口**
@Override
public void onClick(View v) {
Logger.e("pickerValue click:", mPicker.getValue() + "");
this.mDialog.dismiss();
this.mListener.onNumberPickerClick(mType, mPicker.getValue());
}@Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { Logger.e("pickerValue change:", newVal + ""); }
-
** 实例中的调用**
NumberPickerUtil numberPickerUtil = new NumberPickerUtil();
numberPickerUtil.setOnNumberPickerClickListener(this);
numberPickerUtil.numberPicker(this, "选择金额", "元", mMin, mMax, mInit, 1);@Override public void onNumberPickerClick(int type, int mPickerValue) { // TODO }
-
** 附上源码**
相信看完了之前的DatePickerDialogUtil后,对这里的思路也会很清晰,这里就不再多说了,需要注意的是这里加上了保存的状态,即对pickerValue的处理,效果就是第二次的点击会将第一次的值赋为初始值,下面给出代码:
public class NumberPickerUtil implements View.OnClickListener, NumberPicker.OnValueChangeListener {private Dialog mDialog; private Button mButton; private TextView mTextView; private NumberPicker mPicker; private OnNumberPickerClickListener mListener; private int mType; public interface OnNumberPickerClickListener { void onNumberPickerClick(int type, int pickerValue); } public void setOnNumberPickerClickListener(OnNumberPickerClickListener listener) { this.mListener = listener; } public void numberPicker(Context context, String title, String tips, int minValue, int maxValue, int initValue, int type) { this.mType = type; this.mDialog = new Dialog(context); this.mDialog.setContentView(R.layout.dialog_numberpicker); this.mDialog.setTitle(title); this.mButton = (Button) mDialog.findViewById(R.id.btn_sure); this.mTextView = (TextView) mDialog.findViewById(R.id.tv_numberpickertips); this.mPicker = (NumberPicker) mDialog.findViewById(R.id.np_numberPicker); // 设置不可编辑 this.mPicker.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS); this.mPicker.setMinValue(minValue); this.mPicker.setMaxValue(maxValue); // 此处有坑!setValue需在min和max之后! this.mPicker.setValue(initValue); this.mPicker.setWrapSelectorWheel(false); this.mPicker.setOnValueChangedListener(this); this.mTextView.setText(tips); this.mButton.setOnClickListener(this); this.mDialog.show(); } @Override public void onClick(View v) { Logger.e("pickerValue click:", mPicker.getValue() + ""); this.mDialog.dismiss(); this.mListener.onNumberPickerClick(mType, mPicker.getValue()); } @Override public void onValueChange(NumberPicker picker, int oldVal, int newVal) { Logger.e("pickerValue change:", newVal + ""); } }
尾声
再来说两句......
Github地址:
https://github.com/Ivorfason杂谈一下
关于自定义控件这部分其实和自定义View也有一定的共通之处,需要自己结合项目实际需求加以调控,这样的Extract才会变的更有意义。后期会不定期更新自己的学习心得,欢迎大家查漏补缺......