Creating custom DatePicker dialog

2019-03-03 11:50发布

问题:

Requirement: When the user clicks on the TextView, a date picker should open up. The default date selected should be the date in the TextView. If the date is in the past, the DatePicker dialog's 'Set' button should be disabled. If the clickable TextView is empty, the default date in the DatePicker should be today's date.

回答1:

This is a scenario I've already solved and am sharing here in order to help the Xamarin community. The code isn't very optimized, just FYI.

So, what we exactly need in this scenario is access to the event that the user is changing dates on the DatePicker Dialog. This can only be done if you use a DatePicker inside your own Dialog for more control. In my opinion, you cannot get access to this event if you use the default DatePickerDialog. Thus, we create a dialog extending the DialogFragment class and then implement the DatePicker inside of it. When the user clicks the TextView, we use show the fragment. Let's begin:

Here's the MainActivity:

using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using Java.Util;
using Java.Text;


namespace DatePickerTest
{
    [Activity(Label = "DatePickerTest", MainLauncher = true, Icon = "@drawable/icon", Theme = "@android:style/Theme.Holo.Light")]
    public class MainActivity : Activity
    {
        private string dueDate;

        private TextView dateLabel;
        private DateTime date;

        protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);

            // Set our view from the "main" layout resource
            SetContentView(Resource.Layout.Main);

            dateLabel = (TextView)FindViewById(Resource.Id.dateLabel);

            dueDate = dateLabel.Text;

            dateLabel.Click += delegate { ShowDialog(); };

        }

        public void ShowDialog()
        {
            var transaction = FragmentManager.BeginTransaction();
            var dialogFragment = new mDialogFragment();
            dialogFragment.Show(transaction, "dialog_fragment");
        }

        //Used for communication with the fragment
        public string GetDueDate()
        {
            return dueDate;
        }

        //Used for communication with the fragment
        public void SetDueDate(DateTime date)
        {
         //Additional check so that date isn't set in the past
            if (date < DateTime.Now.Date)
                Toast.MakeText(this, "Something went wrong! Please try again", ToastLength.Long).Show();
            else
            {
                SimpleDateFormat MdyFormat = new SimpleDateFormat("MM/dd/yyyy");
                dueDate = MdyFormat.Format(Date.Parse(date.ToString()));
                dateLabel.Text = dueDate;
            }
        }

    }
}

Main.axml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">
    <Button
        android:id="@+id/MyButton"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/Hello" />
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:background="#FAFAFA"
        android:layout_height="wrap_content">
        <TextView
            android:id="@+id/dueDateLabel"
            android:layout_height="45dp"
            android:layout_width="wrap_content"
            android:text="Due Date:"
            android:padding="15dp"
            android:textColor="#2E2E2E" />
        <TextView
            android:id="@+id/dateLabel"
            android:layout_height="45dp"
            android:layout_width="fill_parent"
            android:hint="Some Date"
            android:textColor="#2E2E2E"
            android:text="03/16/2015" />
    </LinearLayout>
</LinearLayout>

MDialogFragment.cs :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Util;
using Android.Views;
using Android.Widget;
using Java.Util;
using Java.Text;

namespace DatePickerTest
{
    public class mDialogFragment : DialogFragment
    {
        DatePicker picker;
        private MainActivity MActivity;
        private int Year, Month, Day;
        private string DueDate;
        private DateTime SelectedDueDate;
        private string tempString = "";

        public override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);

            //Get the instance of the MainActivity
            MActivity = (MainActivity) this.Activity;

            //Get the currently set due date
            DueDate = MActivity.GetDueDate();

            //Get instance of the Calendar
            Calendar Today = Calendar.Instance;

