# 焦点管理

### 焦点说明

眼镜上的屏幕无法直接触控，无法像手机可以直接选中目标View，所以需要**自己维护焦点**的切换，一般**前滑**和**后滑**手势用来切换焦点，**单击**手势用来触发焦点View的事件响应，**双击**手势退出焦点返回上一级。

焦点管理最简单的实现，可以通过维护一个索引值index，不同的索引值对应不同的能获取焦点的view，前滑和后滑手势触发焦点位的切换，单击响应对应view的点击事件，双击释放焦点给上一级或退出页面。但对于复杂页面，这样简单粗暴的实现方式，并不容易维护。

```kotlin
//1.构建FocusHolder对象
val focusHolder = FocusHolder(true)
....
// 2. 以左边布局中的view，作为focus对象的target，
mBindingPair.setLeft {
    //2.1 构建focusInfo对象
    val btn1Info = FocusInfo(
        btn1,
        eventHandler = { action ->
        // 处理事件响应
            when (action) {
                is TempleAction.Click -> {
                    FToast.show("bt1 click")
                }
                else -> Unit

            }
        },
        focusChangeHandler = { hasFocus ->
        // 处理焦点切换
            mBindingPair.updateView {
                btn1.setBackgroundColor(getColor(if(hasFocus) R.color.purple_200 else R.color.black))
            }
        }
    )
    //2.2. 添加同一层级的可以获取焦点的多个FocusInfo对象
    focusHolder.addFocusTarget(
         // 第一个焦点位
        btn1Info,
        // 第二个焦点位
        FocusInfo(
            btn2,
            eventHandler = {action ->
                when(action) {
                    is TempleAction.Click -> {
                        FToast.show("bt2 click")
                    }
                    else -> Unit
                }
            },
            focusChangeHandler = { hasFocus ->
                mBindingPair.updateView {
                    btn2.setBackgroundColor(getColor(if(hasFocus) R.color.purple_200 else R.color.black))
                }
            }
        ),
        // 第xxx个焦点位
        ......
    )
    // 2.3 设置默认的焦点位
    focusHolder.currentFocus(mBindingPair.left.btn1)
}
// 3. 最终构建FixPosFocusTracker对象
fixPosFocusTracker = FixPosFocusTracker(focusHolder).apply {
    //3.1 设置当前获取到焦点，如果没有获取焦点，则不会响应任何事件
    focusObj.hasFocus = true
}
......
//4. 与TempleAction事件流对接
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.RESUMED) {
        templeActionViewModel.state.collect {
            FLogger.i("DemoActivity", "action = $it")
            when (it) {
                is TempleAction.DoubleClick -> {
                    // 统一处理双击退出逻辑
                    finish()
                }
                else -> fixPosFocusTracker?.handleFocusTargetEvent(it)
            }
        }
    }
}
```

这样同一层级的焦点，就可以按照以上流程来处理。而FixPosFocusTracker本身又可以作为FocusInfo的target，层层嵌套后，即可应对复杂页面的焦点切换场景。

\
对于RecyclerView来说，焦点一般在某个Item View上，焦点的切换又是另外一种情况。根据焦点位是否固定，可以分为**列表+固定焦点位** 和 **列表+移动焦点位**两种情况，SDK分别提供了**RecyclerViewSlidingTracker** 和**RecyclerViewFocusTracker**两个工具类来处理，具体代码参考SDKDemo中的FixedFocusPosRVActivity和MovedFocusPosRVActivity两个页面。

针对其他情况，比如自定义View中的整体的焦点切换，可以通过实现IFocusable接口来统一处理，只有在hasFocus属性为true时，才响应事件。

同时RecyclerViewSlidingTracker、RecyclerViewFocusTracker、IFocusable对象都可以作为FocusInfo的target。

### 动态焦点

{% file src="/files/hYC710RX9TjhYF3OsJUV" %}

> 根据用户交互、视线追踪、场景变化或设备姿态，**实时调整视图中 “焦点视图” 的位置、状态、显示内容或可交互优先级** —— 简单说就是让 “用户当前需要关注 / 可操作的核心视图” 随场景动态变化，而非固定在某个位置或状态
>
> 1. 用户转头看左侧菜单 → 焦点自动从右侧弹窗切换到左侧菜单；
> 2. 场景中出现新的交互目标（如虚拟按钮）→ 动态添加该按钮为新的焦点视图；

在眼镜合目的视图中动态调整焦点视图，需要强依赖视图组件，动态添加焦点视图示例代码如下（详情请参考sample中的DynamicFocusTargetActivity）：

<pre class="language-kotlin"><code class="lang-kotlin">private fun addDynamicFocus() {
    // 扩展函数API动态添加焦点View
    // 使用可变引用，因为handle需要在eventHandler中使用
    var handle: FocusViewHandle&#x3C;View>? = null
    handle = mBindingPair.addFocusView(
        parent = mBindingPair.left.llParent,
<strong>        viewFactory = {
</strong>        //这里动态添加一个button，实际以需求为准
<strong>            Button(this@DynamicFocusTargetActivity).apply {
</strong><strong>                text = "dynamic focus target"
</strong>                background = null
<strong>            }
</strong><strong>        },
</strong>        focusHolder = focusHolder,
<strong>        focusConfig = {
</strong><strong>            // 设置布局参数（eg：LinearLayout，以实际视图为准）
</strong><strong>            layoutParamsFactory = { parent, view ->
</strong><strong>                LinearLayout.LayoutParams(
</strong>                    150.dp,
                    ViewGroup.LayoutParams.WRAP_CONTENT
<strong>                ).apply {
</strong><strong>                    setMargins(
</strong>                        20.dp,
                        5.dp,
                        20.dp,
                        5.dp
                    )
<strong>                }
</strong><strong>            }
</strong>
            //这里处理视图的响应事件
<strong>            eventHandler = { action ->
</strong><strong>                when (action) {
</strong>                    is TempleAction.Click -> {
                        FToast.show("dynamic focus target click!!")
                    }

                    else -> Unit
                }
<strong>            }
</strong>            //这里处理视图焦点切换
<strong>            onFocusChange = { view, hasFocus, isLeft ->
</strong><strong>                triggerFocus(hasFocus, view, isLeft)
</strong><strong>            }
</strong><strong>        }
</strong><strong>    )
</strong>
    // focusHandles是一个管理动态添加焦点视图的可变列表，可酌情添加
    focusHandles.add(handle)

    currentDynamicFocus = handle
}
</code></pre>

移除动态焦点视图如下：

```kotlin
currentDynamicFocus.clearFocusView()
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://rayneo.gitbook.io/rayneo-devdoc/x-xi-lie/android-kai-fa/neng-li-jie-shao/jiao-dian-guan-li.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
