I will give a detailed guide on creating one without using any deprecated APIs.
First, let's start with the InputMethodService
implementation, you can use the onCreateInputView()
method to display the main keyboard layout. It needs you to return a View
that will be displayed on the screen when the service starts. So you can create a Keyboard layout in res/layout/keyboard_view.xml
:
public class ControlBoard extends InputMethodService {
View mainKeyboardView;
boolean shiftPressed = false; // shiftPressed and metaState will be used for tracking shift key
int metaState = 0;
@Override
public View onCreateInputView() {
mainKeyboardView = getLayoutInflater().inflate(R.layout.keyboard_view, null);
return mainKeyboardView;
}
.....
To create the layout, you can use whatever you want, but I will show you how to do it with LinearLayout
s. You can use one LinearLayout as the root element with orientation:vertical
, and then nest more LinearLayouts with orientation:horizontal
to create rows, with Button
s. For storing the keycodes, you can use the android:tag
attribute in the Buttons. For styles, you can apply theme
to the root element or style
to each Button.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/keyboard_view"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="@drawable/keyboard_bg"
>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="48dp"
>
<!--
You can define the keycodes in "android:tag" using Unicode scheme or KeyEvent.KEYCODE_ scheme.
I will use the latter because it is easier to implement and allows greater features,
such as handling all special keys simultaneously and using meta keys like ctrl or alt. -->
<Button style="@style/NormalKeyStyles" android:tag="SHIFT" android:text="SHFT" />
<Button style="@style/NormalKeyStyles" android:tag="G" android:text="g" />
<Button style="@style/NormalKeyStyles" android:tag="3" android:text="3" />
<Button style="@style/NormalKeyStyles" android:tag="APOSTROPHE" android:text="\'" />
<Button style="@style/NormalKeyStyles" android:tag="DEL" android:text="<\-" />
<Button style="@style/NormalKeyStyles" android:tag="ENTER" android:text="<\-\|" />
</LinearLayout>
<!-- Add more rows with the same scheme. -->
</LinearLayout>
Reference: KeyEvent.KEYCODE_
res/values/styles.xml
:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="NormalKeyStyles" parent="TextAppearance.AppCompat">
<item name="android:layout_width">0dp</item>
<item name="android:layout_weight">1</item>
<item name="android:layout_height">match_parent</item>
<!-- key_bg_selector is a ColorStateList which specifies different background colors for different states of button. -->
<item name="android:background">@color/key_bg_selector</item>
<item name="android:textColor">@color/white</item>
<item name="android:textSize">24sp</item>
<item name="android:textAllCaps">false</item>
<item name="android:onClick">onKeyClick</item>
</style>
</resources>
As you can see, onKeyClick()
method is bound to each Button
via the NormalKeyStyles
, it will handle the click events. Let's see how to handle them.
We can use the View.getTag()
method to extract the keycode we assigned to the keys in xml file. And then, we can use InputConnection.sendKeyEvent()
to send the event to the client.
public void onKeyClick(View pressedKey) {
InputConnection inputConnection = getCurrentInputConnection();
if (inputConnection == null) return;
long now = System.currentTimeMillis();
String keyType = (String) pressedKey.getTag();
}
switch(keyType) {
case "SHIFT":
shiftPressed = !shiftPressed;
default:
// check if shift is active and then set metaState accordingly for sending the KeyEvent
if (shiftPressed) {
metaState = KeyCode.META_SHIFT_MASK;
} else {
metaState = 0;
}
try {
// keycode is retrieved from the KeyEvent.KEYCODE_keyname variables using Reflection API.
int keycode = KeyEvent.class.getField("KEYCODE_" + keyType).getInt(null), 0, metaState);
// you can also use commitText() if you want, but you will have to add extra cases for special keys like ENTER, DEL, etc.
// This approach handles all of them in one case
inputConnection.sendKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keycode);
} catch (IllegalAccessException | NoSuchFieldException e) {
//
}
}
}
}
And now you are good to go, but I will like to add some notes that might be helpful to you:
You can change the layout after initialization using setInputView()
, so you may add a key with tag "CHANGE_LAYOUT" and add a case for it in the onKeyClick()
method to switch between different keyboards.
If you want to add extra symbols in the layout, which has no direct KeyEvent
keycode field, such as $ sign, or emojis or other unicode characters, you can use SHIFT
mask there if available (like SHIFT + 4
produces $), or you can use commitText()
for inputting that.
For adding key preview popups, you can use a PopupWindow
with a TextView
and display it on top of the key when touch starts, and dismiss it when touch ends, with setText()
to change the PopupWindow
text.
For a more sophisticated implementation, you can see my Control Board source on GitHub.