Search code examples
androidnumberpicker

android numberpicker can't read keyboard number input


I'm trying to create a numberpicker to select a month. It works aslong as I select the value through scrolling or if i use the keyboard to input the month by text (e.g. "jan" for januari)

I also want my users to be able to input '1' to select januari. From numberpicker source code, it seems this should be possible:

/**
 * @return The selected index given its displayed <code>value</code>.
 */
private int getSelectedPos(String value) {
    if (mDisplayedValues == null) {
        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException e) {
            // Ignore as if it's not a number we don't care
        }
    } else {
        for (int i = 0; i < mDisplayedValues.length; i++) {
            // Don't force the user to type in jan when ja will do
            value = value.toLowerCase();
            if (mDisplayedValues[i].toLowerCase().startsWith(value)) {
                return mMinValue + i;
            }
        }

        /*
         * The user might have typed in a number into the month field i.e.
         * 10 instead of OCT so support that too.
         */
        try {
            return Integer.parseInt(value);
        } catch (NumberFormatException e) {

            // Ignore as if it's not a number we don't care
        }
    }
    return mMinValue;
}

The problem is, if I try to input a number, the EditText just stays empty. This is how I initialise my numberpicker:

    //getting the months using Calendar
    Calendar cal = Calendar.getInstance();
    Map<String, Integer> monthMap = cal.getDisplayNames(Calendar.MONTH, Calendar.LONG, Locale.getDefault());
    TreeMap<Integer, String> sorted = new TreeMap<>();
    for(Map.Entry<String, Integer> entry : monthMap.entrySet()) {
        sorted.put(entry.getValue(), entry.getKey());
    }

    String[] displayNames = sorted.values().toArray(new String[]{});

    mMonthPicker.setMinValue(0);
    mMonthPicker.setMaxValue(11);
    mMonthPicker.setDisplayedValues(displayNames);
    mMonthPicker.setWrapSelectorWheel(false);

I tried setting the inputtype for the edittext to InputType.TYPE_NULL using the answer given here but that didn't change anything.

The Edit Text stays empty if I try to input a number.


Solution

  • I finally figured it out. The numberpicker didn't allow numbers for month input because of the filter on the EditText.

    I solved it by copying some code of the NumberKeyListener from numberpicker source code and adjusting it so it would accept numeric input.

    Then i added this filter on the EditText, which I look up by going through the childviews and checking if the current view is an EditText. I found the code for looking up the EditText in the answer here: NumberPicker doesn't work with keyboard

    my code looks like this:

    mInputText = findInput(mMonthPicker);
    mInputText.setFilters(new InputFilter[]{ new InputTextFilter() });
    

    this is my filter:

    /**
     * Filter for accepting only valid indices or prefixes of the string
     * representation of valid indices.
     */
    class InputTextFilter extends NumberKeyListener {
    
        /**
         * The numbers accepted by the input text's {@link android.view.LayoutInflater.Filter}
         */
       private final char[] DIGIT_CHARACTERS = new char[] {
                // Latin digits are the common case
                '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                // Arabic-Indic
                '\u0660', '\u0661', '\u0662', '\u0663', '\u0664', '\u0665', '\u0666', '\u0667', '\u0668'
                , '\u0669',
                // Extended Arabic-Indic
                '\u06f0', '\u06f1', '\u06f2', '\u06f3', '\u06f4', '\u06f5', '\u06f6', '\u06f7', '\u06f8'
                , '\u06f9',
                // Hindi and Marathi (Devanagari script)
                '\u0966', '\u0967', '\u0968', '\u0969', '\u096a', '\u096b', '\u096c', '\u096d', '\u096e'
                , '\u096f',
                // Bengali
                '\u09e6', '\u09e7', '\u09e8', '\u09e9', '\u09ea', '\u09eb', '\u09ec', '\u09ed', '\u09ee'
                , '\u09ef',
                // Kannada
                '\u0ce6', '\u0ce7', '\u0ce8', '\u0ce9', '\u0cea', '\u0ceb', '\u0cec', '\u0ced', '\u0cee'
                , '\u0cef'
        };
    
        // XXX This doesn't allow for range limits when controlled by a
        // soft input method!
        public int getInputType() {
            return InputType.TYPE_CLASS_TEXT;
        }
    
        @Override
        protected char[] getAcceptedChars() {
            return DIGIT_CHARACTERS;
        }
    
        @Override
        public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
            Log.v("filter", "source:" + source.toString());
    
            CharSequence filtered = String.valueOf(source.subSequence(start, end));
    
            Log.v("filter", "filtered:" + filtered.toString());
            if (TextUtils.isEmpty(filtered)) {
                return "";
            }
            String result = String.valueOf(dest.subSequence(0, dstart)) + filtered
                    + dest.subSequence(dend, dest.length());
            String str = String.valueOf(result).toLowerCase();
            try{
                int value = Integer.parseInt(str);
    
                if(1 <= value && value <= 12) {
                    return source;
                }
            } catch(NumberFormatException e) {
                //continue with the checking
            }
    
            for (String val : mMonthPicker.getDisplayedValues()) {
                String valLowerCase = val.toLowerCase();
                if (valLowerCase.startsWith(str)) {
                    final int selstart = result.length();
                    final int selend = val.length();
                    mInputText.post(new Runnable() {
                        @Override
                        public void run() {
                            mInputText.setSelection(selstart, selend);
                        }
                    });
                    return val.subSequence(dstart, val.length());
                }
            }
            return "";
    
        }
    }
    

    and this is the code to find the EditText:

    private EditText findInput(ViewGroup np) {
        int count = np.getChildCount();
        for (int i = 0; i < count; i++) {
            final View child = np.getChildAt(i);
            if (child instanceof ViewGroup) {
                findInput((ViewGroup) child);
            } else if (child instanceof EditText) {
                return (EditText) child;
            }
        }
        return null;
    }