How to set a custom minutes interval in TimePickerDialog in Android
Asked Answered
L

4

7

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.

Leadsman answered 10/10, 2011 at 13:32 Comment(0)
B
15

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.

Baron answered 14/11, 2013 at 5:18 Comment(4)
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..Turbellarian
When I change interval to 15 mins, it works fine. Not sure if there is/are any other similar solution to thisTurbellarian
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 :)Turbellarian
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 viewDemonology
G
13

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
Georgie answered 2/4, 2012 at 6:44 Comment(2)
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?Aleece
Thanks, @Rizwan Sohaib. Please see question in comment above.Aleece
L
1

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

Lusaka answered 10/10, 2011 at 13:40 Comment(1)
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: #2580716Leadsman
T
1
/**
 * 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);
    }
}
Turaco answered 13/10, 2017 at 12:1 Comment(1)
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?Scandinavia

© 2022 - 2024 — McMap. All rights reserved.