Custom Keyboard: handling inputType change

android, android-edittext, android-layout, android-softkeyboard, java

Solution

I think `onStartInputView()` is the callback you need to obtain:

Called when the input view is being shown and input has started on a new editor. This will always be called after onStartInput(EditorInfo, boolean), allowing you to do your general setup there and just view-specific setup here. You are guaranteed that onCreateInputView() will have been called some time before this function is called.

So, you get to know what exact input type to show in `onStartInput()`, but the actual place to perform switching to this new keyboard type should be `onStartInputView()`.

See how the sample `SoftKeyboard` application handles that functionality.

@Override public void onStartInput(EditorInfo attribute, boolean restarting) {
    super.onStartInput(attribute, restarting);

    ...

    // We are now going to initialize our state based on the type of
    // text being edited.
    switch (attribute.inputType & InputType.TYPE_MASK_CLASS) {
        case InputType.TYPE_CLASS_NUMBER:
        case InputType.TYPE_CLASS_DATETIME:
            mCurKeyboard = mSymbolsKeyboard;
            break;

        case InputType.TYPE_CLASS_PHONE:
            mCurKeyboard = mSymbolsKeyboard;
            break;

        case InputType.TYPE_CLASS_TEXT:
            mCurKeyboard = mQwertyKeyboard;
            ...
            break;

        default:
            // For all unknown input types, default to the alphabetic
            // keyboard with no special features.
            mCurKeyboard = mQwertyKeyboard;
    }

}

@Override public void onStartInputView(EditorInfo attribute, boolean restarting) {
    super.onStartInputView(attribute, restarting);
    // Apply the selected keyboard to the input view.
    setLatinKeyboard(mCurKeyboard);
    ...
}

private void setLatinKeyboard(LatinKeyboard nextKeyboard) {
    final boolean shouldSupportLanguageSwitchKey =
            mInputMethodManager.shouldOfferSwitchingToNextInputMethod(getToken());
    nextKeyboard.setLanguageSwitchKeyVisibility(shouldSupportLanguageSwitchKey);
    mInputView.setKeyboard(nextKeyboard);
}

Problem

I am running into an issue that I can't figure out. I wrote a simple custom IME keyboard based on this sample. It basically has two custom keyboards, one for letters and one for numbers. They use different layouts. However, when I add two `EditText` controls one for text and one for numbers, the keyboard does not refresh to the type it belongs. What I mean is that if I select the `EditText` with `inputType="text"` first, the QWERTY keyboard layout comes up. But then when I select the second `EditText` with `inputType="number"` the QWERTY keyboard shows up again. However it is supposed to load a different layout for numbers that is wired into the code. In other words, here's the test activity layout: Now if I select the "Text" field, the QWERTY keyboard comes up as below: However, if I select the "Number" filed, the QWERTY keyboard still shows up which is wrong. The expected behavior would be this keyboard to show up. Here's the code for the CustomIME and I tried to use `postInvalidate()` on the view, pre-load all layouts during `onInitializeInterface()` but nothing worked. It never switches to the number's layout properly ``` public class CustomIME extends InputMethodService implements KeyboardView.OnKeyboardActionListener { public static final String CUSTOM_IME = "CUSTOM_IME"; private KeyboardView mKeyboardView; private Keyboard mKeyboardCurrent; private KeyboardType mKeyboardTypeCurrent = KeyboardType.QWERTY_LETTERS; private boolean mCAPs = false; enum KeyboardType { QWERTY_LETTERS, NUMBERS } @Override public View onCreateInputView() { loadCurrentKeyboard(); mKeyboardView = (KeyboardView) getLayoutInflater().inflate(R.layout.custom_ime_keyboard, null); mKeyboardView.setBackgroundResource(R.drawable.btn_gradient); mKeyboardView.setOnKeyboardActionListener(this); if (mKeyboardCurrent != null) { mKeyboardView.setKeyboard(mKeyboardCurrent); } return mKeyboardView; } @Override public void onInitializeInterface() { // tried loading everything here but did not make a difference } private void loadCurrentKeyboard() { if (mKeyboardTypeCurrent == KeyboardType.QWERTY_LETTERS) { mKeyboardCurrent = new Keyboard(getApplicationContext(), R.xml.custom_ime_qwerty); } else if (mKeyboardTypeCurrent == KeyboardType.NUMBERS) { mKeyboardCurrent = new Keyboard(getApplicationContext(), R.xml.custom_ime_number); } else { Log.e(CUSTOM_IME, "Invalid keyboard type"); } } @Override public void onStartInput(EditorInfo attribute, boolean restarting) { super.onStartInput(attribute, restarting); switch (attribute.inputType & InputType.TYPE_MASK_CLASS) { case InputType.TYPE_CLASS_NUMBER: boolean signed = (attribute.inputType & InputType.TYPE_NUMBER_FLAG_SIGNED) != 0; boolean decimal = (attribute.inputType & InputType.TYPE_NUMBER_FLAG_DECIMAL) != 0; // set default mKeyboardTypeCurrent = KeyboardType.QWERTY_LETTERS; if (!signed && !decimal) { mKeyboardTypeCurrent = KeyboardType.NUMBERS; } break; case InputType.TYPE_CLASS_TEXT: default: mKeyboardTypeCurrent = KeyboardType.QWERTY_LETTERS; } // This did not make a difference if (mKeyboardView != null) { mKeyboardView.postInvalidate(); } } @Override public void onKey(int primaryCode, int[] keyCodes) { InputConnection inputConnection = getCurrentInputConnection(); switch (primaryCode) { default: char asciiCode = (char) primaryCode; if (Character.isLetter(asciiCode) && mCAPs) { asciiCode = Character.toUpperCase(asciiCode); } inputConnection.commitText(String.valueOf(asciiCode), 1); } } } ``` And the layouts: custom_ime_keyboard.xml: ``` <?xml version="1.0" encoding="UTF-8"?> <android.inputmethodservice.KeyboardView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/custom_ime_keyboard_id1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:keyPreviewLayout="@layout/custom_ime_preview" /> ``` activity_main.xml ``` <LinearLayout android:id="@+id/layout1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_centerHorizontal="true" android:layout_margin="10dp" android:orientation="horizontal"> <EditText android:id="@+id/edit1" android:layout_width="100dp" android:layout_height="60dp" android:inputType="text" android:hint="Text" android:padding="10dp" android:textSize="12sp" /> <EditText android:id="@+id/edit2" android:layout_width="100dp" android:layout_height="60dp" android:hint="Number" android:inputType="number" android:padding="10dp" android:textSize="12sp" /> </LinearLayout> ``` Finally the keyboard layouts (custom_ime_qwerty.xml, and custom_ime_number.xml). ``` <Keyboard xmlns:android="http://schemas.android.com/apk/res/android" android:keyHeight="64dp" android:keyWidth="9%p"> <!--1st row--> <Row android:rowEdgeFlags="top"> <Key android:codes="113" android:keyEdgeFlags="left" android:keyLabel="q" /> <Key android:codes="119" android:keyLabel="w" /> <Key android:codes="101" android:keyLabel="e" /> <Key android:codes="114" android:keyLabel="r" /> etc... <Keyboard xmlns:android="http://schemas.android.com/apk/res/android" android:keyHeight="64dp" android:keyWidth="20%p" android:label="number" android:verticalGap="0px"> <!--1st row--> <Row android:rowEdgeFlags="top"> <Key android:codes="49" android:keyEdgeFlags="left" android:keyLabel="1" /> <Key android:codes="50" android:keyLabel="2" /> <Key android:codes="51" android:keyLabel="3" /> ```

Original source