I. Event Architecture
1.1 Event Propagation Model
HarmonyOS adopts a three-phase event propagation mechanism (Capture-Target-Bubble), optimized for device characteristics while maintaining Web standards compatibility:
graph LR A[Device Input] --> B{ArkUI Event Dispatcher} B --> C[Target Component] C --> D{Bubble Phase} D --> E[Parent Component]
Mouse Events: Left-click, movement, hover (Right-click handled separately)
Keyboard Events: Compound key recognition and preprocessing
1.2 Core Event Types
Event Category | Typical Callback Method | Data Object |
---|---|---|
Mouse Hover | onHover((isHover) => void) | HoverEvent |
Mouse Interaction | onMouse((event: MouseEvent) => void) | MouseEvent |
Keyboard Input | onKeyEvent((event: KeyEvent) => void) | KeyEvent |
Focus Changes | onFocus/Blur | FocusEvent |
II. Complex Scenario Solutions
2.1 Precision Event Targeting
Problem Scenario: Nested List Button requiring independent mouse event handling
List({ space: 20 }) { ForEach(items, (item, index) => { ListItem() { Button("Action") .onMouse((e) => { if(e.button === MouseButton.Left) { this.handleItemClick(index); // Left-click handling } }) .onHover((isHover) => { this.updateHoverState(index, isHover); // Independent hover state }) } }) }
2.2 Z-Shaped Focus Navigation
Implementation: Cross-hierarchy focus transition logic
Column() { @State activeTab = 0 Tabs() { TabContent() { List() { ForEach(data, (_, i) => { ListItem() .focusable(true) .tabIndex(i === 0 ? 1 : -1) .onKeyEvent((e) => { if(e.type === KeyType.Down && e.keyCode === KeyCode.Tab) { this.activeTab = (this.activeTab + 1) % 3; nextTabItem.requestFocus(); return true; } }) }) } } } }
2.3 Compound Key Handling
Shortcut Implementation:
// Global shortcut registration @Entry @Component struct ShortcutDemo { build() { WindowEventBus.on(KeyEvent, (event) => { if(event.isCtrlDown() && event.keyCode === KeyCode.S) { this.handleSave(); } }) } } // Component-level handling Button("Save") .onKeyEvent((e) => { if(e.isCtrlDown() && e.keyCode === KeyCode.Enter) { e.stopPropagation(); this.saveData(); } })
III. Event Styling Control
3.1 Hover Visual Feedback
Button("Hover Effect") .hoverEffect(HoverEffect.Default) .onHover((isHover) => { this.hoverStyle = isHover ? { backgroundColor: Color.LightBlue, shadow: new BoxShadow({ color: Color.Gray, radius: 4, offsetX: 2, offsetY: 2 }) } : {}; })
3.2 Focus State Indication
Dynamic focus border:
TextInput() .focusable(true) .onFocus(() => this.showFocusRing = true) .onBlur(() => this.showFocusRing = false) .focusStyle({ border: { color: this.showFocusRing ? Color.Blue : Color.Transparent, width: 2 }, shadow: this.showFocusRing ? { color: Color.BlueAlpha(0.3), radius: 4, offsetX: 2, offsetY: 2 } : undefined })
IV. Performance Optimization
4.1 Event Throttling
// Scroll event debouncing let scrollTimeout: number; Scroll() { .onScroll((e) => { clearTimeout(scrollTimeout); scrollTimeout = setTimeout(() => { this.processScrollData(e); }, 100); }) }
4.2 Virtual Event Handling
Large list optimization:
List({ space: 20 }) { Scroll() { VirtualList({ count: 1000, itemHeight: 80 }) { ForEach(visibleData, (item) => { ListItem() .onMouse((e) => this.handleVirtualItemHover(e)) }) } } }
V. Debugging
5.1 Event Tracking
// Enable event logging setGlobalEventDebug(true, { filter: ['mouse', 'key'], logLevel: DebugLevel.Verbose }); // Sample output: [Event] 2025-06-23 14:30:00.123: Type: MouseEvent Target: Button#0x7f8d9c0 Position: (120, 240) Buttons: Left Action: Press
5.2 Common Issues
Symptom | Likely Cause | Solution |
---|---|---|
Event not triggering | Missing focusable prop | Add .focusable(true) |
Focus mismanagement | Improper event bubbling | Use event.stopPropagation() |
Style not applying | Style scope conflict | Use @style qualifier |
Shortcut not working | Global listener missing | Register via WindowEventBus |
VI. Implementation Scenarios
Scenario 1: Data Table Interaction
Table() { ForEach(headers, (header) => { TableHeader(header) }) ForEach(rows, (row) => { TableRow() { ForEach(row.cells, (cell) => { TableCell() .onMouse((e) => { if(e.action === MouseAction.DoubleClick) { this.editCell(cell); } }) }) } }) }
Scenario 2: Cross-Device Control
Mobile sender:
@Entry @Component struct MobileController { @Link remoteComponent: RemoteControlComponent build() { Column() { Button("Fullscreen") .onClick(() => { this.remoteComponent.postEvent( new KeyEvent({ type: KeyType.Down, keyCode: KeyCode.F11 }) ); }) } } }
Desktop receiver:
@Component struct DesktopReceiver { onKeyEvent(event: KeyEvent) { if(event.type === KeyType.Down && event.keyCode === KeyCode.F11) { this.toggleFullScreen(); } } }
VII. Development Techniques
7.1 Custom Gesture Recognition
GestureContainer() { @State startPos: Point = { x: 0, y: 0 } onMove((e) => { if(e.touches.length === 3) { const deltaX = e.touches[0].x - this.startPos.x; const deltaY = e.touches[0].y - this.startPos.y; this.handleThreeFingerSwipe(deltaX, deltaY); } }) }
7.2 Hardware Acceleration
// Force GPU rendering Component.setRenderMode(RenderMode.Hardware); // Canvas optimization Canvas() .enableHardwareAcceleration(true) .render(() => { drawComplexPath(); });
VIII. Best Practices
Event Delegation: Delegate high-frequency events to parent components
State Isolation: Use @link for bidirectional parent-child state sync
Device Adaptation:
if(DeviceType.mobile) { this.disableHoverEffects(); }
- Accessibility:
Button("Submit") .accessibilityLabel("Confirm order submission") .accessibilityHint("Double-tap to complete")
Top comments (0)