How to set a custom minutes interval in TimePickerDialog in Android

18,525

Solution 1

Using a combination of this from @Rizwan and this other thread, I came up with a combined solution that allows arbitrary minute increments in a TimePickerDialog. The main issue is that most of the functionality is hidden by the android TimePickerDialog and TimePicker classes and it doesn't appear to allow

  1. Extend TimePickerDialog to allow us easier access
  2. Use reflection to reach inside the display and access the required bits (see below)
  3. rewire the minute 'NumberPicker' to display our values
  4. rewire the TimePicker to receive and return values form the NumberPicker honoring our custom increment.
  5. block onStop() so that it doesn't reset the value on close.

Warning

The main issue with reaching inside the UI is that elements are referenced by ids which are likely to change, and even the name of the id is not guaranteed to be the same forever. Having said that, this is working, stable solution and likely to work for the foreseeable future. In my opinion the empty catch block should warn that the UI has changed and should fall back to the default (increment 1 minute) behaviour.

Solution

    private class DurationTimePickDialog extends TimePickerDialog
    {
        final OnTimeSetListener mCallback;
        TimePicker mTimePicker;
        final int increment;

        public DurationTimePickDialog(Context context, OnTimeSetListener callBack, int hourOfDay, int minute, boolean is24HourView, int increment)
        {
            super(context, callBack, hourOfDay, minute/increment, is24HourView);
            this.mCallback = callBack;
            this.increment = increment;
        }

        @Override
        public void onClick(DialogInterface dialog, int which) {
                if (mCallback != null && mTimePicker!=null) {
                    mTimePicker.clearFocus();
                    mCallback.onTimeSet(mTimePicker, mTimePicker.getCurrentHour(),
                            mTimePicker.getCurrentMinute()*increment);
                }
        }

        @Override
        protected void onStop()
        {
            // override and do nothing
        }

        @Override
        protected void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
            try
            {
                Class<?> rClass = Class.forName("com.android.internal.R$id");
                Field timePicker = rClass.getField("timePicker");
                this.mTimePicker = (TimePicker)findViewById(timePicker.getInt(null));
                Field m = rClass.getField("minute");
                
                NumberPicker mMinuteSpinner = (NumberPicker)mTimePicker.findViewById(m.getInt(null));
                mMinuteSpinner.setMinValue(0);
                mMinuteSpinner.setMaxValue((60/increment)-1);
                List<String> displayedValues = new ArrayList<String>();
                for(int i=0;i<60;i+=increment)
                {
                    displayedValues.add(String.format("%02d", i));
                }
                mMinuteSpinner.setDisplayedValues(displayedValues.toArray(new String[0]));
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }

}

constructor accepts the increment value and retains some other references. Note that this omits error checking and we'd prefer 60%increment==0

onCreate uses the name of the UI fields and reflection to discover the current location. Again this omits error checking and should be 'fail-safe' ie revert to default behaviour if something goes wrong.

onClick overridden to return the correct minute value to the callback listener

onStop overridden to prevent the (incorrect) index value being returned a second time, when the dialog closes. Go on, try it yourself.

Most of this comes from digging into the TimePickerDialog source.

UPDATE FOR ANDROID 11+

The reflection used in the example above was marked as greylist-max-q, meaning it would no longer be possible after Android 10 (API 29 - Q). In the short term (until August 2021) it is possible to drop the target version back to API 29 while still building against Android 11 (API 30 - R) but after this date the Play Store will no longer accept these builds and will require API 30 as the build target. This eventual failure was anticipated and noted in the warning above.

Solution 2

well thats fine if you used time-picker instead of time-picker-dialog. But there is a solution for this actually.. here is what I used to meet the same requirement.. I used CustomTimePickerDialog. Every thing will be same, only TimePickerDialog will change to CustomTimePickerDialog in the code.

CustomTimePickerDialog timePickerDialog = new CustomTimePickerDialog(myActivity.this, timeSetListener, 
            Calendar.getInstance().get(Calendar.HOUR), 
            CustomTimePickerDialog.getRoundedMinute(Calendar.getInstance().get(Calendar.MINUTE) + CustomTimePickerDialog.TIME_PICKER_INTERVAL), 
            false
);
timePickerDialog.setTitle("2. Select Time");
timePickerDialog.show();

Here is my CustomTimePickerDialog class... Just use this class in your project and change TimePickerDialog to CustomTimePickerDialog..

public class CustomTimePickerDialog extends TimePickerDialog{

    public static final int TIME_PICKER_INTERVAL=10;
    private boolean mIgnoreEvent=false;

    public CustomTimePickerDialog(Context context, OnTimeSetListener callBack, int hourOfDay, int minute,
        boolean is24HourView) {
    super(context, callBack, hourOfDay, minute, is24HourView);
    }
/*
 * (non-Javadoc)
 * @see android.app.TimePickerDialog#onTimeChanged(android.widget.TimePicker, int, int)
 * Implements Time Change Interval
 */
    @Override
    public void onTimeChanged(TimePicker timePicker, int hourOfDay, int minute) {
        super.onTimeChanged(timePicker, hourOfDay, minute);
        this.setTitle("2. Select Time");
        if (!mIgnoreEvent){
            minute = getRoundedMinute(minute);
            mIgnoreEvent=true;
            timePicker.setCurrentMinute(minute);
            mIgnoreEvent=false;
        }
    }

