Architecture of the Flick Input Controller and Popup View System in JapaneseKeyboard
The flick input controller and popup view system in JapaneseKeyboard employs a three-layer architecture that decouples gesture detection from visual feedback, allowing controllers to calculate flick directions via touch events while dedicated popup views handle the floating bubble UI.
The JapaneseKeyboard repository implements a sophisticated flick-based input mechanism for Japanese character entry on Android. Understanding the flick input controller and popup view system architecture reveals how gesture detection, state management, and visual feedback are separated into distinct layers to support multiple input modes including standard flicks, grid layouts, and custom angle detection.
Core Architecture Layers
The system is organized into three distinct layers that communicate through well-defined interfaces.
Listener and Model Layer
The foundation of the system is defined in FlickListener.kt located at core/src/main/java/com/kazumaproject/core/domain/listener/FlickListener.kt. This interface establishes the contract for flick events across the application.
UI components like TenKey register a FlickListener to receive callbacks via onFlick(gestureType, key, char?). The listener abstracts the input logic from the consumer, allowing any view to receive flick events without knowing the specifics of how gestures are detected or mapped to characters.
Controllers and Input Logic
Three specialized controllers interpret touch gestures, calculate directions using geometric operations, and drive the popup windows:
-
StandardFlickInputController (
custom_keyboard/src/main/java/com/kazumaproject/custom_keyboard/controller/StandardFlickInputController.kt): Handles basic four-directional flicks (up, down, left, right) plus tap. It records touch start positions and usessqrtandatan2calculations onACTION_MOVEevents to determineFlickDirection. -
GridFlickInputController (
custom_keyboard/src/main/java/com/kazumaproject/custom_keyboard/controller/GridFlickInputController.kt): Detects long-press gestures to trigger a grid popup showing multiple candidates simultaneously. It manages apopupMapof directional windows and handles extended directions likeUP_LEFT_FAR. -
CustomAngleFlickController (
custom_keyboard/src/main/java/com/kazumaproject/custom_keyboard/controller/CustomAngleFlickController.kt): Supports arbitrary angle ranges and map page switching (e.g., flickingUP_RIGHTto switch pages), usingCustomAngleFlickPopupViewfor rectangular orbit UIs.
Each controller follows the same lifecycle: (a) register a touch listener on the key view via attach(view, map), (b) record the start point on ACTION_DOWN, (c) calculate direction on ACTION_MOVE, (d) update popup visuals, and (e) notify the registered FlickListener on ACTION_UP.
Popup-View System
The visual feedback layer consists of specialized views and extension functions that render floating bubbles:
-
PopupWindowExtension.kt (
tenkey/src/main/java/com/kazumaproject/tenkey/extensions/PopupWindowExtension.kt): Provides helper functions likesetPopUpWindowFlickRight,setPopUpWindowFlickLeft,setPopUpWindowFlickTop, andsetPopUpWindowFlickBottom. These configurePopupWindowinstances withKeyWindowLayout(arrow shape, size, direction) and handle positioning viashowAsDropDownorshowAtLocation. -
StandardFlickPopupView (
custom_keyboard/src/main/java/com/kazumaproject/custom_keyboard/view/StandardFlickPopupView.kt): Draws circular popups with single-character labels and optional 5-character matrix displays for tap directions. -
DirectionalKeyPopupView (
custom_keyboard/src/main/java/com/kazumaproject/custom_keyboard/view/DirectionalKeyPopupView.kt): Used by the grid controller to render individual directional bubbles. -
FlickGridPopupView (
custom_keyboard/src/main/java/com/kazumaproject/custom_keyboard/view/FlickGridPopupView.kt): Renders a 3×3 grid layout of candidates when long-press is detected.
These views allow runtime theme changes and calculate their own dimensions, while controllers remain responsible only for instantiation and dismissal timing.
Flick Input Flow and Interaction
A typical interaction flows through the system as follows:
-
Setup: A
TenKeyinstance creates a controller and registers aFlickListenerviasetOnFlickListener. The controller receives a character map linkingFlickDirectionvalues to specific characters. -
Touch Down: The controller's
attach(view, map)method stores theanchorViewand displays an initial center popup usingsetPopUpWindowCenteror the standard flick popup view. -
Movement: As the finger moves, the controller calculates the
FlickDirectionusing vector math (sqrt/atan2). It calls the appropriate popup extension (e.g.,setPopUpWindowFlickRight) to reposition the window and updates the inner view's text via methods likesetTextFlick*to reflect the character under the current direction. -
Release: On
ACTION_UP, the controller determines the final direction, looks up the corresponding character fromcharacterMap, invokeslistener?.onFlick(finalDirection, character), and dismisses allPopupWindowinstances.
Implementation Examples
Registering a Flick Listener on TenKey
val tenKey = findViewById<TenKey>(R.id.ten_key)
// Set the listener that receives flick events
tenKey.setOnFlickListener(object : FlickListener {
override fun onFlick(gestureType: GestureType, key: Key, char: Char?) {
// gestureType indicates Tap, FlickLeft, FlickTop, etc.
// char is the character mapped to that direction
}
})
// Optional: Enable visual flick guides
tenKey.setFlickGuideEnabled(true)
Relevant source: TenKey.setOnFlickListener and setFlickGuideEnabled in tenkey/src/main/java/com/kazumaproject/tenkey/TenKey.kt.
Attaching a Standard Flick Controller
val button = findViewById<AppCompatButton>(R.id.key_1)
// Map directions to characters
val flickMap = mapOf(
FlickDirection.TAP to "あ",
FlickDirection.UP to "ぁ",
FlickDirection.DOWN to "あ",
FlickDirection.LEFT to "か",
FlickDirection.RIGHT to "が"
)
val flickCtrl = StandardFlickInputController(this)
flickCtrl.attach(button, flickMap)
flickCtrl.listener = object : StandardFlickInputController.StandardFlickListener {
override fun onFlick(character: String) {
// Insert character into text field
}
}
Relevant source: StandardFlickInputController.attach in custom_keyboard/src/main/java/com/kazumaproject/custom_keyboard/controller/StandardFlickInputController.kt.
Using Popup Extensions Directly
val popup = PopupWindow(
layout,
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
false
)
val keyWindow = KeyWindowLayout(context)
popup.setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
// Position a right-flick bubble relative to anchorView
popup.setPopUpWindowFlickRight(context, keyWindow, anchorView)
Relevant source: Extension functions in tenkey/src/main/java/com/kazumaproject/tenkey/extensions/PopupWindowExtension.kt.
Implementing Long-Press Grid Flick
val keyView = findViewById<View>(R.id.key_5)
// Initialize with sensitivity threshold
val gridCtrl = GridFlickInputController(this, flickSensitivity = 80)
val charMap = mapOf(
FlickDirection.TAP to "ん",
FlickDirection.UP to "ん",
FlickDirection.DOWN to "ゔ",
FlickDirection.UP_LEFT_FAR to "ば",
FlickDirection.UP_RIGHT_FAR to "ぱ"
)
gridCtrl.attach(keyView, charMap)
gridCtrl.listener = object : GridFlickInputController.GridFlickListener {
override fun onFlick(character: String, isFlick: Boolean) {
// isFlick distinguishes directional flicks from taps
}
}
Relevant source: GridFlickInputController.attach and longPressJob handling in custom_keyboard/src/main/java/com/kazumaproject/custom_keyboard/controller/GridFlickInputController.kt.
Summary
- The flick input controller and popup view system separates concerns into three layers: listener contracts, gesture controllers, and visual popup views.
- Controllers like
StandardFlickInputControlleruse geometric calculations (sqrt/atan2) on touch events to determineFlickDirectionwhile managingPopupWindowlifecycle. - The popup system provides specialized views (
StandardFlickPopupView,FlickGridPopupView) and extension functions (setPopUpWindowFlickRight, etc.) for positioning and theming feedback bubbles. - This modular architecture allows independent replacement of gesture algorithms or visual styles without affecting the other layer.
Frequently Asked Questions
How does the controller determine which direction the user flicked?
The controller captures the touch start coordinates on ACTION_DOWN and calculates the delta on ACTION_MOVE using sqrt and atan2 to determine the angle and distance. If the distance exceeds a threshold, the angle maps to a FlickDirection enum value (such as UP, DOWN, LEFT, RIGHT, or diagonal variants).
Can I customize the appearance of the flick popup bubbles?
Yes. The popup visual style is controlled by classes like StandardFlickPopupView and DirectionalKeyPopupView, which extend Android's view system. You can modify the background shapes, text colors, and arrow directions in these files without changing the gesture detection logic in the controllers.
What is the difference between StandardFlickInputController and GridFlickInputController?
StandardFlickInputController handles single-direction flicks from a center point, showing one popup at a time. GridFlickInputController detects long-presses to display a 3×3 grid of candidates using FlickGridPopupView, allowing selection from multiple directions simultaneously. The grid controller manages multiple PopupWindow instances in a popupMap rather than a single window.
Where is the flick listener interface defined?
The FlickListener interface is defined in core/src/main/java/com/kazumaproject/core/domain/listener/FlickListener.kt. It declares the onFlick(gestureType, key, char?) method that all controllers invoke when a gesture completes, ensuring consistent communication between the input logic and UI components like TenKey.
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 →