androidandroid-softkeyboardkeyboard-layout

How to properly copy deprecated KeyboardView.java and Keyboard.java to android project?


I'm attempting to follow the suggestions from Google regarding moving forward with KeyboardView.java here: https://developer.android.com/reference/android/inputmethodservice/KeyboardView

I've copied and tweaked KeyboardView.java, Keyboard.java and the portions in com.android.internal.R that are noted as deprecated, and created a new res/values/styles.xml file with the definition for the styles.

Everything builds just fine, but when it gets to the point of displaying the custom keyboard, I get a null pointer exception. I can see the issue is in the copied KeyboardView code here (the count of a.getIndexCount() is 0, so mKeyBackground never gets set and results in the null exception when it calls "mKeyBackground.getPadding(mPadding)"). I can't figure out what I'm missing?:

    public KeyboardView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        TypedArray a =
                context.obtainStyledAttributes(
                        attrs, R.styleable.KeyboardView, defStyle, 0);
        LayoutInflater inflate =
                (LayoutInflater) context
                        .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        int previewLayout = 0;
        int keyTextSize = 0;

// n is set to 0
        int n = a.getIndexCount();

// since n = 0, the loop is never entered
        for (int i = 0; i < n; i++) {
            int attr = a.getIndex(i);
            switch (attr) {
                case R.styleable.KeyboardView_keyBackground:

// mKeyBackground is supposed to be set but isn't because n = 0
                    mKeyBackground = a.getDrawable(attr);
                    break;
                case R.styleable.KeyboardView_verticalCorrection:
                    mVerticalCorrection = a.getDimensionPixelOffset(attr, 0);
                    break;
                case R.styleable.KeyboardView_keyPreviewLayout:
                    previewLayout = a.getResourceId(attr, 0);
                    break;
                case R.styleable.KeyboardView_keyPreviewOffset:
                    mPreviewOffset = a.getDimensionPixelOffset(attr, 0);
                    break;
                case R.styleable.KeyboardView_keyPreviewHeight:
                    mPreviewHeight = a.getDimensionPixelSize(attr, 80);
                    break;
                case R.styleable.KeyboardView_keyTextSize:
                    mKeyTextSize = a.getDimensionPixelSize(attr, 18);
                    break;
                case R.styleable.KeyboardView_keyTextColor:
                    mKeyTextColor = a.getColor(attr, 0xFF000000);
                    break;
                case R.styleable.KeyboardView_labelTextSize:
                    mLabelTextSize = a.getDimensionPixelSize(attr, 14);
                    break;
                case R.styleable.KeyboardView_popupLayout:
                    mPopupLayout = a.getResourceId(attr, 0);
                    break;
            }
        }

        // Get the settings preferences
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
        mVibrateOn = sp.getBoolean(PREF_VIBRATE_ON, mVibrateOn);
        mSoundOn = sp.getBoolean(PREF_SOUND_ON, mSoundOn);
        mProximityCorrectOn = sp.getBoolean(PREF_PROXIMITY_CORRECTION, true);

        mPreviewPopup = new PopupWindow(context);
        if (previewLayout != 0) {
            mPreviewText = (TextView) inflate.inflate(previewLayout, null);
            mPreviewTextSizeLarge = (int) mPreviewText.getTextSize();
            mPreviewPopup.setContentView(mPreviewText);
            mPreviewPopup.setBackgroundDrawable(null);
        } else {
            mShowPreview = false;
        }

        mPreviewPopup.setTouchable(false);

        mPopupKeyboard = new PopupWindow(context);
        mPopupKeyboard.setBackgroundDrawable(null);
        //mPopupKeyboard.setClippingEnabled(false);

        mPopupParent = this;
        //mPredicting = true;

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setTextSize(keyTextSize);
        mPaint.setTextAlign(Align.CENTER);
        mPadding = new Rect(0, 0, 0, 0);
        mMiniKeyboardCache = new HashMap<Key,View>();

// Program crashes here from null pointer since mKeyBackground isn't set
        mKeyBackground.getPadding(mPadding);

        resetMultiTap();
        initGestureDetector();
    }

Here is the styles.xml file:

styles.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="KeyboardView" parent="android:Widget">
        <item name="android:background">@color/black</item>
        <item name="keyBackground">@drawable/background</item>
        <item name="keyTextSize">22sp</item>
        <item name="keyTextColor">@color/white</item>
        <item name="keyPreviewLayout">@layout/preview</item>
        <item name="keyPreviewOffset">-12dp</item>
        <item name="keyPreviewHeight">80dp</item>
        <item name="labelTextSize">14sp</item>
        <item name="popupLayout">@layout/preview</item>
        <item name="verticalCorrection">-10dp</item>
        <!-- <item name="shadowColor">@android:color/transparent</item> -->
        <!-- <item name="shadowRadius">2.75</item> -->
    </style>
</resources>

