Android中的NumberPicker与格式化程序在首次呈现时不进行格式化

39

我有一个NumberPicker,它有一个格式化程序,可以在NumberPicker旋转或手动输入值时格式化显示的数字。这个工作正常,但是当NumberPicker首次显示并且我使用setValue(0)进行初始化时,0不会被格式化(应该显示为“-”,而不是0)。从那时起,只要我旋转NumberPicker,一切就都正常了。

我该如何强制NumberPicker始终进行格式化 - 无论是在第一次呈现时还是在我使用键盘手动输入数字时?

这是我的格式化程序

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;
 }

}

我使用setFormatter()将我的格式化程序添加到选择器中,这就是我对选择器所做的全部操作。

    picker.setMaxValue(max);
    picker.setMinValue(min);
    picker.setFormatter(new PickerFormatter(single, multiple));
    picker.setWrapSelectorWheel(wrap);

你能贴一些代码吗?这有助于回答问题。 - Ankit Aggarwal
这只是标准实现,真的。 - A. Steenbergen
添加了一些代码,但问题不在代码上,NumberPicker 在第一次渲染时没有执行代码。 - A. Steenbergen
我对这个有点新,但是你能从构造函数中调用format()吗?我不认为它最初被调用。那会有帮助吗? - Ankit Aggarwal
我刚刚在谷歌上搜索了一下,并在Android项目的问题报告中找到了这个问题:http://code.google.com/p/android/issues/detail?id=35482 让我们等待修复。 - georgepiva
10个回答

29

dgel的解决方案对我没有效果:当我点击选择器时,格式会再次消失。这个 bug 是由于在NumberPicker内设置输入过滤器所致,当未使用setDisplayValues时。因此,我想出了这个解决方法:

Field f = NumberPicker.class.getDeclaredField("mInputText");
f.setAccessible(true);
EditText inputText = (EditText)f.get(mPicker);
inputText.setFilters(new InputFilter[0]);

1
在 API 25 上对我有用。谢谢! - rjr-apps

27

我也遇到了这个让人烦恼的小错误。使用了这个答案中的技巧,得到了一个不太好看但很有效的修复方法。

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();
}

在实例化我的数字选择器后立即调用那个私有的changeValueByOne方法似乎足以促使格式化程序表现出应有的行为。数字选择器出现得很干净,第一个值被正确格式化了。就像我说的,很恶劣但是有效。


13
在安卓编程中,你经常不得不使用一些麻烦的技巧。 - A. Steenbergen
3
这个漏洞在两年前就已经报告了……谷歌,你开玩笑吧?在Android 4.4.2上仍然存在问题。 - serine
4
确实 - 这仍然存在于棉花糖版本中 :( - Nathan Osman
5
Android 7.1.1上的一个漏洞似乎仍然完全没有被注意到。 - Andrew No
8
埃隆·马斯克将一辆特斯拉汽车送入了太空,这个事件至今仍存在。 - Karl-John Chow
显示剩余5条评论

12

我有同样的问题,我使用了 setDisplayedValues() 方法。

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)

然而,这并不允许用户在拾取器中手动设置值。


Android开发者参考:注意:通过setDisplayedValues(String[])设置的显示值数组的长度必须等于可选择数字的范围,该范围等于getMaxValue() - getMinValue() + 1。 - MikeJfromVA

7
以下解决方案适用于API 18-26,无需使用反射,也无需使用setDisplayedValues()方法。它包含两个步骤:
  1. 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);
      }
    }
    
  2. 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;
     }
    }
    

6

以下是基于 torvinSebastian 的答案的解决方案。您无需子类化任何内容或使用反射。

View editView = numberPicker.getChildAt(0);

if (editView instanceof EditText) {
    // Remove default input filter
    ((EditText) editView).setFilters(new InputFilter[0]);
}

1
感谢这个解决方案,使用数据绑定、数字选择器和格式化程序,还包括Unicode符号,对我很有用。 - Dimitri Williams

4
通过反射调用私有方法 changeValueByOne() 的方法,如之前的回答所述,在 API Level 16(Android 4.1.2 及以上版本)上对我有效,但在 API Level 15(Android 4.0.3)上似乎没有帮助!
对我在 API Level 15(及以上版本)上有效的方法是使用自定义格式化程序创建字符串数组,并将其与方法 setDisplayedValues() 一起传递到数字选择器。
另请参见:Android 3.x and 4.x NumberPicker Example

4

我觉得NoActivity提供的答案对我很有用,但我只需要做以下操作:

View firstItem = bugFixNumberPicker.getChildAt(0);
if (firstItem != null) {
  firstItem.setVisibility(View.INVISIBLE);
}

为了修复此问题,我不需要子类化NumberPicker。 我没有看到在触摸时选择器元素消失的问题。

我可以确认在触摸时格式化的元素会消失(在Lollipop上测试过)。 - Nikolai
这是最好的工作答案,(对我来说)被接受的答案也可以,但是使用compose时,文本颜色会消失。 - Narek Hayrapetyan

3

基于Nikolai的答案的Kotlin版本

private fun initNumberPicker() {
    nrPicker.children.iterator().forEach {
        if (it is EditText) it.filters = arrayOfNulls(0)    // remove default input filter
    }
}

1
我通过调用成功修复了它。
picker.invalidate();

在设置格式化程序之后。


0

如果选择的索引不是0,则改进Nikolai的答案。这样做对性能不太好,但可以解决问题。

for(index in numberPicker.minValue..numberPicker.maxValue) {

        val editView = numberPicker.getChildAt(index-numberPicker.minValue)
        if (editView != null && editView is EditText) {
            // Remove default input filter
            (editView as EditText).filters = arrayOfNulls(0)
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接