前言
喔时间是一种很奇妙的东西,数字亦如是。
在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 + "");
}
}
尾声
再来说两句......
杂谈一下
关于自定义控件这部分其实和自定义View也有一定的共通之处,需要自己结合项目实际需求加以调控,这样的Extract才会变的更有意义。后期会不定期更新自己的学习心得,欢迎大家查漏补缺......