How the Dynamic Key State System Updates Keyboard Keys at Runtime in JapaneseKeyboard
The dynamic key state system in JapaneseKeyboard uses a runtime map of key IDs to state indices that updates individual key labels, actions, and icons without rebuilding the entire layout.
The Japanese Keyboard (kazumaproject/japanesekeyboard) implements a sophisticated dynamic key state system that enables individual keys to change their appearance and behavior while the keyboard remains visible. This mechanism powers features like toggling Enter key functions, switching between Dakuten and Katakana modes, and modifying spacebar behavior on demand. Understanding this system reveals how the repository achieves fluid UI updates without expensive full-layout reconstructions.
Architecture of the Dynamic Key State System
The system operates through three coordinated layers: static layout definition, runtime state mapping, and view-level mutation.
Static Layout Definition with Dynamic States
Each key that supports runtime changes is defined in KeyboardDefaultLayouts.kt with a list of possible dynamic states stored as FlickAction objects. When createFinalLayout generates the base layout, keys include a dynamicStates property containing all potential variations.
For example, the Enter key defines multiple behavioral states:
private val enterKeyStates = listOf(
FlickAction.Action(KeyAction.NewLine, "改行"),
FlickAction.Action(KeyAction.Confirm, drawableResId = R.drawable.baseline_arrow_right_alt_24),
FlickAction.Action(KeyAction.Enter, drawableResId = R.drawable.baseline_keyboard_return_24),
// Additional variations...
)
The applyKeyState helper method in KeyboardDefaultLayouts.kt handles the actual substitution:
private fun applyKeyState(baseLayout: KeyboardLayout, keyId: String, stateIndex: Int): KeyboardLayout
This function locates the target key by keyId, selects the FlickAction at stateIndex (defaulting to the first state if invalid), and constructs a new KeyData instance with the updated label, action, and drawable resource.
Runtime State Selection via dynamicKeyStates Map
When users interact with mode toggles, IMEService.kt constructs a dynamicKeyStates map that tracks which variation each mutable key should display. This map uses string key IDs as identifiers and zero-based integers as state indices.
A typical runtime configuration looks like:
dynamicKeyStates = mapOf(
"enter_key" to currentEnterKeyIndex,
"dakuten_toggle_key" to currentDakutenKeyIndex,
"katakana_toggle_key" to currentKatakanaKeyIndex,
"space_convert_key" to currentSpaceKeyIndex
)
The system passes this map to KeyboardDefaultLayouts.createFinalLayout, which iterates through the entries and applies each state change via applyKeyState. Alternatively, for incremental updates, the map can be stored and applied directly to the view layer.
View-Level Application with updateDynamicKey
FlickKeyboardView.kt implements updateDynamicKey(keyId, stateIndex) to mutate the visible keyboard without regenerating the entire layout. This method performs a live lookup of the key in the current KeyboardLayout, extracts the corresponding FlickAction from dynamicStates, and swaps the KeyData instance in the layout's key list.
The view then triggers a targeted redraw using notifyItemChanged on the underlying adapter, ensuring only the affected key refreshes while neighboring keys remain stable.
Core Implementation Files
The dynamic key state system spans three critical files:
-
custom_keyboard/src/main/java/com/kazumaproject/custom_keyboard/layout/KeyboardDefaultLayouts.kt– Generates base layouts, maintainsdynamicStateslists for each mutable key, and implementsapplyKeyStatefor state substitution. -
custom_keyboard/src/main/java/com/kazumaproject/custom_keyboard/view/FlickKeyboardView.kt– Hosts the visual keyboard component and implementsupdateDynamicKeyto perform granular UI updates. -
app/src/main/java/com/kazumaproject/markdownhelperkeyboard/ime_service/IMEService.kt– Coordinates runtime state changes, constructs thedynamicKeyStatesmap based on user input, and triggers layout updates.
Practical Code Examples
Creating a Layout with Predefined States
Initialize the keyboard with specific key variations using the dynamicKeyStates parameter:
val dynamicStates = mapOf(
"enter_key" to 2, // Select the third Enter key variant
"dakuten_toggle_key" to 1 // Select the second Dakuten option
)
val layout = KeyboardDefaultLayouts.createFinalLayout(
mode = KeyboardInputMode.HIRAGANA,
dynamicKeyStates = dynamicStates,
inputLayoutType = "toggle",
inputStyle = "default",
isDeleteFlickEnabled = true
)
This produces a KeyboardLayout instance with the specified variants already applied to the Enter and Dakuten keys.
Updating a Single Key at Runtime
When users toggle the Enter key mode, update only that specific key:
// Inside IMEService or UI controller
customLayoutDefault.updateDynamicKey(
keyId = "enter_key",
stateIndex = newEnterKeyIndex // 0 = NewLine, 1 = Confirm, 2 = Enter, etc.
)
The view immediately refreshes the key's label and drawable without disrupting other keys.
Performing a Full Layout Refresh
After multiple state changes, regenerate the complete layout with updated states:
val newDynamicMap = mapOf(
"enter_key" to currentEnterKeyIndex,
"dakuten_toggle_key" to currentDakutenKeyIndex,
"space_convert_key" to currentSpaceKeyIndex,
"katakana_toggle_key" to currentKatakanaKeyIndex
)
val refreshedLayout = KeyboardDefaultLayouts.createFinalLayout(
mode = customKeyboardMode,
dynamicKeyStates = newDynamicMap,
inputLayoutType = sumireInputKeyLayoutType ?: "toggle",
inputStyle = sumireInputStyle ?: "default",
isDeleteFlickEnabled = isDeleteLeftFlickPreference ?: true
)
customLayoutDefault.setKeyboard(refreshedLayout)
Summary
- The dynamic key state system uses a
Map<String, Int>structure (dynamicKeyStates) to track which variation each key should display. KeyboardDefaultLayouts.createFinalLayoutaccepts this map and applies states viaapplyKeyState, generating a complete layout with substitutedKeyDataobjects.FlickKeyboardView.updateDynamicKeyenables surgical updates to individual keys, triggering targeted adapter refreshes rather than full layout rebuilds.- Keys define their mutable states as lists of
FlickActionobjects indynamicStates, allowing extensible behavior modification without structural changes. - The separation between layout generation (
KeyboardDefaultLayouts) and view mutation (FlickKeyboardView) facilitates testing and maintenance.
Frequently Asked Questions
How does the system know which key variant to display?
The system looks up the key ID in the dynamicKeyStates map, retrieves the integer state index, and selects the corresponding FlickAction from the key's dynamicStates list. If the index is out of bounds, it defaults to the first state in the list.
Can I add new dynamic states to existing keys without modifying core files?
Yes. Since dynamicStates are defined as mutable lists within KeyboardDefaultLayouts.kt, you can append new FlickAction entries to any key's state list. The applyKeyState method automatically handles any valid index within the expanded list.
What is the performance impact of calling updateDynamicKey frequently?
The impact is minimal because updateDynamicKey in FlickKeyboardView.kt calls notifyItemChanged on the underlying RecyclerView adapter, which triggers a partial bind for only the affected key view. This avoids the layout inflation and measurement costs associated with full keyboard rebuilds.
How does IMEService coordinate state changes across different input modes?
IMEService.kt maintains current state indices as instance variables (such as currentEnterKeyIndex and currentDakutenKeyIndex), updates these values when users activate toggles, and reconstructs the dynamicKeyStates map before passing it to either createFinalLayout or updateDynamicKey depending on whether a full refresh or incremental update is required.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →