Candidate Window Popup Positioning in JapaneseKeyboard: Anchor-Based Geometry and Offset Calculations
The candidate window popup anchors to the pressed key view using Android's PopupWindow, calculating dynamic offsets based on key dimensions to position the bubble above, beside, or centered relative to the key with directional arrows.
The kazumaproject/japanesekeyboard implements candidate window popup positioning through a sophisticated anchor-based system in Kotlin. This Android IME calculates precise offsets from the pressed key's dimensions to ensure the suggestion bubble appears correctly positioned regardless of screen orientation.
Anchor-Based PopupWindow Architecture
The positioning system centers on Android's PopupWindow API anchored directly to the key view receiving the touch event. In app/src/main/java/com/kazumaproject/markdownhelperkeyboard/ime_service/IMEService.kt, the service instantiates the candidate window wrapping a KeyWindowLayout (the bubble UI) and attaches it to the pressed key view:
// IMEService.kt - candidate window creation
floatingCandidateWindow = PopupWindow(
candidateView,
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
true
)
floatingCandidateWindow?.setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
All geometry configuration happens through extension functions that modify the PopupWindow's dimensions, arrow properties, and display coordinates before calling showAsDropDown.
Extension Function Geometry in PopupWindowExtension.kt
The core positioning logic resides in TenKey/src/main/java/com/kazumaproject/tenkey/extensions/PopupWindowExtension.kt. Each direction-specific extension performs three critical steps: sizing the popup window, configuring the arrow direction in the bubble layout, and calculating x/y offsets relative to the anchor view.
Size Calculations
Extensions calculate width by adding a half-key margin to the anchor's dimensions: width = anchorView.width + (anchorView.width)/2 + 24. Height matches the anchor view: height = anchorView.height.
Offset Mathematics
Offsets use negative values to shift the popup relative to the anchor's bottom-left origin. The arithmetic remains consistent across all Android orientations (ORIENTATION_PORTRAIT, ORIENTATION_LANDSCAPE, ORIENTATION_UNDEFINED), ensuring identical placement during device rotation.
Direction-Specific Positioning Implementations
Right-Flick Positioning
For right-flick gestures, the setPopUpWindowFlickRight extension positions the bubble to the right of the pressed key with the arrow pointing left:
// PopupWindowExtension.kt
fun PopupWindow.setPopUpWindowFlickRight(
context: Context,
keyWindowLayout: KeyWindowLayout,
anchorView: View
) {
this.width = anchorView.width + (anchorView.width) / 2 + 24
this.height = anchorView.height
this.setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
keyWindowLayout.let { bubble ->
if (bubble.arrowDirection != ArrowDirection.LEFT_CENTER) this.dismiss()
bubble.arrowDirection = ArrowDirection.LEFT_CENTER
bubble.arrowHeight = anchorView.height.toFloat() - 5
bubble.arrowWidth = (anchorView.width / 2).toFloat() - 8
bubble.cornersRadius = 10f
}
showAsDropDown(
anchorView,
-(anchorView.width + 14), // shifts popup left to sit right of key
-(anchorView.height), // lifts up one key height
Gravity.CENTER
)
}
The horizontal offset -(anchorView.width + 14) shifts the popup leftward so it appears adjacent to the key's right edge, while -(anchorView.height) lifts it vertically to align with the key center.
Center Positioning for Candidate Bubbles
When displaying the main candidate window directly above a key, IMEService.kt calls setPopUpWindowCenter:
// IMEService.kt - showing candidate bubble above pressed key
floatingCandidateWindow?.setPopUpWindowCenter(
context,
candidateBubbleLayout,
pressedKeyView
)
This extension sets arrowDirection = ArrowDirection.BOTTOM_CENTER (arrow pointing down) and calculates the vertical offset as -(anchorView.height * 2) - 16. This positions the bubble exactly two key heights above the anchor, leaving room for the downward-pointing arrow to connect to the pressed key.
Arrow Configuration and Visual Alignment
The KeyWindowLayout class in core/ui/key_window/KeyWindowLayout.kt manages the visual arrow connecting bubble to key. Extension functions configure:
- arrowDirection: Determines which edge contains the pointer (LEFT_CENTER for right-positioned popups, BOTTOM_CENTER for overhead popups)
- arrowHeight: Set to
anchorView.height.toFloat() - 5 - arrowWidth: Calculated as
(anchorView.width / 2).toFloat() - 8 - cornersRadius: Fixed at
10ffor consistent rounded corners
Hiding the Popup Window
When the key releases or the IME resets, the service dismisses the popup to prevent stuck windows:
floatingCandidateWindow?.dismiss()
floatingCandidateWindow = null
The same PopupWindow instance is reused across candidate displays, eliminating geometry recomputation until the next invocation.
Summary
- Anchor-based positioning: The popup uses Android's
showAsDropDownanchored to the pressed key view, not absolute screen coordinates. - Dimension-driven offsets: All positioning uses the anchor view's
widthandheightproperties, making the logic orientation-independent. - Extension functions:
PopupWindowExtension.ktcontains the geometry logic for right, left, top, bottom, and center positions with specific ArrowDirection configurations. - Consistent across rotations: The same arithmetic applies to portrait, landscape, and undefined orientations.
- Reuse pattern: The PopupWindow instance is reused and nulled after dismissal to prevent memory leaks.
Frequently Asked Questions
How is the candidate window positioned relative to the pressed key?
The candidate window uses Android's PopupWindow anchored to the pressed key view via showAsDropDown(anchor, xOffset, yOffset, Gravity.CENTER). Extension functions in PopupWindowExtension.kt calculate negative x and y offsets based on the anchor view's dimensions to position the bubble above, beside, or centered on the key.
What determines the arrow direction in the candidate popup?
The arrow direction is set via the KeyWindowLayout.arrowDirection property before showing the popup. For candidate bubbles appearing above the key, ArrowDirection.BOTTOM_CENTER is used. For right-flick popups, ArrowDirection.LEFT_CENTER ensures the arrow points toward the pressed key. The arrow dimensions are calculated from the anchor view's size.
Which files contain the core positioning logic?
The positioning logic spans three primary files: TenKey/src/main/java/com/kazumaproject/tenkey/extensions/PopupWindowExtension.kt contains the extension functions calculating offsets and arrow geometry; app/src/main/java/com/kazumaproject/markdownhelperkeyboard/ime_service/IMEService.kt creates the PopupWindow and initiates the display; and core/ui/key_window/KeyWindowLayout.kt defines the arrow rendering properties.
Does the positioning logic change when the device rotates?
No, the positioning arithmetic remains identical across all orientations. The code does not branch for ORIENTATION_PORTRAIT, ORIENTATION_LANDSCAPE, or ORIENTATION_UNDEFINED. Because offsets derive from the anchor view's dimensions rather than screen coordinates, the popup maintains correct positioning relative to the key regardless of device rotation.
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 →