未分类

Android实战记(三)——实现弹出日期选择对话框

这次同学提出了一个问题,他没搞懂怎么利用DatePicker实现弹出窗口选择日期的逻辑。于是我看了看官方文档,发现官方对使用Picker专门写了一篇指南。有指南当然是最好了。这篇指南里还有弹出窗口选择时间的例子,跟选择日期的例子很相近,此处不赘述了。

之前已经学习过使用AlertDialog,而此次的DatePickerDialog就继承于AlertDialog,本来感觉会与其有很多相似之处,但其实差距还是挺大的。因为指南中推荐使用DialogFragment来容纳(?原文为host,不知道该怎么翻译)DatePickerDialogTimePickerDialog,因为

DialogFragment能帮你管理对话框的生命周期,并且允许你在不同的布局配置中显示Picker,如在手机中的基本对话框中或者是在大屏幕里作为布局的一部分嵌入。(说白了就是说Fragment的好处嘛)

然而我看《第一行代码》的时候以为碎片不重要就跳过了。。。没想到现在就要还上一点了。还就还吧~

配置DialogFragment

前面已经说过DatePickerDialog要在DialogFragment中容纳,自然要创建一个DialogFragment

  • 创建DialogFragment的子Fragment,实现DatePickerDialog.OnDateSetListener接口
  • 覆盖onCreateDialog(Bundle)方法,因为要选择日期,所以要给一个默认值:
    • 使用Calendar.getInstance()获取一个表示当前时间的Calendar对象
    • 调用calendar.get(int)取出对应的年、月、日
    • return new DatePickerDialog(getActivity(), listener, year, month, day)来创建DatePickerDialog(listener是上面实现的接口的实例,当然是this,也就是这个Dialog。year/month/day是用来设置初始值的)
  • 实现OnDateSetListener接口的onDateSet()方法,此处是时间设定完成后调用,理所当然地要在这里传回设置好的数据。查阅了很多资料,对于FragmentActivity传递数据都推荐使用实现接口+回调方法来实现,因为DialogFragment提供了onAttach(Context)方法来获取父Activity,不用白不用。这里简单解释一下何为回调,简单地说就是
    • A持有对B的引用
    • 在A中使用B
    • B中也持有对A的引用
    • 使用完B后在B中回去使用A。
  • 最后一步就称为回调。此例中,父Activity持有对DialogFragment的引用,然后在Activity中创建DialogFragment,获取了用户设置的日期后再回调父Activity将日期返回。
    • 定义传回数据用的接口i.e.SendDate和方法
  • 因为要回调Activity的方法,肯定要持有Activity对象引用,且要在DialogFragment的生命周期结束时将引用释放,免得Activity回收时出现错误。
    • onAttach()方法中获取父Activity并判断是否实现SendDate接口
    • onDetach()方法中释放对付Activity的引用
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      public class DatePickerFragment extends DialogFragment implements DatePickerDialog.OnDateSetListener {
      interface SendDate{
      public void sendDate(int year, int month, int day);
      }
      private SendDate listener;
      @Override
      public Dialog onCreateDialog(Bundle savedInstanceState) {
      // Use the current date as the default date in the picker
      final Calendar c = Calendar.getInstance();
      int year = c.get(Calendar.YEAR);
      int month = c.get(Calendar.MONTH);
      int day = c.get(Calendar.DAY_OF_MONTH);
      // Create a new instance of DatePickerDialog and return it
      return new DatePickerDialog(getActivity(), this, year, month, day);
      }

      public void onDateSet(DatePicker view, int year, int month, int day) {
      // Do something with the date chosen by the user
      listener.sendDate(year, month, day);
      }

      @Override
      public void onAttach(Context context) {
      super.onAttach(context);
      if (context instanceof SendDate){
      listener = (SendDate)context;
      }else{
      throw new IllegalArgumentException("Activity must implement SendDate");
      }
      }

      @Override
      public void onDetach() {
      super.onDetach();
      listener = null;
      }
      }

  • 此处再简单介绍一下DialogFragment的生命周期:
    Fragment Lifecycle
    • onAttach()中传入要attach的Activity实例,使得DialogFragment中可以调用Activity中的方法
    • onCreate()中可以对对话框的样式进行设置
    • onCreateDialog()中设置对对话框的监听
    • onCreateView()中初始化View,并对SavedInstanceState进行解析
    • onPause():用户离开片段的第一个信号。通常应该在此方法内确认在当前用户会话结束后仍然有效的任何更改(因为用户可能不会返回)

      其他知识点:

      • instanceof运算符用以判断某对象是否为某类的实例。

配置MainActivity

  • 首先需要一个显示读取到的日期的控件,此处选择最简单的TextView。此处不能忘了初始化Text。为了方便测试起见,需要给DatePickerDialog设置一个触发器,这里选择按钮+点击监听。
  • 在点击按钮以后,需要做的事是:
    • 新建一个上面自己写的DialogFragment子类对象;
    • 构建DatePickerDialog并显示;
    • 实现上面写的回调接口。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      public class MainActivity extends FragmentActivity implements DatePickerFragment.SendDate{
      private TextView dateText;
      private Button requireDate;
      private DatePickerFragment fragment = new DatePickerFragment();
      private int year;
      private int month;
      private int day;
      @Override
      protected void onCreate(Bundle savedInstanceState) {

      final Calendar calendar = Calendar.getInstance();
      year = calendar.get(Calendar.YEAR);
      month = calendar.get(Calendar.MONTH)+1;
      day = calendar.get(Calendar.DAY_OF_MONTH);
      dateText.setText(String.format("%d-%d-%d", year, month, day));
      requireDate.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
      fragment.show(getSupportFragmentManager(), "dataPicker");
      }
      });
      }

      public void setDate(int year, int month, int day){
      this.year = year;
      this.month = month+1;
      this.day = day;
      dateText.setText(String.format("%d-%d-%d", this.year, this.month, this.day));
      }

      @Override
      public void sendDate(int year, int month, int day) {
      setDate(year, month, day);
      }
      }

其他知识点:

  • calendar.get(Calendar.MONTH)方法取得的月份是从0开始的。如果不用一些格式化手段直接用int显示的话,需要+1。(在初始化DatePickerDialog的时候并没有+1,但是月份是正常的,推测是用了其他手段,看了源码没有头绪。。)
  • 如果在自己写的DialogFragment的子类里导入的是android.support.v4.app.DialogFragment的话,在活动中初始化对话框时就要用show(getSupportFragmentManager(), String)方法;如果导入的是android.app.DialogFragment的话,就要用show(getFragmentManager(), String)方法。
分享到