Created
July 7, 2017 14:34
-
-
Save smaugho/14dae83f3284fa05455ee0a9e4f13099 to your computer and use it in GitHub Desktop.
This class overrides the TimePickerDialog, showing alswas the Fixed Spinner even for Android 7 Nougout, and it permits to the spinner only to select in 15 minutes interval
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class CustomTimePickerDialog extends TimePickerDialog { | |
private final static int TIME_PICKER_INTERVAL = 15; | |
private TimePicker timePicker; | |
private final OnTimeSetListener callback; | |
public HorekoTimePicker(Context context, | |
OnTimeSetListener callBack, int hourOfDay, int minute, boolean is24HourView) { | |
super(context, callBack, hourOfDay, minute/TIME_PICKER_INTERVAL, is24HourView); | |
this.callback = callBack; | |
fixSpinner(context, hourOfDay, minute, is24HourView); | |
} | |
/** | |
* Workaround for this bug: https://code.google.com/p/android/issues/detail?id=222208 | |
* In Android 7.0 Nougat, spinner mode for the TimePicker in TimePickerDialog is | |
* incorrectly displayed as clock, even when the theme specifies otherwise, such as: | |
* | |
* <resources> | |
* <style name="Theme.MyApp" parent="Theme.AppCompat.Light.NoActionBar"> | |
* <item name="android:timePickerStyle">@style/Widget.MyApp.TimePicker</item> | |
* </style> | |
* | |
* <style name="Widget.MyApp.TimePicker" parent="android:Widget.Material.TimePicker"> | |
* <item name="android:timePickerMode">spinner</item> | |
* </style> | |
* </resources> | |
* | |
* May also pass TimePickerDialog.THEME_HOLO_LIGHT as an argument to the constructor, | |
* as this theme has the TimePickerMode set to spinner. | |
* | |
* Taken from: https://gist.github.com/jeffdgr8/6bc5f990bf0c13a7334ce385d482af9f | |
*/ | |
private void fixSpinner(Context context, int hourOfDay, int minute, boolean is24HourView) { | |
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // android:timePickerMode spinner and clock began in Lollipop | |
try { | |
// Get the theme's android:timePickerMode | |
final int MODE_SPINNER = 1; | |
Class<?> styleableClass = Class.forName("com.android.internal.R$styleable"); | |
Field timePickerStyleableField = styleableClass.getField("TimePicker"); | |
int[] timePickerStyleable = (int[]) timePickerStyleableField.get(null); | |
final TypedArray a = context.obtainStyledAttributes(null, timePickerStyleable, android.R.attr.timePickerStyle, 0); | |
Field timePickerModeStyleableField = styleableClass.getField("TimePicker_timePickerMode"); | |
int timePickerModeStyleable = timePickerModeStyleableField.getInt(null); | |
final int mode = a.getInt(timePickerModeStyleable, MODE_SPINNER); | |
a.recycle(); | |
if (mode == MODE_SPINNER) { | |
timePicker = (TimePicker) findField(TimePickerDialog.class, TimePicker.class, "mTimePicker").get(this); | |
Class<?> delegateClass = Class.forName("android.widget.TimePicker$TimePickerDelegate"); | |
Field delegateField = findField(TimePicker.class, delegateClass, "mDelegate"); | |
Object delegate = delegateField.get(timePicker); | |
Class<?> spinnerDelegateClass; | |
if (Build.VERSION.SDK_INT != Build.VERSION_CODES.LOLLIPOP) { | |
spinnerDelegateClass = Class.forName("android.widget.TimePickerSpinnerDelegate"); | |
} else { | |
// TimePickerSpinnerDelegate was initially misnamed TimePickerClockDelegate in API 21! | |
spinnerDelegateClass = Class.forName("android.widget.TimePickerClockDelegate"); | |
} | |
// In 7.0 Nougat for some reason the timePickerMode is ignored and the delegate is TimePickerClockDelegate | |
if (delegate.getClass() != spinnerDelegateClass) { | |
delegateField.set(timePicker, null); // throw out the TimePickerClockDelegate! | |
timePicker.removeAllViews(); // remove the TimePickerClockDelegate views | |
Constructor spinnerDelegateConstructor = spinnerDelegateClass.getConstructor(TimePicker.class, Context.class, AttributeSet.class, int.class, int.class); | |
spinnerDelegateConstructor.setAccessible(true); | |
// Instantiate a TimePickerSpinnerDelegate | |
delegate = spinnerDelegateConstructor.newInstance(timePicker, context, null, android.R.attr.timePickerStyle, 0); | |
delegateField.set(timePicker, delegate); // set the TimePicker.mDelegate to the spinner delegate | |
// Set up the TimePicker again, with the TimePickerSpinnerDelegate | |
timePicker.setIs24HourView(is24HourView); | |
timePicker.setCurrentHour(hourOfDay); | |
timePicker.setCurrentMinute(minute); | |
timePicker.setOnTimeChangedListener(this); | |
} | |
setTimeIntervals(); | |
} | |
} catch (Exception e) { | |
throw new RuntimeException(e); | |
} | |
} | |
} | |
private static Field findField(Class objectClass, Class fieldClass, String expectedName) { | |
try { | |
Field field = objectClass.getDeclaredField(expectedName); | |
field.setAccessible(true); | |
return field; | |
} catch (NoSuchFieldException e) {} // ignore | |
// search for it if it wasn't found under the expected ivar name | |
for (Field searchField : objectClass.getDeclaredFields()) { | |
if (searchField.getType() == fieldClass) { | |
searchField.setAccessible(true); | |
return searchField; | |
} | |
} | |
return null; | |
} | |
@Override | |
protected void onStop() { } | |
/* | |
* Feature #363: (Un)availability times in 15min interval | |
* https://abix.webhop.net/redmine/issues/363 | |
* Solution extracted from | |
* http://stackoverflow.com/questions/20214547/show-timepicker-with-minutes-intervals-in-android | |
*/ | |
@Override | |
public void onClick(DialogInterface dialog, int which) { | |
super.onClick(dialog, which); | |
if (callback != null && timePicker != null) { | |
timePicker.clearFocus(); | |
callback.onTimeSet(timePicker, timePicker.getCurrentHour(), | |
timePicker.getCurrentMinute()*TIME_PICKER_INTERVAL); | |
} | |
} | |
private void setTimeIntervals() { | |
try { | |
Class<?> classForid = Class.forName("com.android.internal.R$id"); | |
Field field = classForid.getField("minute"); | |
NumberPicker mMinuteSpinner = (NumberPicker) timePicker.findViewById(field.getInt(null)); | |
mMinuteSpinner.setMinValue(0); | |
mMinuteSpinner.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)); | |
} | |
mMinuteSpinner.setDisplayedValues(displayedValues.toArray(new String[0])); | |
} catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
My code is not working on android 9 (On other versions it is working). It showing this error
" java.lang.RuntimeException: java.lang.NoSuchFieldException: mTimePicker "
Please suggest me any solution
This is the code for CustomTimePickerDialog.java class
import android.app.TimePickerDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.widget.NumberPicker;
import android.widget.TimePicker;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
public class CustomTimePickerDialog extends TimePickerDialog {
/* Field timepicker = rClass.getDeclaredField("mTimePicker");
TimePicker timePicker = (TimePicker) findViewById(timepicker.getInt(this));/
/ Class rClass = getClass().getSuperclass(); Field mTimePickerField = rClass.getDeclaredField("mTimePicker"); mTimePickerField.setAccessible(true);*/ // TimePicker mTimePicker = (TimePicker) mTimePickerField.get(this); timePicker = findViewById (Resources.getSystem().getIdentifier("mTimePicker", "id", "android")); timePicker.setOnTimeChangedListener(new TimePicker.OnTimeChangedListener() { @OverRide public void onTimeChanged(TimePicker view, int hourOfDay, int minute) { if(mOnTimeChangedListener != null) { mOnTimeChangedListener.onTimeChanged(view, hourOfDay, minute); } } }); /*Class classForid = Class.forName("com.android.internal.R$id");
// Field m = classForid.getField("minute");
Field m = classForid.getField("minute");*/
NumberPicker mMinuteSpinner = timePicker.findViewById(Resources.getSystem().getIdentifier(
"minute",
"id",
"android"
));
// NumberPicker mMinuteSpinner = (NumberPicker) mTimePicker.findViewById(m.getInt(this));
if (this.isMinuteVisible) {
mMinuteSpinner.setVisibility(View.VISIBLE);
mMinuteSpinner.setMinValue(0);
mMinuteSpinner.setMaxValue((60 / increment) - 1);
List displayedValues = new ArrayList();
for (int i = 0; i < 60; i += increment) {
displayedValues.add(String.format("%02d", i));
}
mMinuteSpinner.setDescendantFocusability(NumberPicker.FOCUS_BLOCK_DESCENDANTS);
mMinuteSpinner.setDisplayedValues(displayedValues.toArray(new String[0]));
} else {
mMinuteSpinner.setVisibility(View.GONE);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}