hsz
hsz

Reputation: 152266

TimePicker in PreferenceScreen

I'd like to create a preference field called Interval and I want to be able to popup a TimePicker and set a mm:ss formated value with minimal value 00:30 and step 30 seconds.

Is it possible to use TimePicker in PreferenceScreen ?

Upvotes: 79

Views: 42203

Answers (9)

Matt Koala
Matt Koala

Reputation: 2191

CommonsWare's solution has a few problems, which I fixed:

  • It doesn't update the field properly after it is changed
  • The minutes value only persists a single digit, e.g. 10:2 instead of 10:02
  • If you use PreferenceManager.setDefaultPreferences to set initial default preferences in your app, it won't work because onSetInitialValue needs to persist it
  • The formatting of the result isn't tailored to the user's Locale (e.g. US uses AM/PM)

Here's my code, enjoy.

public class TimePreference extends DialogPreference {
    private int lastHour=0;
    private int lastMinute=0;
    private TimePicker picker=null;

    public static int getHour(String time) {
        String[] pieces=time.split(":");

        return(Integer.parseInt(pieces[0]));
    }

    public static int getMinute(String time) {
        String[] pieces=time.split(":");

        return(Integer.parseInt(pieces[1]));
    }

    public TimePreference(Context ctxt, AttributeSet attrs) {
        super(ctxt, attrs);

        setPositiveButtonText("Set");
        setNegativeButtonText("Cancel");
    }

    @Override
    protected View onCreateDialogView() {
        picker=new TimePicker(getContext());

        return(picker);
    }

    @Override
    protected void onBindDialogView(View v) {
        super.onBindDialogView(v);

        picker.setCurrentHour(lastHour);
        picker.setCurrentMinute(lastMinute);
    }

    @Override
    protected void onDialogClosed(boolean positiveResult) {
        super.onDialogClosed(positiveResult);

        if (positiveResult) {
            lastHour=picker.getCurrentHour();
            lastMinute=picker.getCurrentMinute();

            setSummary(getSummary());

            String lastMinuteString = String.valueOf(lastMinute);
            String time = String.valueOf(lastHour) + ":" + (lastMinuteString.length() == 1 ? "0" + lastMinuteString : lastMinuteString);

            if (callChangeListener(time)) {
                persistString(time);
            }
        }
    }

    @Override
    protected Object onGetDefaultValue(TypedArray a, int index) {
        return(a.getString(index));
    }

    @Override
    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {

        String time;
        String defaultValueStr = (defaultValue != null) ? defaultValue.toString() : "00:00";
        if (restoreValue)
            time = getPersistedString(defaultValueStr);
        else {
            time = defaultValueStr;
            if (shouldPersist())
                persistString(defaultValueStr);
        }

        lastHour=getHour(time);
        lastMinute=getMinute(time);

        setSummary(getSummary());
    }

    @Override
    public CharSequence getSummary() {

        Calendar cal = Calendar.getInstance();
        cal.set(Calendar.HOUR_OF_DAY, lastHour);
        cal.set(Calendar.MINUTE, lastMinute);
        DateFormat sdf = SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT);

        return sdf.format(cal.getTime());
    }

}

Upvotes: 5

0101100101
0101100101

Reputation: 5911

With Android 6, "current hour" and "current minute" are deprecated. Use this to ensure Marshmallow compatibility:

import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TimePicker;

public class TimePreference extends DialogPreference {

private int lastHour;
private int lastMinute;
private TimePicker picker;

public TimePreference(Context ctx, AttributeSet attrs) {
    super(ctx, attrs);
    setPositiveButtonText(ctx.getString(android.R.string.ok));
    setNegativeButtonText(ctx.getString(android.R.string.cancel));
}

@Override
protected View onCreateDialogView() {
    picker = new TimePicker(getContext());
    picker.setIs24HourView(true);
    return picker;
}

@SuppressWarnings("deprecation")
@Override
protected void onBindDialogView(View v) {
    super.onBindDialogView(v);
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
        picker.setCurrentHour(lastHour);
        picker.setCurrentMinute(lastMinute);
    } else {
        picker.setHour(lastHour);
        picker.setMinute(lastMinute);
    }
}

@SuppressWarnings("deprecation")
@Override
protected void onDialogClosed(boolean positiveResult) {
    super.onDialogClosed(positiveResult);
    if (positiveResult) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            lastHour = picker.getCurrentHour();
            lastMinute = picker.getCurrentMinute();
        } else {
            lastHour = picker.getHour();
            lastMinute = picker.getMinute();
        }
        String time = String.valueOf(lastHour) + ":" + String.valueOf(lastMinute);
        if (callChangeListener(time)) {
            persistString(time);
        }
    }
}

