# Architecture of the Flick Input Controller and Popup View System in JapaneseKeyboard

> Explore the three-layer architecture of the flick input controller and popup view system in JapaneseKeyboard. Decouple gesture detection from UI for efficient flick input.

- Repository: [Kazu/japanesekeyboard](https://github.com/kazumaproject/japanesekeyboard)
- Tags: architecture
- Published: 2026-03-05

---

**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`](https://github.com/kazumaproject/japanesekeyboard/blob/main/FlickListener.kt) located at [`core/src/main/java/com/kazumaproject/core/domain/listener/FlickListener.kt`](https://github.com/kazumaproject/japanesekeyboard/blob/main/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`](https://github.com/kazumaproject/japanesekeyboard/blob/main/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 uses `sqrt` and `atan2` calculations on `ACTION_MOVE` events to determine `FlickDirection`.

- **GridFlickInputController** ([`custom_keyboard/src/main/java/com/kazumaproject/custom_keyboard/controller/GridFlickInputController.kt`](https://github.com/kazumaproject/japanesekeyboard/blob/main/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 a `popupMap` of directional windows and handles extended directions like `UP_LEFT_FAR`.

- **CustomAngleFlickController** ([`custom_keyboard/src/main/java/com/kazumaproject/custom_keyboard/controller/CustomAngleFlickController.kt`](https://github.com/kazumaproject/japanesekeyboard/blob/main/custom_keyboard/src/main/java/com/kazumaproject/custom_keyboard/controller/CustomAngleFlickController.kt)): Supports arbitrary angle ranges and map page switching (e.g., flicking `UP_RIGHT` to switch pages), using `CustomAngleFlickPopupView` for 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`](https://github.com/kazumaproject/japanesekeyboard/blob/main/tenkey/src/main/java/com/kazumaproject/tenkey/extensions/PopupWindowExtension.kt)): Provides helper functions like `setPopUpWindowFlickRight`, `setPopUpWindowFlickLeft`, `setPopUpWindowFlickTop`, and `setPopUpWindowFlickBottom`. These configure `PopupWindow` instances with `KeyWindowLayout` (arrow shape, size, direction) and handle positioning via `showAsDropDown` or `showAtLocation`.

- **StandardFlickPopupView** ([`custom_keyboard/src/main/java/com/kazumaproject/custom_keyboard/view/StandardFlickPopupView.kt`](https://github.com/kazumaproject/japanesekeyboard/blob/main/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`](https://github.com/kazumaproject/japanesekeyboard/blob/main/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`](https://github.com/kazumaproject/japanesekeyboard/blob/main/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:

1. **Setup**: A `TenKey` instance creates a controller and registers a `FlickListener` via `setOnFlickListener`. The controller receives a character map linking `FlickDirection` values to specific characters.

2. **Touch Down**: The controller's `attach(view, map)` method stores the `anchorView` and displays an initial center popup using `setPopUpWindowCenter` or the standard flick popup view.

3. **Movement**: As the finger moves, the controller calculates the `FlickDirection` using 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 like `setTextFlick*` to reflect the character under the current direction.

4. **Release**: On `ACTION_UP`, the controller determines the final direction, looks up the corresponding character from `characterMap`, invokes `listener?.onFlick(finalDirection, character)`, and dismisses all `PopupWindow` instances.

## Implementation Examples

### Registering a Flick Listener on TenKey

```kotlin
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`](https://github.com/kazumaproject/japanesekeyboard/blob/main/tenkey/src/main/java/com/kazumaproject/tenkey/TenKey.kt).

### Attaching a Standard Flick Controller

```kotlin
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`](https://github.com/kazumaproject/japanesekeyboard/blob/main/custom_keyboard/src/main/java/com/kazumaproject/custom_keyboard/controller/StandardFlickInputController.kt).

### Using Popup Extensions Directly

```kotlin
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`](https://github.com/kazumaproject/japanesekeyboard/blob/main/tenkey/src/main/java/com/kazumaproject/tenkey/extensions/PopupWindowExtension.kt).

### Implementing Long-Press Grid Flick

```kotlin
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`](https://github.com/kazumaproject/japanesekeyboard/blob/main/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 `StandardFlickInputController` use geometric calculations (`sqrt`/`atan2`) on touch events to determine `FlickDirection` while managing `PopupWindow` lifecycle.
- 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`](https://github.com/kazumaproject/japanesekeyboard/blob/main/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`.