android datepicker 自定义,Android 自定义控件 之 DatePicker与NumberPicker

鄢晔
2023-12-01

前言

喔时间是一种很奇妙的东西,数字亦如是。

在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才会变的更有意义。后期会不定期更新自己的学习心得,欢迎大家查漏补缺......

 类似资料: