Thursday, May 15, 2014

Creating Custom Date and Time Picker in Android

I'm developing an App where I need to input the Date and the Time on a single field. What I want is first the user will press an EditText or any other element and then a Dialog box will be shown where the standard Android Date Picker and Time Picker are laid out in tabs.

Date Picker

Time Picker
 After researching for a while over the internet, I found nothing similar on what I want to implement in my App. I wrote this library so I can reuse it whenever I need a Date and Time Picker.

What we want to create is a DialogFragment which is actually a Dialog wrapped in a Fragment. Inside this fragment, we will be creating an AlertDialog using the AlertDialog.Builder. After creating the AlertDialog, we would now inflate a view and attach it to the AlertDialog. This view contains the TabHost and the contents DatePicker and TimePicker.

I wrote this library in Android Studio Preview (0.5.8) so doing it in Eclipse will be different and I'm not sure how to create it there.

The first thing we you need is to have the Android Support Repository and Android Support Library installed in your system. This can be installed using the Android SDK Manager.


After making sure you have installed this library, we can now start creating a library in Android Studio. Create a New Project and make sure to tick the options "Mark this project as library" and the Support Mode "Fragments". Remove the "Create activity" option.


We first create a DateTime class that will be used to manipulate Date object into different date formats and vice versa. This class can accept a Date object and can return a Date String that can be formatted or it can accept a Date String and convert it to a Date object. The class can also return the Year, Month, Day, Hour, Minute and Second.
package com.pagilagan.lib.datetimepicker.datetimepicker;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

/**
 * Created by Arman.Pagilagan on 15/05/14.
 */
public class DateTime {

    public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
    private Date mDate;
    private Calendar mCalendar;

    /**
     * Constructor with no parameters which will create DateTime
     * Object with the current date and time
     */
    public DateTime() {

        this(new Date());

    }

    /**
     * Constructor with Date parameter which will initialize the
     * object to the date specified
     * @param date
     */
    public DateTime(Date date) {

        mDate = date;
        mCalendar = Calendar.getInstance();
        mCalendar.setTime(mDate);

    }

    /**
     * Constructor with DateFormat and DateString which will be
     * used to initialize the object to the date string specified
     * @param dateFormat String DateFormat
     * @param dateString Date in String
     */
    public DateTime(String dateFormat, String dateString) {

        mCalendar = Calendar.getInstance();
        SimpleDateFormat mFormat = new SimpleDateFormat(dateFormat);

        try {
            mDate = mFormat.parse(dateString);
            mCalendar.setTime(mDate);
        } catch (ParseException e) {
            e.printStackTrace();
        }

    }

    /**
     * Constructor with DateString formatted as default DateFormat
     * defined in DATE_FORMAT variable
     * @param dateString
     */
    public DateTime(String dateString) {

        this(DATE_FORMAT, dateString);

    }

    /**
     * Constructor with Year, Month, Day, Hour, Minute, and Second
     * which will be use to set the date to
     * @param year Year
     * @param monthOfYear Month of Year
     * @param dayOfMonth Day of Month
     * @param hourOfDay Hour of Day
     * @param minuteOfHour Minute
     * @param secondOfMinute Second
     */
    public DateTime(int year, int monthOfYear, int dayOfMonth,
                    int hourOfDay, int minuteOfHour, int secondOfMinute) {

        mCalendar = Calendar.getInstance();
        mCalendar.set(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, secondOfMinute);
        mDate = mCalendar.getTime();

    }

    /**
     * Constructor with Year, Month Day, Hour, Minute which
     * will be use to set the date to
     * @param year Year
     * @param monthOfYear Month of Year
     * @param dayOfMonth Day of Month
     * @param hourOfDay Hour of Day
     * @param minuteOfHour Minute
     */
    public DateTime(int year, int monthOfYear, int dayOfMonth,
                    int hourOfDay, int minuteOfHour) {

        this(year, monthOfYear, dayOfMonth, hourOfDay, minuteOfHour, 0);

    }

    /**
     * Constructor with Date only (no time)
     * @param year Year
     * @param monthOfYear Month of Year
     * @param dayOfMonth Day of Month
     */
    public DateTime(int year, int monthOfYear, int dayOfMonth) {

        this(year, monthOfYear, dayOfMonth, 0,0,0);

    }

    public Date getDate() {
        return mDate;
    }

    public Calendar getCalendar() {
        return mCalendar;
    }

    public String getDateString(String dateFormat) {

        SimpleDateFormat mFormat = new SimpleDateFormat(dateFormat);
        return mFormat.format(mDate);

    }

    public String getDateString() {

        return getDateString(DATE_FORMAT);

    }

    public int getYear() {

        return mCalendar.get(Calendar.YEAR);

    }

    public int getMonthOfYear() {

        return mCalendar.get(Calendar.MONTH);

    }

    public int getDayOfMonth() {

        return mCalendar.get(Calendar.DAY_OF_MONTH);

    }

    public int getHourOfDay() {

        return mCalendar.get(Calendar.HOUR_OF_DAY);

    }

    public int getMinuteOfHour() {

        return mCalendar.get(Calendar.MINUTE);

    }

    public int getSecondOfMinute() {

        return mCalendar.get(Calendar.SECOND);

    }

}

Create the layout date_time_picker.xml which will contain the view that will be attached to the AlertDialog. This contains the TabHost, its TabWidgets, as well as the contents of the tab: DatePicker and TimePicker widgets.

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/date_time"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <!-- TabHost that will hold the Tabs and its content -->
    <TabHost
        android:id="@+id/tab_host"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <!-- TabWidget that will hold the Tabs -->
            <TabWidget
                android:id="@android:id/tabs"
                android:layout_width="match_parent"
                android:layout_height="match_parent" />

            <!-- Layout that will hold the content of the Tabs -->
            <FrameLayout
                android:id="@android:id/tabcontent"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <!-- Layout for the DatePicker -->
                <LinearLayout
                    android:id="@+id/date_content"
                    android:orientation="vertical"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent">

                    <DatePicker
                        android:id="@+id/date_picker"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:calendarViewShown="false" />

                </LinearLayout>

                <!-- Layout for the TimePicker -->
                <LinearLayout
                    android:id="@+id/time_content"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:orientation="vertical">

                    <TimePicker
                        android:id="@+id/time_picker"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent" />

                </LinearLayout>

            </FrameLayout>

        </LinearLayout>

    </TabHost>

</LinearLayout>

Then we create the class called DateTimePicker which is the DialogFragment that will be called when showing the DateTimePicker.

