Android 自定义控件 之 DatePicker与NumberPicker

前言

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容