DEV Community

魔眼天王
魔眼天王

Posted on

HarmonyOS Focus and Focus Navigation Technology Sharing

I. Core Mechanisms of the Focus System

1.1 Focus Chain Formation Rules

HarmonyOS adopts a tree-based focus chain mechanism. When a component gains focus, all its parent components automatically form a focus chain. For example, in a nested scenario where a Row is inside a Column, the focus chain will follow the hierarchy: Page > Column > Row > TextInput. Developers can verify the focus chain status via the onFocus callback.

1.2 Activation State Control

​Activation Condition: Triggered by the first TAB key press on an external keyboard.
​Exit Mechanism: Immediately exits activation state upon any touch event (including hover).
​Debugging Tip: Monitor the state in real time using FocusController.isActivated.

II. Focus Solutions for Complex Layouts

2.1 Precise Focus Positioning

​Problem Scenario: Need to directly position focus on a specific Button within a deeply nested List cell.
​Implementation Solution:

// Parent List sets focus navigation strategy
List({ space: 20 }) {
  ForEach(listData, (item, index) => {
    ListItem() {
      // Critical control configuration
      Button("Operation")
        .focusable(true)
        .tabIndex(index === targetIndex ? 1 : -1) // Force specify TAB order
        .requestFocus({ id: `btn-${index}` }) // Programmatic focus
        .onFocus(() => console.log("Precise positioning successful"))
    }
  })
}
Enter fullscreen mode Exit fullscreen mode

2.2 Z-shaped Focus Navigation

​Scenario Requirement: After focus is on the last element of a horizontal Row, pressing the down arrow key should jump to the first element of the lower Column.
​Configuration Method:

Row({ alignContent: Alignment.Center }) {
  ForEach(horizontalItems, (item) => {
    Item().focusable(true).tabIndex(1)
  })
}
.tabIndex(2) // Set ROW's TAB priority
.groupDefaultFocus(true) // Allow cross-level focusing

Column() {
  // Lower container captures focus
  Container()
    .focusable(true)
    .tabIndex(3)
    .onFocus(() => console.log("Cross-level focus successful"))
}
Enter fullscreen mode Exit fullscreen mode

III. In-depth Control of Focus Styles

3.1 Style Configuration Solutions

Property Type Supported Style Types Effective Conditions
focusBox Border/background color/corner radius Requires focusable=true
focusColor Text highlight color Specific to input-type components
focusShadow Projection effect Requires elevation>0

​Solution to Style Override Issues:

// Force override parent styles
Button()
  .focusBox({
    strokeColor: Color.Red, // Red border
    strokeWidth: 3,         // 3px line width
    margin: LengthMetrics.px(5) // Extended margin
  })
  .parentStyleOverride(true) // Key override property
Enter fullscreen mode Exit fullscreen mode

3.2 Debugging for Incomplete Style Display

​Common Issue: Custom focus box is clipped.
​Troubleshooting Steps:

  • Check if the parent container's overflow property is set to Visible.

  • Verify if focusBox.margin exceeds the parent container's boundaries.

  • Use LayoutInspector to check the actual rendered dimensions.

  • Add flexGrow: 1 to ensure container adaptability.

IV. Focus Navigation Strategies

4.1 Direction Control

​Scenario: Implement cross-shaped focus navigation in a Flex container.

Flex({ direction: FlexDirection.Row }) {
  ForEach(items, (item) => {
    Item()
      .flexGrow(1)
      .onKeyDown((event) => {
        if(event.key === Key.ArrowDown) {
          // Manually control cross-row jump
          nextRowItem.requestFocus()
          return true // Prevent default focus navigation
        }
      })
  })
}
Enter fullscreen mode Exit fullscreen mode

4.2 Dynamic Layout Adaptation

​Focus Maintenance During Dynamic List Item Loading:

List({ space: 20 }) {
  Scroll() {
    ForEach(dynamicData, (item) => {
      ListItem()
        .focusable(true)
        .defaultFocus(index === 0) // Default focus on first item
        .onFocusLost(() => {
          // Record position on focus loss
          lastFocusedIndex = index
        })
    })
  }
  .onScrollEnd(() => {
    // Restore focus after scrolling
    if(lastFocusedIndex !== -1) {
      listRef.scrollToItem(lastFocusedIndex)
    }
  })
}
Enter fullscreen mode Exit fullscreen mode

V. Handling Special Scenarios

5.1 Modal Dialog Focus Management

Dialog() {
  // Force dialog to gain focus
  .focusable(true)
  .defaultFocus(true)
  .onDismissed(() => {
    // Return to original focus when closed
    originalFocusComponent.requestFocus()
  })
}
Enter fullscreen mode Exit fullscreen mode

5.2 Cross-component Focus Transfer

​Automatic Transfer Between Parent and Child Components:

// Parent Component
@Component
struct Parent {
  @Link childFocus: boolean = false

  build() {
    Column() {
      Child({ onFocus: () => this.childFocus = true })
      Button("Jump")
        .onClick(() => childComponent.requestFocus())
    }
  }
}

// Child Component
@Component
struct Child {
  @Prop onFocus: () => void

  build() {
    TextInput()
      .onFocus(() => this.onFocus())
  }
}
Enter fullscreen mode Exit fullscreen mode

VI. Performance Optimization

  • ​Focus Preloading: Preset defaultFocus during page initialization.

  • ​Invalid Focus Cleanup: Promptly call clearFocus() to release invalid focus.

  • ​Batch Operation Optimization:

// Batch set non-focusable
listItems.forEach(item => {
  item.focusable(false)
})
Enter fullscreen mode Exit fullscreen mode
  • ​Focus Event Throttling:
onFocus(() => {
  throttle(() => {
    // Processing logic
  }, 300)
})
Enter fullscreen mode Exit fullscreen mode

VII. Debugging Tools

Focus Visualization

// Enable focus border display
setGlobalFocusStyle({
  showBorder: true,
  borderColor: Color.Blue
})
Enter fullscreen mode Exit fullscreen mode

Focus Path Logging

FocusController.onPathChange((path) => {
  console.log("Current focus path:", path)
})
Enter fullscreen mode Exit fullscreen mode

Performance Monitoring

PerformanceMonitor.start('focus')
// ... Operations ...
PerformanceMonitor.stop('focus')
Enter fullscreen mode Exit fullscreen mode

Top comments (0)