I am trying to create a NumberPicker dialog in my preference screen. I have already made one following this:https://stackoverflow.com/a/5533295/2442638
However, for my second dialog, I only want one spinner, so I have adapted the code as follows:
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.NumberPicker;
public class SnoozeTPP extends DialogPreference {
private int Minute = 0;
private NumberPicker np= null;
public static int getMinute(String time) {
String[] pieces = time.split(":");
return (Integer.parseInt(pieces[1]));
}
public SnoozeTPP(Context context, AttributeSet attrs) {
super(context, attrs);
setPositiveButtonText("Set");
setNegativeButtonText("Cancel");
}
#Override
protected View onCreateDialogView() {
np = new NumberPicker(getContext());
return (np);
}
#Override
protected void onBindDialogView(View v) {
super.onBindDialogView(v);
np.setMaxValue(60);
np.setValue(Minute);
}
#Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (positiveResult) {
Minute = np.getValue();
String time = 0 + ":" + String.valueOf(Minute);
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("08:00");
} else {
time = getPersistedString(defaultValue.toString());
}
} else {
time = defaultValue.toString();
}
Minute = getMinute(time);
}
}
There are no errors and the dialog pops up correctly, but the layout of it seems to be "messed up" :-). The blue line stretch across the whole dialog instead of just the width of the numbers.
The question is - how to set the layout correctly? (I am sure there are lots of other mistakes as well!)
Thank you
I solved this by using the CyanogenMod number picker.
Java file:
https://github.com/CyanogenMod/android_packages_apps_Trebuchet/blob/cm-10.2/src/com/cyanogenmod/trebuchet/preference/NumberPickerPreference.java
XML file:
https://github.com/CyanogenMod/android_packages_apps_Trebuchet/blob/cm-10.2/res/layout/number_picker_dialog.xml
Attributes:
https://github.com/CyanogenMod/android_packages_apps_Trebuchet/blob/cm-10.2/res/values/attrs.xml#L158
Here is an example of simple, but working NumberPickerPreference, saving integer value between 1 and 100:
NumberPickerPreference.java:
public class NumberPickerPreference extends DialogPreference {
private NumberPicker mPicker;
private Integer mNumber = 0;
public NumberPickerPreference(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NumberPickerPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setPositiveButtonText(android.R.string.ok);
setNegativeButtonText(android.R.string.cancel);
}
#Override
protected View onCreateDialogView() {
mPicker = new NumberPicker(getContext());
mPicker.setMinValue(1);
mPicker.setMaxValue(100);
mPicker.setValue(mNumber);
return mPicker;
}
#Override
protected void onDialogClosed(boolean positiveResult) {
if (positiveResult) {
// needed when user edits the text field and clicks OK
mPicker.clearFocus();
setValue(mPicker.getValue());
}
}
#Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
setValue(restoreValue ? getPersistedInt(mNumber) : (Integer) defaultValue);
}
public void setValue(int value) {
if (shouldPersist()) {
persistInt(value);
}
if (value != mNumber) {
mNumber = value;
notifyChanged();
}
}
#Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getInt(index, 0);
}
}
This is more a workaround than a solution, but i hope it helps. Adding a dummy textView solved the problem. I got exactly the same problem.
My xml File:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="#+id/textDummyEmpty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/textDummyEmpty" />
<NumberPicker
android:id="#+id/numberPicker1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
</LinearLayout>
and
android:text="#string/textDummyEmpty"
is an empty String. Maybe its also enough to use just a view instead of a textView.
return a LinearLayout in onCreateDialogView rather than NumberPicker as below:
#Override
protected View onCreateDialogView() {
numberPicker = new NumberPicker(getContext());
numberPicker.setMinValue(1);
numberPicker.setMaxValue(12);
numberPicker.setWrapSelectorWheel(false);
numberPicker.setValue(lastValue);
LinearLayout.LayoutParams pickerParams = new LinearLayout.LayoutParams
(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
pickerParams.gravity = Gravity.CENTER;
numberPicker.setLayoutParams(pickerParams);
LinearLayout layout = new LinearLayout(getContext());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams
(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setLayoutParams(params);
layout.addView(numberPicker);
return layout;
//return numberPicker;
}
Related
I have a frustrating problem, I have created a custom preference for Android, using the support library.
public class CustomTimePreference extends DialogPreference {
public int hour = 0;
public int minute = 0;
public CustomTimePreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
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);
}
#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);
}
}
and
public class CustomTimePreferenceDialogFragmentCompat 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);
CustomTimePreference pref = (CustomTimePreference) getPreference();
timePicker.setCurrentHour(pref.hour);
timePicker.setCurrentMinute(pref.minute);
}
#Override
public void onDialogClosed(boolean positiveResult) {
if (positiveResult) {
CustomTimePreference pref = (CustomTimePreference) getPreference();
pref.hour = timePicker.getCurrentHour();
pref.minute = timePicker.getCurrentMinute();
String value = CustomTimePreference.timeToString(pref.hour, pref.minute);
if (pref.callChangeListener(value)) pref.persistStringValue(value);
}
}
#Override
public Preference findPreference(CharSequence charSequence) {
return getPreference();
}
}
For completeness, the xml contained within the preferences.xml is:
<customcontrols.CustomTimePreference
android:key="time_pace_preference"
android:title="#string/title_time_pace_preference"
android:defaultValue="#string/settings_default_pace"
android:summary="Set some time"
/>
However, when I attempt to call
PreferenceManager.setDefaultValues(mContext, preferences, true);
I receive
java.lang.ClassCastException: customcontrols.CustomTimePreference cannot be cast to android.preference.Preference
Why is this happening? as CustomTimePreference extends DialogPreference which itself extends Preference, this should be fine?!
If I don't call the setDefaultValues() I am able to go into my settings fragment and view the custom control?
What am I doing wrong, and how do I fix it!?
If you are extending android.support.v7.preference.DialogPreference that will cause this crash.
If so, you can use android.support.v7.preference.PreferenceManager#setDefaultValues(android.content.Context, int, boolean) instead.
I have read the many answers on this question but my question is asking where I place the code. I am looking to validate that a number is greater than 100 in the EditTextPreference. This is the code I use to populate the preferences:
public class SettingsFrag extends PreferenceFragment{
//Override onCreate so that the code will run when the activity is started.
#Override
public void onCreate(Bundle savedInstanceState){
//Call to the super class.
super.onCreate(savedInstanceState);
//add the preferences from the XML file.
addPreferencesFromResource(R.xml.preferences);
}
}
Is it in here I add the validation or would I have to create another class?
preferences.xml:
<EditTextPreference
android:key="geofence_range"
android:title="Geofence Size"
android:defaultValue="500"
android:inputType="number"
android:summary="Geofence Size Around User Location"
android:dialogTitle="Enter Size (meters):" />
Add setOnPreferenceChangeListener for EditTextPreference after addPreferencesFromResource to validate data input for User:
EditTextPreference edit_Pref = (EditTextPreference)
getPreferenceScreen().findPreference("geofence_range");
edit_Pref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
// put validation here..
if(<validation pass>){
return true;
}else{
return false;
}
}
});
Note: This answer is based on the deprecated android.preference.EditTextPreference.
Huh. Another one of those things that should be so darned easy in Android but isn't. Other answers just silently prevent writing back the result to preferences, which seems a bit shoddy. (Showing toast is less shoddy, but is still shoddy).
You'll need a custom preference to do this. Customize onValidate to suit your needs.
package com.two_play.extensions;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.EditText;
public class ValidatingEditTextPreference extends EditTextPreference {
public ValidatingEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public ValidatingEditTextPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public ValidatingEditTextPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ValidatingEditTextPreference(Context context) {
super(context);
}
#Override
protected void showDialog(Bundle state) {
super.showDialog(state);
AlertDialog dlg = (AlertDialog)getDialog();
View positiveButton = dlg.getButton(DialogInterface.BUTTON_POSITIVE);
getEditText().setError(null);
positiveButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onPositiveButtonClicked(v);
}
});
}
private void onPositiveButtonClicked(View v) {
String errorMessage = onValidate(getEditText().getText().toString());
if (errorMessage == null)
{
getEditText().setError(null);
onClick(getDialog(),DialogInterface.BUTTON_POSITIVE);
getDialog().dismiss();
} else {
getEditText().setError(errorMessage);
return; // return WITHOUT dismissing the dialog.
}
}
/***
* Called to validate contents of the edit text.
*
* Return null to indicate success, or return a validation error message to display on the edit text.
*
* #param text The text to validate.
* #return An error message, or null if the value passes validation.
*/
public String onValidate(String text)
{
try {
Double.parseDouble(text);
return null;
} catch (Exception e)
{
return getContext().getString(R.string.error_invalid_number);
}
}
}
It seems more elegant to me to disable the "OK" button instead of allowing the user to press it but then discard their input and show the error. getDialog seems to be gone in androidx.preference, but this seems to work for me instead:
final EditTextPreference p = new EditTextPreference(context);
p.setOnBindEditTextListener(new EditTextPreference.OnBindEditTextListener() {
#Override
public void onBindEditText(#NonNull final EditText editText) {
editText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable editable) {
String validationError;
try {
// ... insert your validation logic here, throw on failure ...
validationError = null; // All OK!
} catch (Exception e) {
validationError = e.getMessage();
}
editText.setError(validationError);
editText.getRootView().findViewById(android.R.id.button1)
.setEnabled(validationError == null);
}
});
}
});
Here is my Kotlin implementation for androidx.preference.* based on Vladimir Panteleev's answer:
class CustomPreference : EditTextPreference {
// ...
private fun applyValidation() = setOnBindEditTextListener { editText ->
editText.doAfterTextChanged { editable ->
requireNotNull(editable)
// TODO Add validation magic here.
editText.error = if (criteria.isValid()) {
null // Everything is fine.
} else {
if (criteria.getErrorMessage() == null) "Unknown validation error"
else resources.getString(criteria.getErrorMessage())
}
}
}
}
The handy doAfterTextChanged extension is part of the androidx.core:core-ktx library.
The problem wit onSelectedDayChange is that, when i open the calendarview and click on the focused day, nothing happens. For example i want to see details for today. Now i open the calendarview and today is focused. So i have to click on a other day ond click back on today to view the details. Is there any trick do fix this?
CalendarView derives from View which can generate onClick() events. You could try capturing the onClick() event from the view. Just a thought
I made the following class to solve this problem.
public class CustomCalendarView extends CalendarView {
private final String LOG_HEADER = "CAL:VW";
private Date previousSelectedDate = new Date();
private OnDateChangeListener listener;
private CheckSameSelectedDateAsyncTask task = new CheckSameSelectedDateAsyncTask();
public CustomCalendarView(Context context) {
super(context);
}
public CustomCalendarView(Context context, AttributeSet attribute) {
super(context, attribute);
}
public CustomCalendarView(Context context, AttributeSet attribute, int defStyle) {
super(context, attribute, defStyle);
}
#Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if ((task.getStatus() == AsyncTask.Status.PENDING) || (task.getStatus() == AsyncTask.Status.FINISHED)) {
task = new CheckSameSelectedDateAsyncTask();
task.execute();
}
return false;
}
private void checkSameSelectedDate() {
Date selectedDate = new Date(super.getDate());
if (selectedDate.getDay() == previousSelectedDate.getDay() &&
selectedDate.getMonth() == previousSelectedDate.getMonth() &&
selectedDate.getYear() == previousSelectedDate.getYear()) {
if (listener != null) {
this.listener.onSameSelectedDayChange(this, selectedDate.getYear(), selectedDate.getMonth(), selectedDate.getDay());
}
}
this.previousSelectedDate = selectedDate;
}
public void setSameSelectedDayChangeListener(OnDateChangeListener listener) {
this.listener = listener;
}
public interface OnDateChangeListener extends CalendarView.OnDateChangeListener {
void onSameSelectedDayChange(CalendarView view, int year, int month, int day);
}
private class CheckSameSelectedDateAsyncTask extends AsyncTask<Void, Void, Void> {
#Override
protected Void doInBackground(Void... params) {
try {
//Note: Breaking point between 75 - 100
Thread.sleep(300);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
checkSameSelectedDate();
}
}
}
For custom preferences, e.g. a time picker preference, I use the following XML in Android:
<com.my.package.TimePreference android:key="notification_time" android:selectable="true" android:title="#string/notification_time" android:enabled="true" android:summary="#string/setTime" android:defaultValue="00:00" />
Where the class "TimePreference" is:
package com.my.package;
import android.content.Context;
import android.content.res.TypedArray;
import android.preference.DialogPreference;
import android.text.format.DateFormat;
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;
private boolean is_24_hours = true;
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) {
this(ctxt, null);
}
public TimePreference(Context ctxt, AttributeSet attrs) {
this(ctxt, attrs, 0);
}
public TimePreference(Context ctxt, AttributeSet attrs, int defStyle) {
super(ctxt, attrs, defStyle);
setPositiveButtonText(ctxt.getString(R.string.save));
setNegativeButtonText(ctxt.getString(R.string.cancel));
try {
is_24_hours = DateFormat.is24HourFormat(ctxt);
}
catch (Exception e) {
is_24_hours = true;
}
}
#Override
protected View onCreateDialogView() {
picker = new TimePicker(getContext());
if (is_24_hours) {
picker.setIs24HourView(true);
}
else {
picker.setIs24HourView(false);
}
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) {
picker.clearFocus(); // important - otherwise manual input is not saved
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);
}
}
This has always worked great on Android 2.2 and 2.3 - but now that I've switched to Android 4.0 I can see that this preference does not adjust to the preference screen's layout:
How can I solve this problem? Is there a solution better than setting margin/padding manually?
Finally found the problem:
The constructor super() must be called that takes only two arguments (and no int defStyle argument).
In the code from the question, super() is called with a predefined defStyle value of 0 whcih prevents Android from choosing any nice layout. If you call super() without giving a default style argument, super class DialogPreference's constructor chooses the best style on its own.
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 ?
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);
}
}
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()));
}
}
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)
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"/>
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());
}
}
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.
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>
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]);
}
}
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());
}