package com.pagilagan.lib.datetimepicker.datetimepicker;  
 import android.app.Activity;  
 import android.app.AlertDialog;  
 import android.app.Dialog;  
 import android.content.Context;  
 import android.content.DialogInterface;  
 import android.os.Bundle;  
 import android.support.v4.app.DialogFragment;  
 import android.view.LayoutInflater;  
 import android.view.View;  
 import android.widget.DatePicker;  
 import android.widget.TabHost;  
 import android.widget.TimePicker;  
 import java.util.Date;  
 /**  
  * Created by Arman.Pagilagan on 15/05/14.  
  */  
 public class DateTimePicker extends DialogFragment {  
   public static final String TAG_FRAG_DATE_TIME = "fragDateTime";  
   private static final String KEY_DIALOG_TITLE = "dialogTitle";  
   private static final String KEY_INIT_DATE = "initDate";  
   private static final String TAG_DATE = "date";  
   private static final String TAG_TIME = "time";  
   private Context mContext;  
   private ButtonClickListener mButtonClickListener;  
   private OnDateTimeSetListener mOnDateTimeSetListener;  
   private Bundle mArgument;  
   private DatePicker mDatePicker;  
   private TimePicker mTimePicker;  
   // DialogFragment constructor must be empty  
   public DateTimePicker() {  
   }  
   @Override  
   public void onAttach(Activity activity) {  
     super.onAttach(activity);  
     mContext = activity;  
     mButtonClickListener = new ButtonClickListener();  
   }  
   /**  
    *  
    * @param dialogTitle Title of the DateTimePicker DialogFragment  
    * @param initDate Initial Date and Time set to the Date and Time Picker  
    * @return Instance of the DateTimePicker DialogFragment  
    */  
   public static DateTimePicker newInstance(CharSequence dialogTitle, Date initDate) {  
     // Create a new instance of DateTimePicker  
     DateTimePicker mDateTimePicker = new DateTimePicker();  
     // Setup the constructor parameters as arguments  
     Bundle mBundle = new Bundle();  
     mBundle.putCharSequence(KEY_DIALOG_TITLE, dialogTitle);  
     mBundle.putSerializable(KEY_INIT_DATE, initDate);  
     mDateTimePicker.setArguments(mBundle);  
     // Return instance with arguments  
     return mDateTimePicker;  
   }  
   @Override  
   public Dialog onCreateDialog(Bundle savedInstanceState) {  
     // Retrieve Argument passed to the constructor  
     mArgument = getArguments();  
     // Use an AlertDialog Builder to initially create the Dialog  
     AlertDialog.Builder mBuilder = new AlertDialog.Builder(mContext);  
     // Setup the Dialog  
     mBuilder.setTitle(mArgument.getCharSequence(KEY_DIALOG_TITLE));  
     mBuilder.setNegativeButton(android.R.string.no,mButtonClickListener);  
     mBuilder.setPositiveButton(android.R.string.yes,mButtonClickListener);  
     // Create the Alert Dialog  
     AlertDialog mDialog = mBuilder.create();  
     // Set the View to the Dialog  
     mDialog.setView(  
         createDateTimeView(mDialog.getLayoutInflater())  
     );  
     // Return the Dialog created  
     return mDialog;  
   }  
   /**  
    * Inflates the XML Layout and setups the tabs  
    * @param layoutInflater Layout inflater from the Dialog  
    * @return Returns a view that will be set to the Dialog  
    */  
   private View createDateTimeView(LayoutInflater layoutInflater) {  
     // Inflate the XML Layout using the inflater from the created Dialog  
     View mView = layoutInflater.inflate(R.layout.date_time_picker,null);  
     // Extract the TabHost  
     TabHost mTabHost = (TabHost) mView.findViewById(R.id.tab_host);  
     mTabHost.setup();  
     // Create Date Tab and add to TabHost  
     TabHost.TabSpec mDateTab = mTabHost.newTabSpec(TAG_DATE);  
     mDateTab.setIndicator(getString(R.string.tab_date));  
     mDateTab.setContent(R.id.date_content);  
     mTabHost.addTab(mDateTab);  
     // Create Time Tab and add to TabHost  
     TabHost.TabSpec mTimeTab = mTabHost.newTabSpec(TAG_TIME);  
     mTimeTab.setIndicator(getString(R.string.tab_time));  
     mTimeTab.setContent(R.id.time_content);  
     mTabHost.addTab(mTimeTab);  
     // Retrieve Date from Arguments sent to the Dialog  
     DateTime mDateTime = new DateTime((Date) mArgument.getSerializable(KEY_INIT_DATE));  
     // Initialize Date and Time Pickers  
     mDatePicker = (DatePicker) mView.findViewById(R.id.date_picker);  
     mTimePicker = (TimePicker) mView.findViewById(R.id.time_picker);  
     mDatePicker.init(mDateTime.getYear(), mDateTime.getMonthOfYear(),  
         mDateTime.getDayOfMonth(), null);  
     mTimePicker.setCurrentHour(mDateTime.getHourOfDay());  
     mTimePicker.setCurrentMinute(mDateTime.getMinuteOfHour());  
     // Return created view  
     return mView;  
   }  
   /**  
    * Sets the OnDateTimeSetListener interface  
    * @param onDateTimeSetListener Interface that is used to send the Date and Time  
    *               to the calling object  
    */  
   public void setOnDateTimeSetListener(OnDateTimeSetListener onDateTimeSetListener) {  
     mOnDateTimeSetListener = onDateTimeSetListener;  
   }  
   private class ButtonClickListener implements DialogInterface.OnClickListener {  
     @Override  
     public void onClick(DialogInterface dialogInterface, int result) {  
       // Determine if the user selected Ok  
       if(DialogInterface.BUTTON_POSITIVE == result) {  
         DateTime mDateTime = new DateTime(  
             mDatePicker.getYear(),  
             mDatePicker.getMonth(),  
             mDatePicker.getDayOfMonth(),  
             mTimePicker.getCurrentHour(),  
             mTimePicker.getCurrentMinute()  
         );  
         mOnDateTimeSetListener.DateTimeSet(mDateTime.getDate());  
       }  
     }  
   }  
   /**  
    * Interface for sending the Date and Time to the calling object  
    */  
   public interface OnDateTimeSetListener {  
     public void DateTimeSet(Date date);  
   }  
 }  

Lastly, we will create a wrapper class called SimpleDateTimePicker that will call the DateTimePicker and also handle the Fragment maintenance.

package com.pagilagan.lib.datetimepicker.datetimepicker;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;

import java.util.Date;

/**
 * Created by Arman.Pagilagan on 16/05/14.
 */
public class SimpleDateTimePicker {

    private CharSequence mDialogTitle;
    private Date mInitDate;
    private DateTimePicker.OnDateTimeSetListener mOnDateTimeSetListener;
    private FragmentManager mFragmentManager;