@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
    return a.getString(index);
}

@Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
    String time;
    if (restoreValue) {
        if (defaultValue == null) {
            time = getPersistedString("00:00");
        } else {
            time = getPersistedString(defaultValue.toString());
        }
    } else {
        time = defaultValue.toString();
    }
    lastHour = getHour(time);
    lastMinute = getMinute(time);
}

public static int getHour(String time) {
    String[] pieces = time.split(":");
    return Integer.parseInt(pieces[0]);
}

public static int getMinute(String time) {
    String[] pieces = time.split(":");
    return Integer.parseInt(pieces[1]);
}
}

Upvotes: 1

Dalija Prasnikar
Dalija Prasnikar

Reputation: 28512

For Preferences Support Library different code is needed. It requires two custom classes TimePreference and TimePreferenceDialogFragmentCompat, as well as overide of onDisplayPreferenceDialog method in PreferenceFragmentCompat extension class.


TimePreference.java

package com.test;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.v7.preference.DialogPreference;
import android.util.AttributeSet;

public class TimePreference extends DialogPreference
{
    public int hour = 0;
    public int minute = 0;

    public static int parseHour(String value)
    {
        try
        {
            String[] time = value.split(":");
            return (Integer.parseInt(time[0]));
        }
        catch (Exception e)
        {
            return 0;
        }
    }

    public static int parseMinute(String value)
    {
        try
        {
            String[] time = value.split(":");
            return (Integer.parseInt(time[1]));
        }
        catch (Exception e)
        {
            return 0;
        }
    }

    public static String timeToString(int h, int m)
    {
        return String.format("%02d", h) + ":" + String.format("%02d", m);
    }

    public TimePreference(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }

    @Override
    protected Object onGetDefaultValue(TypedArray a, int index)
    {
        return a.getString(index);
    }

    @Override
    protected void onSetInitialValue(boolean restoreValue, Object defaultValue)
    {
        String value;
        if (restoreValue)
        {
            if (defaultValue == null) value = getPersistedString("00:00");
            else value = getPersistedString(defaultValue.toString());
        }
        else
        {
            value = defaultValue.toString();
        }

        hour = parseHour(value);
        minute = parseMinute(value);
    }

    public void persistStringValue(String value)
    {
        persistString(value);
    }
}

TimePreferenceDialogFragmentCompat.java

package com.test;

import android.content.Context;
import android.support.v7.preference.DialogPreference;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceDialogFragmentCompat;
import android.view.View;
import android.widget.TimePicker;

public class TimePreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat implements DialogPreference.TargetFragment
{
    TimePicker timePicker = null;

    @Override
    protected View onCreateDialogView(Context context)
    {
        timePicker = new TimePicker(context);
        return (timePicker);
    }

    @Override
    protected void onBindDialogView(View v)
    {
        super.onBindDialogView(v);
        timePicker.setIs24HourView(true);
        TimePreference pref = (TimePreference) getPreference();
        timePicker.setCurrentHour(pref.hour);
        timePicker.setCurrentMinute(pref.minute);
    }

    @Override
    public void onDialogClosed(boolean positiveResult)
    {
        if (positiveResult)
        {
            TimePreference pref = (TimePreference) getPreference();
            pref.hour = timePicker.getCurrentHour();
            pref.minute = timePicker.getCurrentMinute();

            String value = TimePreference.timeToString(pref.hour, pref.minute);
            if (pref.callChangeListener(value)) pref.persistStringValue(value);
        }
    }

    @Override
    public Preference findPreference(CharSequence charSequence)
    {
        return getPreference();
    }
}

Required modifications in PreferenceFragmentCompat extension class

    public static class PreferencesFragment extends PreferenceFragmentCompat
    {
       ....

        @Override
        public void onDisplayPreferenceDialog(Preference preference)
        {
            DialogFragment dialogFragment = null;
            if (preference instanceof TimePreference)
            {
                dialogFragment = new TimePreferenceDialogFragmentCompat();
                Bundle bundle = new Bundle(1);
                bundle.putString("key", preference.getKey());
                dialogFragment.setArguments(bundle);
            }

            if (dialogFragment != null)
            {
                dialogFragment.setTargetFragment(this, 0);
                dialogFragment.show(this.getFragmentManager(), "android.support.v7.preference.PreferenceFragment.DIALOG");
            }
            else
            {
                super.onDisplayPreferenceDialog(preference);
            }
        }
    }

With above code time preference can be used in preferences xml file like this

