美文网首页Oh My AndroidAndroid 精华Android 干货
Android 自定义控件 之 DatePicker与Numbe

Android 自定义控件 之 DatePicker与Numbe

作者: Ivor0057 | 来源:发表于2016-04-25 18:53 被阅读4381次

    前言

    喔时间是一种很奇妙的东西,数字亦如是。
      在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才会变的更有意义。后期会不定期更新自己的学习心得,欢迎大家查漏补缺......

    相关文章

      网友评论

      • 叨码:datePicker能控制显示的行数么?比如我想显示3行 怎么控制?
      • coco猫:最好能给个图
        Ivor0057:@coco猫 好的,以后会让图文并茂起来~ :stuck_out_tongue_closed_eyes:
      • 79d7fe0fb071:为什么不给个图呢
        Ivor0057:@HTML5_ 好的,以后会多给点截图的 :blush:

      本文标题:Android 自定义控件 之 DatePicker与Numbe

      本文链接:https://www.haomeiwen.com/subject/nmzfrttx.html