Android NumberPicker with Formatter doesn't format on first rendering
I have a NumberPicker that has a formatter that formats the displayed numbers either when the NumberPicker spins or when a value is entered manually. This works fine, but when the NumberPicker is first shown and I initialize it with setValue(0)
the 0 does not get formatted (it should display as "-" instead of 0). As soon as I spin the NumberPicker from that point on everything works.
How can I force the NumberPicker to format always - Both on first rendering and also when I enter a number manually with the keyboard?
This is my formatter
public class PickerFormatter implements Formatter {
private String mSingle;
private String mMultiple;
public PickerFormatter(String single, String multiple) {
mSingle = single;
mMultiple = multiple;
}
@Override
public String format(int num) {
if (num == 0) {
return "-";
}
if (num == 1) {
return num + " " + mSingle;
}
return num + " " + mMultiple;
}
}
I add my formatter to the picker with setFormatter()
, this is all I do to the picker.
picker.setMaxValue(max);
picker.setMinValue(min);
picker.setFormatter(new PickerFormatter(single, multiple));
picker.setWrapSelectorWheel(wrap);
Solution 1:
dgel's solution doesn't work for me: when I tap on the picker, formatting disappears again. This bug is caused by input filter set on EditText
inside NumberPicker
when setDisplayValues
isn't used. So I came up with this workaround:
Field f = NumberPicker.class.getDeclaredField("mInputText");
f.setAccessible(true);
EditText inputText = (EditText)f.get(mPicker);
inputText.setFilters(new InputFilter[0]);
Solution 2:
I also encountered this annoying little bug. Used a technique from this answer to come up with a nasty but effective fix.
NumberPicker picker = (NumberPicker)view.findViewById(id.picker);
picker.setMinValue(1);
picker.setMaxValue(5);
picker.setWrapSelectorWheel(false);
picker.setFormatter(new NumberPicker.Formatter() {
@Override
public String format(int value) {
return my_formatter(value);
}
});
try {
Method method = picker.getClass().getDeclaredMethod("changeValueByOne", boolean.class);
method.setAccessible(true);
method.invoke(picker, true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
Calling that private changeValueByOne
method immediately after instantiating my number picker seems to kick the formatter enough to behave how it should. The number picker comes up nice and clean with the first value formatted correctly. Like I said, nasty but effective.
Solution 3:
I had the same problem and I used the setDisplayedValues()
method instead.
int max = 99;
String[] values = new String[99];
values[0] = “-” + mSingle
values[1] =
for(int i=2; i<=max; i++){
makeNames[i] = String.valueOf(i) + mMultiple;
}
picker.setMinValue(0);
picker.setMaxValue(max);
picker.setDisplayedValues(values)
This doesn't allow the user to set the value manually in the picker though.
Solution 4:
The following solution worked out for me for APIs 18-26 without using reflection, and without using setDisplayedValues()
.
It consists of two steps:
-
Make sure the first element shows by setting it's visibility to invisible (I used Layout Inspector to see the difference with when it shows, it's not logical but
View.INVISIBLE
actually makes the view visible).private void initNumberPicker() { // Inflate or create your BugFixNumberPicker class // Do your initialization on bugFixNumberPicker... bugFixNumberPicker.setFormatter(new NumberPicker.Formatter() { @Override public String format(final int value) { // Format to your needs return aFormatMethod(value); } }); // Fix for bug in Android Picker where the first element is not shown View firstItem = bugFixNumberPicker.getChildAt(0); if (firstItem != null) { firstItem.setVisibility(View.INVISIBLE); } }
-
Subclass NumberPicker and make sure no click events go through so the glitch where picker elements disapear on touch can't happen.
public class BugFixNumberPicker extends NumberPicker { public BugFixNumberPicker(Context context) { super(context); } public BugFixNumberPicker(Context context, AttributeSet attrs) { super(context, attrs); } public BugFixNumberPicker(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean performClick() { return false; } @Override public boolean performLongClick() { return false; } @Override public boolean onInterceptTouchEvent(MotionEvent event) { return false; } }
Solution 5:
Here's my solution based on answers by torvin and Sebastian. You don't have to subclass anything or use reflection.
View editView = numberPicker.getChildAt(0);
if (editView instanceof EditText) {
// Remove default input filter
((EditText) editView).setFilters(new InputFilter[0]);
}