<com.test.TimePreference
    android:key="some_time"
    android:title="Set some time"
    android:defaultValue="12:00"
    android:summary="Set some time"/>

Upvotes: 31

David V&#225;vra
David V&#225;vra

Reputation: 19159

I have modified the code from first answer:

  • it stores selected time in long form (milliseconds) which is easier to work with (using Calendar) then string
  • it automatically shows selected time in summary field in user's format (12 or 24 hour)

Updated code:

public class TimePreference extends DialogPreference {
    private Calendar calendar;
    private TimePicker picker = null;

    public TimePreference(Context ctxt) {
        this(ctxt, null);
    }

    public TimePreference(Context ctxt, AttributeSet attrs) {
        this(ctxt, attrs, android.R.attr.dialogPreferenceStyle);
    }

    public TimePreference(Context ctxt, AttributeSet attrs, int defStyle) {
        super(ctxt, attrs, defStyle);

        setPositiveButtonText(R.string.set);
        setNegativeButtonText(R.string.cancel);
        calendar = new GregorianCalendar();
    }

    @Override
    protected View onCreateDialogView() {
        picker = new TimePicker(getContext());
        return (picker);
    }

    @Override
    protected void onBindDialogView(View v) {
        super.onBindDialogView(v);
        picker.setCurrentHour(calendar.get(Calendar.HOUR_OF_DAY));
        picker.setCurrentMinute(calendar.get(Calendar.MINUTE));
    }

    @Override
    protected void onDialogClosed(boolean positiveResult) {
        super.onDialogClosed(positiveResult);

        if (positiveResult) {
            calendar.set(Calendar.HOUR_OF_DAY, picker.getCurrentHour());
            calendar.set(Calendar.MINUTE, picker.getCurrentMinute());

            setSummary(getSummary());
            if (callChangeListener(calendar.getTimeInMillis())) {
                persistLong(calendar.getTimeInMillis());
                notifyChanged();
            }
        }
    }

    @Override
    protected Object onGetDefaultValue(TypedArray a, int index) {
        return (a.getString(index));
    }

    @Override
    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {

        if (restoreValue) {
            if (defaultValue == null) {
                calendar.setTimeInMillis(getPersistedLong(System.currentTimeMillis()));
            } else {
                calendar.setTimeInMillis(Long.parseLong(getPersistedString((String) defaultValue)));
            }
        } else {
            if (defaultValue == null) {
                calendar.setTimeInMillis(System.currentTimeMillis());
            } else {
                calendar.setTimeInMillis(Long.parseLong((String) defaultValue));
            }
        }
        setSummary(getSummary());
    }

    @Override
    public CharSequence getSummary() {
        if (calendar == null) {
            return null;
        }
        return DateFormat.getTimeFormat(getContext()).format(new Date(calendar.getTimeInMillis()));
    }
} 

Upvotes: 72

Binoy Babu
Binoy Babu

Reputation: 17139

I have modified CommonsWare answer to use JodaTime library:

import android.content.Context;
import android.content.res.TypedArray;
import android.preference.DialogPreference;
import android.support.annotation.NonNull;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TimePicker;

import org.joda.time.LocalTime;

public class TimePreference extends DialogPreference {
    private int lastHour;
    private int lastMinute;
    private TimePicker picker;

    public TimePreference(Context context, AttributeSet attrs) {
        super(context, attrs);

        setPositiveButtonText("Set");
        setNegativeButtonText("Cancel");
    }

    @Override
    protected View onCreateDialogView() {
        picker = new TimePicker(getContext());

        return(picker);
    }

    @Override
    protected void onBindDialogView(@NonNull View v) {
        super.onBindDialogView(v);

        picker.setCurrentHour(lastHour);
        picker.setCurrentMinute(lastMinute);
    }

    @Override
    protected void onDialogClosed(boolean positiveResult) {
        super.onDialogClosed(positiveResult);

        if (positiveResult) {
            lastHour = picker.getCurrentHour();
            lastMinute = picker.getCurrentMinute();

            LocalTime localTime = new LocalTime(lastHour, lastMinute);
            String time = localTime.toString();

            if (callChangeListener(time)) {
                persistString(time);
            }
        }
    }

    @Override
    protected Object onGetDefaultValue(TypedArray a, int index) {
        return(a.getString(index));
    }

    @Override
    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
        LocalTime time;

        if (restoreValue) {
            if (defaultValue == null) {
                time = LocalTime.parse(getPersistedString("08:00:00.000"));
            }
            else {
                time = LocalTime.parse(getPersistedString(defaultValue.toString()));
            }
        } else {
            time = LocalTime.parse(defaultValue.toString());
        }