            //Update the class variables
            Year = Today.Get(Calendar.Year);
            Month = Today.Get(Calendar.Month);
            Day = Today.Get(Calendar.Date);
        }

        public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
        {
            //Inflating the dialog layout
            var view = inflater.Inflate(Resource.Layout.MDialogLayout, container, false);
            //Finding all the views in it:
            var cancel = (Button)view.FindViewById(Resource.Id.cancel);
            var set = (Button)view.FindViewById(Resource.Id.set);
            picker = (DatePicker)view.FindViewById(Resource.Id.pickerdate);

            //DatePicker flag to make it look like the default DatePicker
            picker.CalendarViewShown = false;

            //Checking to see if current date is in the past, if YES, disable the 'Set' button
            if ((DateTime.Parse(DueDate) < DateTime.Now)) { set.Enabled = false; }

            //Initate the picker with the current due date OR today's date
            picker.Init(GetDefaultYear(), GetDefaultMonth(), GetDefaultDayOfMonth(), new onDateChangedListener((picker1, year, month, day) =>
            {
                //Getting the DatePicker value in a string
                tempString = (month + 1) + "/" + day + "/" + year;

                //Parsing the value into a variable
                SelectedDueDate = (DateTime.Parse(tempString).Date);

                //Setting the MDatePicker dialog's Title
                Dialog.SetTitle(GetDateDetails(SelectedDueDate));

                //Enable/Disalbe 'Set' button depending on the condition
                if (SelectedDueDate >= DateTime.Now.Date)
                    set.Enabled = true;
                else
                    set.Enabled = false;

            }));

            //Setting Dialog Title for the first time when it opens
            Dialog.SetTitle(GetDateDetails(DateTime.Parse(DueDate)));

            //Click function for Cancel button
            cancel.Click += delegate{Dismiss();};

            //Click function for Set button
            set.Click += (object sender, EventArgs e) =>
            {
                SetSelectedDueDate(sender, e);
            };
            return view;
        }

        private string GetDateDetails(DateTime date)
        {
            string DateDetails;
            Calendar cal = Calendar.Instance;
            SimpleDateFormat DayOfWeekFormat = new SimpleDateFormat("EEE");
            SimpleDateFormat MonthFormat = new SimpleDateFormat("MMM");

            DateDetails = DayOfWeekFormat.Format(Date.Parse(date.ToString())) + ", " + date.Day + " " + MonthFormat.Format(Date.Parse(date.ToString())) + " " + date.Year;

            return DateDetails;
        }

        private void SetSelectedDueDate(object sender, EventArgs e)
        {
            MActivity.SetDueDate(SelectedDueDate);
            Dismiss();
        }

        private int GetDefaultMonth()
        {
            //The currently set due date is in the format "MM/DD/YYYY"
            if(MActivity.GetDueDate()==null || MActivity.GetDueDate() == "")
                return Month;

            return Convert.ToInt32(MActivity.GetDueDate().Substring(0, 2)) - 1;


        }

        private int GetDefaultDayOfMonth()
        {
            if (MActivity.GetDueDate() == null || MActivity.GetDueDate() == "")
                return Day;
            return Convert.ToInt32(MActivity.GetDueDate().Substring(3, 2)); 
        }

        private int GetDefaultYear()
        {
            if (MActivity.GetDueDate() == null || MActivity.GetDueDate() == "")
                return Year;
            return Convert.ToInt32(MActivity.GetDueDate().Substring(6, 4));
        }
    }

    //We need this class and interface implementation to create and Init the DatePicker
    class onDateChangedListener : Java.Lang.Object, DatePicker.IOnDateChangedListener
    {

        Action<DatePicker, int, int, int> callback;

        public onDateChangedListener(Action<DatePicker, int, int, int> callback)
        {
            this.callback = callback;
        }

        public void OnDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth)
        {
            callback(view, year, monthOfYear, dayOfMonth);
        }
    }
}

MDialogLayout.axml :

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content">

    <DatePicker
        android:id="@+id/pickerdate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal" />
    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">
        <Button
            android:id="@+id/cancel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Cancel"
            android:layout_weight="1"
            style="?android:attr/buttonBarButtonStyle" />
        <Button
            android:id="@+id/set"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="SET"
            android:layout_weight="1"
            style="?android:attr/buttonBarButtonStyle"
            android:paddingTop="1dp" />
    </LinearLayout>
</LinearLayout>