    public static int getRoundedMinute(int minute){
         if(minute % TIME_PICKER_INTERVAL != 0){
            int minuteFloor = minute - (minute % TIME_PICKER_INTERVAL);
            minute = minuteFloor + (minute == minuteFloor + 1 ? TIME_PICKER_INTERVAL : 0);
            if (minute == 60)  minute=0;
         }

        return minute;
    }
}

After using this CustomTimePickerDialog class, All you will need to do is use CustomTimePickerDialog instead of TimePickerDialog in your code to access/override default functions of TimePickerDialog class. In the simple way, I mean your timeSetListener will be as following after this...

private CustomTimePickerDialog.OnTimeSetListener timeSetListener = new CustomTimePickerDialog.OnTimeSetListener() {
    @Override
    public void onTimeSet(TimePicker view, int hourOfDay, int minute) {

    }
}// using CustomTimePickerDialog

Solution 3

/**
 * Set TimePicker interval by adding a custom minutes list
 * TIME_PICKER_INTERVAL = Enter your Minutes;
 * @param timePicker
 */
private void setTimePickerInterval(TimePicker timePicker) {
    try {
        int TIME_PICKER_INTERVAL = 10;
        NumberPicker minutePicker = (NumberPicker) timePicker.findViewById(Resources.getSystem().getIdentifier(
                "minute", "id", "android"));
        minutePicker.setMinValue(0);
        minutePicker.setMaxValue((60 / TIME_PICKER_INTERVAL) - 1);
        List<String> displayedValues = new ArrayList<String>();
        for (int i = 0; i < 60; i += TIME_PICKER_INTERVAL) {
            displayedValues.add(String.format("%02d", i));
        }
        minutePicker.setDisplayedValues(displayedValues.toArray(new String[0]));
    } catch (Exception e) {
        Log.e(TAG, "Exception: " + e);
    }
}

Solution 4

You can use a regular AlertDialog and use setView to include a custom TimePicker view.

Share:
18,525

Related videos on Youtube

Joserra
Author by

Joserra

Updated on June 04, 2022

Comments

  • Joserra
    Joserra almost 2 years

    I have got a TimePickerDialog working to set time which is set to a TextView in order to display it. Now, I need help to set that TimePicker (inside the TimePickerDialog) minutues interval to 15 minutes. I have seen there is a post with 15 minutes interval issue related to TimePicker, but I don't know how to apply it to the TimePickerDialog because I don't know how to use the TimePicker that it is created inside the TimePickerDialog. I am new to Android and completely lost in this matter. Thanks in advance.

  • Joserra
    Joserra over 12 years
    I ended up using the time pickers in the activity with no dialog at all.. It war easier. Thanks anyway. If anyone is interested I took info from this post: stackoverflow.com/questions/2580216/…
  • gcl1
    gcl1 about 11 years
    Thanks @Rizwan. This solution works, successfully constraining the selectable times to 15 minute increments, for example. However, the adjacent minute values, displayed above and below the current minute value, are still in one minute increments, rather than 15 minute increments. For example, a selected minute value of 30 has 29 on one side and 31 on the other, rather than 15 on one side and 45 on the other. This causes a somewhat distracting user experience. The solution would be better if the adjacent values were also in 15 minute increments. Do you have a solution for this?
  • gcl1
    gcl1 about 11 years
    Thanks, @Rizwan Sohaib. Please see question in comment above.
  • Lee Yi Hong
    Lee Yi Hong over 9 years
    This solution works but there is a change in the hours when we change the minutes. Currently I have the same code. The increment is 30 minutes. When the original time is 20:30 and we scroll to 00 at the minutes, it will change to 21:00. And if the original time is 15:00 and we scroll to minutes to 30, it will return 14:00. Not sure if anyone have the same problem..
  • Lee Yi Hong
    Lee Yi Hong over 9 years
    When I change interval to 15 mins, it works fine. Not sure if there is/are any other similar solution to this
  • Lee Yi Hong
    Lee Yi Hong over 9 years
    Ok, I manage to solve the 30 minutes interval by adding a onTimeChanged check with last save hours and minutes. If both are different, it will only change the minutes instead of both of them by using view.setCurrentHour(lastSavedHour). But I am not sure if this is a good way of doing it... But if there are better solution do feel free to let me know :)
  • luca992
    luca992 over 8 years
    This doesn't seem to work in android 5: Field m = rClass.getField("minute"); NumberPicker mMinuteSpinner = (NumberPicker)mTimePicker.findViewById(m.getInt(null)); doesn't find the view
  • Manikandan
    Manikandan almost 4 years
    Your answer set the interval, but inside setOnTimeChangedListener, when i scroll the minutes, it still gives me increment of 1 min. Any suggestion on this?