        lastHour = time.getHourOfDay();
        lastMinute = time.getMinuteOfHour();
    }
}

Also you will need to add a Custom preference like Sikora said.

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >

    <EditTextPreference
        android:key="editTextPref_Key"
        android:title="@string/editTextPref_title"/>
    <com.example.myapp.TimePreference
        android:key="timePrefA_Key"
        android:title="@string/timePrefA_title"/>    
    <com.example.myapp.TimePreference
        android:key="timePrefB_Key"
        android:title="@string/timePrefB_title"/>

</PreferenceScreen>

Upvotes: 1

Sikora
Sikora

Reputation: 776

For those whom the implementation of a custom Preference isn't so obvious (like it wasn't for me), you have to add this to your preferences.xml or whatever you're calling it.

You'll end up with something like this:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >

    <EditTextPreference
        android:key="editTextPref_Key"
        android:title="@string/editTextPref_title"/>
    <com.example.myapp.TimePreference
        android:key="timePrefA_Key"
        android:title="@string/timePrefA_title"/>    
    <com.example.myapp.TimePreference
        android:key="timePrefB_Key"
        android:title="@string/timePrefB_title"/>

</PreferenceScreen>

Assuming you added the TimePreference to your own root package:
(src/com/example/myapp/TimePreference.java)

Upvotes: 57

Omid Omidi
Omid Omidi

Reputation: 1670

add this for Summary:

@Override
public CharSequence getSummary() {
    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.YEAR, Calendar.MONTH, Calendar.DAY_OF_MONTH, lastHour, lastMinute);
    return DateFormat.getTimeFormat(getContext()).format(new Date(cal.getTimeInMillis()));
}

and add

setSummary(getSummary());

to end of onSetInitialValue and onDialogClosed.

Upvotes: 4

pmont
pmont

Reputation: 2129

Like LEO87, I was seeing ClassCastException's. The problem was due to stale persisted data from a previous control of the same name. Possible solutions are to clear the app data, use a different name (key), or if you must use the same key name, catch the exception as follows:

@Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
    if (restoreValue) {
        long persistedValue;
        try {
            persistedValue = getPersistedLong(System.currentTimeMillis());
        } catch (Exception e) {
            //Stale persisted data may be the wrong type
            persistedValue = System.currentTimeMillis();
        }
        calendar.setTimeInMillis(persistedValue);
    } else if (defaultValue != null) {
        calendar.setTimeInMillis(Long.parseLong((String) defaultValue));
    } else {
        //!restoreValue, defaultValue == null
        calendar.setTimeInMillis(System.currentTimeMillis());
    }

    setSummary(getSummary());
}

Upvotes: 0

CommonsWare
CommonsWare

Reputation: 1007276

There is no TimePreference built into Android. However, creating your own is fairly easy. Here's one I did:

import android.content.Context;
import android.content.res.TypedArray;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TimePicker;

public class TimePreference extends DialogPreference {
    private int lastHour=0;
    private int lastMinute=0;
    private TimePicker picker=null;

    public static int getHour(String time) {
        String[] pieces=time.split(":");

        return(Integer.parseInt(pieces[0]));
    }

    public static int getMinute(String time) {
        String[] pieces=time.split(":");

        return(Integer.parseInt(pieces[1]));
    }

    public TimePreference(Context ctxt, AttributeSet attrs) {
        super(ctxt, attrs);

        setPositiveButtonText("Set");
        setNegativeButtonText("Cancel");
    }

    @Override
    protected View onCreateDialogView() {
        picker=new TimePicker(getContext());

        return(picker);
    }

    @Override
    protected void onBindDialogView(View v) {
        super.onBindDialogView(v);

        picker.setCurrentHour(lastHour);
        picker.setCurrentMinute(lastMinute);
    }

    @Override
    protected void onDialogClosed(boolean positiveResult) {
        super.onDialogClosed(positiveResult);

        if (positiveResult) {
            lastHour=picker.getCurrentHour();
            lastMinute=picker.getCurrentMinute();

            String time=String.valueOf(lastHour)+":"+String.valueOf(lastMinute);

            if (callChangeListener(time)) {
                persistString(time);
            }
        }
    }

    @Override
    protected Object onGetDefaultValue(TypedArray a, int index) {
        return(a.getString(index));
    }

    @Override
    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
        String time=null;

        if (restoreValue) {
            if (defaultValue==null) {
                time=getPersistedString("00:00");
            }
            else {
                time=getPersistedString(defaultValue.toString());
            }
        }
        else {
            time=defaultValue.toString();
        }

        lastHour=getHour(time);
        lastMinute=getMinute(time);
    }
}

Upvotes: 151

Related Questions