    /**
     * Private constructor that can only be access using the make method
     * @param dialogTitle Title of the Date Time Picker Dialog
     * @param initDate Date object to use to set the initial Date and Time
     * @param onDateTimeSetListener OnDateTimeSetListener interface
     * @param fragmentManager Fragment Manager from the activity
     */
    private SimpleDateTimePicker(CharSequence dialogTitle, Date initDate,
                                 DateTimePicker.OnDateTimeSetListener onDateTimeSetListener,
                                 FragmentManager fragmentManager) {

        // Find if there are any DialogFragments from the FragmentManager
        FragmentTransaction mFragmentTransaction = fragmentManager.beginTransaction();
        Fragment mDateTimeDialogFrag = fragmentManager.findFragmentByTag(
                DateTimePicker.TAG_FRAG_DATE_TIME
        );

        // Remove it if found
        if(mDateTimeDialogFrag != null) {
            mFragmentTransaction.remove(mDateTimeDialogFrag);
        }
        mFragmentTransaction.addToBackStack(null);

        mDialogTitle = dialogTitle;
        mInitDate = initDate;
        mOnDateTimeSetListener = onDateTimeSetListener;
        mFragmentManager = fragmentManager;

    }

    /**
     * Creates a new instance of the SimpleDateTimePicker
     * @param dialogTitle Title of the Date Time Picker Dialog
     * @param initDate Date object to use to set the initial Date and Time
     * @param onDateTimeSetListener OnDateTimeSetListener interface
     * @param fragmentManager Fragment Manager from the activity
     * @return Returns a SimpleDateTimePicker object
     */
    public static SimpleDateTimePicker make(CharSequence dialogTitle, Date initDate,
                       DateTimePicker.OnDateTimeSetListener onDateTimeSetListener,
                       FragmentManager fragmentManager) {

        return new SimpleDateTimePicker(dialogTitle, initDate,
                onDateTimeSetListener, fragmentManager);

    }

    /**
     * Shows the DateTimePicker Dialog
     */
    public void show() {

        // Create new DateTimePicker
        DateTimePicker mDateTimePicker = DateTimePicker.newInstance(mDialogTitle,mInitDate);
        mDateTimePicker.setOnDateTimeSetListener(mOnDateTimeSetListener);
        mDateTimePicker.show(mFragmentManager, DateTimePicker.TAG_FRAG_DATE_TIME);

    }
}


Since this is a library project, we need to create another module to test this library. Create a new module with an Activity:

File > New Module


Make sure to select the correct minimum required SDK correctly, tick the Create Activity and Fragments for the support:


Continue creating a Blank Activity. It will create a new module on the same project.


We then need to set the dependency of this new module to the library we have created. Go to File > Project Structure

On the left side, find the new module that you have created, click on the Dependencies tab, and then on the right side, click on the "+" button and select Module dependency. Select the DateTimePicker module library that we have created.


On our Blank Acitivty, we can now call the DateTimePicker

package com.pagilagan.lib.datepickertest;

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.util.Log;

import com.pagilagan.lib.datetimepicker.datetimepicker.DateTime;
import com.pagilagan.lib.datetimepicker.datetimepicker.DateTimePicker;
import com.pagilagan.lib.datetimepicker.datetimepicker.SimpleDateTimePicker;

import java.util.Date;

public class MainActivity extends ActionBarActivity implements DateTimePicker.OnDateTimeSetListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Create a SimpleDateTimePicker and Show it
        SimpleDateTimePicker simpleDateTimePicker = SimpleDateTimePicker.make(
                "Set Date & Time Title",
                new Date(),
                this,
                getSupportFragmentManager()
        );
        // Show It baby!
        simpleDateTimePicker.show();

        // Or we can chain it to simplify
        SimpleDateTimePicker.make(
                "Set Date & Time Title",
                new Date(),
                this,
                getSupportFragmentManager()
        ).show();
    }

    @Override
    public void DateTimeSet(Date date) {

        // This is the DateTime class we created earlier to handle the conversion
        // of Date to String Format of Date String Format to Date object
        DateTime mDateTime = new DateTime(date);
        // Show in the LOGCAT the selected Date and Time
        Log.v("TEST_TAG","Date and Time selected: " + mDateTime.getDateString());

    }
}

You have to implement the interface OnDateTimeSetListener in the calling activity as this will be called by the DateTimePicker if the date and/or time was changed.

In Logcat, you should see that this interface method is called with the date and time:


On the next blog, I will show you how you can use this library to other Project using Gradle.

No comments:

Post a Comment