Here is my copy of the original com.android.internal.R (in values/attrs.xml)

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="KeyboardView">
        <!-- Default KeyboardView style. -->
        <attr name="keyboardViewStyle" format="reference" />
        <!-- Image for the key. This image needs to be a StateListDrawable, with the following
             possible states: normal, pressed, checkable, checkable+pressed, checkable+checked,
             checkable+checked+pressed. -->
        <attr name="keyBackground" format="reference" />
        <!-- Size of the text for character keys. -->
        <attr name="keyTextSize" format="dimension" />
        <!-- Size of the text for custom keys with some text and no icon. -->
        <attr name="labelTextSize" format="dimension" />
        <!-- Color to use for the label in a key. -->
        <attr name="keyTextColor" format="color" />
        <!-- Layout resource for key press feedback. -->
        <attr name="keyPreviewLayout" format="reference" />
        <!-- Vertical offset of the key press feedback from the key. -->
        <attr name="keyPreviewOffset" format="dimension" />
        <!-- Height of the key press feedback popup. -->
        <attr name="keyPreviewHeight" format="dimension" />
        <!-- Amount to offset the touch Y coordinate by, for bias correction. -->
        <attr name="verticalCorrection" format="dimension" />
        <!-- Layout resource for popup keyboards. -->
        <attr name="popupLayout" format="reference" />
        <!-- <attr name="shadowColor" /> -->
        <!-- <attr name="shadowRadius" /> -->
    </declare-styleable>
    <declare-styleable name="KeyboardViewPreviewState">
        <!-- State for {@link android.inputmethodservice.KeyboardView KeyboardView}
                key preview background. -->
        <attr name="state_long_pressable" format="boolean" />
    </declare-styleable>
    <declare-styleable name="Keyboard">
        <!-- Default width of a key, in pixels or percentage of display width. -->
        <attr name="keyWidth" format="dimension|fraction" />
        <!-- Default height of a key, in pixels or percentage of display width. -->
        <attr name="keyHeight" format="dimension|fraction" />
        <!-- Default horizontal gap between keys. -->
        <attr name="horizontalGap" format="dimension|fraction" />
        <!-- Default vertical gap between rows of keys. -->
        <attr name="verticalGap" format="dimension|fraction" />
    </declare-styleable>
    <declare-styleable name="Keyboard_Row">
        <!-- Row edge flags. -->
        <attr name="rowEdgeFlags">
            <!-- Row is anchored to the top of the keyboard. -->
            <flag name="top" value="4" />
            <!-- Row is anchored to the bottom of the keyboard. -->
            <flag name="bottom" value="8" />
        </attr>
        <!-- Mode of the keyboard. If the mode doesn't match the
             requested keyboard mode, the row will be skipped. -->
        <attr name="keyboardMode" format="reference" />
    </declare-styleable>
    <declare-styleable name="Keyboard_Key">
        <!-- The unicode value or comma-separated values that this key outputs. -->
        <attr name="codes" format="integer|string" />
        <!-- The XML keyboard layout of any popup keyboard. -->
        <attr name="popupKeyboard" format="reference" />
        <!-- The characters to display in the popup keyboard. -->
        <attr name="popupCharacters" format="string" />
        <!-- Key edge flags. -->
        <attr name="keyEdgeFlags">
            <!-- Key is anchored to the left of the keyboard. -->
            <flag name="left" value="1" />
            <!-- Key is anchored to the right of the keyboard. -->
            <flag name="right" value="2" />
        </attr>
        <!-- Whether this is a modifier key such as Alt or Shift. -->
        <attr name="isModifier" format="boolean" />
        <!-- Whether this is a toggle key. -->
        <attr name="isSticky" format="boolean" />
        <!-- Whether long-pressing on this key will make it repeat. -->
        <attr name="isRepeatable" format="boolean" />
        <!-- The icon to show in the popup preview. -->
        <attr name="iconPreview" format="reference" />
        <!-- The string of characters to output when this key is pressed. -->
        <attr name="keyOutputText" format="string" />
        <!-- The label to display on the key. -->
        <attr name="keyLabel" format="string" />
        <!-- The icon to display on the key instead of the label. -->
        <attr name="keyIcon" format="reference" />
        <!-- Mode of the keyboard. If the mode doesn't match the
             requested keyboard mode, the key will be skipped. -->
        <attr name="keyboardMode" />
    </declare-styleable>

</resources>

Solution

  • The KeyboardView style, which contains the default values, isn't being used.

    While you could either set each value manually in the XML layout (as you describe in the comment), like this

    <your.package.name.KeyboardView
        …
        app:keyBackground="@drawable/bg_container"
        …
        />
    

    or set the whole style directly, like this

    <your.package.name.KeyboardView
        …
        style="@style/KeyboardView"
        …
        />
    

    both require setting those values every time the View is used.

    A better approach would be to set the defaults in the KeyboardView.java:

    public KeyboardView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        TypedArray a = context.obtainStyledAttributes(attrs,
                           R.styleable.KeyboardView,
                           R.attr.keyboardViewStyle,
                           R.style.KeyboardView);
        // …
    }