1 关键字

焦点;应用;Tab;

2 问题描述

环境:OpenHarmony 3.2

问题现象:打开应用无法立即获得焦点。需点击键盘Tab按键才可获得焦点。

3 问题原因

3.1 正常机制

打开应用默认元素获得焦点。

3.2 异常机制

打开应用无法立即获得焦点,点击键盘Tab按键默认元素获得焦点。

4 解决方案

更改获得焦点默认逻辑,使得应用进入默认获得焦点。

修改文件foundation/arkui/ace_engine/frameworks/core/pipeline/pipeline_context.hisTabKeyPressed_属性默认值为true

bool isTabKeyPressed_ = true;

5 定位过程

  1. 在设置默认焦点和焦点控制时,发现进入应用无法控制应用焦点。点击键盘Tab按键后,默认焦点生效,焦点也可进行控制。

  2. 由于键盘输入属于多模输入模块,从多模输入模块中查找Tab按键。

    查找foundation/multimodalinput/input/frameworks/proxy/events/src/key_event.cpp文件。

    const int32_t KeyEvent::KEYCODE_TAB = 2049;
  3. 查找KeyEvent::KEYCODE_TAB是否经过特别处理。

    发现未经过特别处理,继续查找多模模块分发过程中是否对TAB按键进行特别处理。

  4. 多模模块通过foundation/multimodalinput/input/frameworks/proxy/event_handler/src/input_manager_impl.cpp文件,将key_event事件分发到窗口管理子系统。

    void InputManagerImpl::OnKeyEvent(std::shared_ptr<KeyEvent> keyEvent)
    {
        CHK_PID_AND_TID();
        CHKPV(keyEvent);
        CHKPV(eventHandler_);
        CHKPV(consumer_);
        std::lock_guard<std::mutex> guard(mtx_);
        BytraceAdapter::StartBytrace(keyEvent, BytraceAdapter::TRACE_STOP, BytraceAdapter::KEY_DISPATCH_EVENT);
        if (!MMIEventHandler::PostTask(eventHandler_,
            std::bind(&InputManagerImpl::OnKeyEventTask, this, consumer_, keyEvent))) {
            MMI_HILOGE("Post task failed");
        }
        MMI_HILOGD("Key event keyCode:%{public}d", keyEvent->GetKeyCode());
    }
    void InputManagerImpl::OnKeyEventTask(std::shared_ptr<IInputEventConsumer> consumer,
    std::shared_ptr<KeyEvent> keyEvent)
    {
        CHK_PID_AND_TID();
        CHKPV(consumer);
        consumer->OnInputEvent(keyEvent);
        MMI_HILOGD("Key event callback keyCode:%{public}d", keyEvent->GetKeyCode());
    }
  5. 继续跟踪,foundation/window/window_manager/wm/src/window_input_channel.cpp接收到事件,未做特殊处理。

    void WindowInputChannel::HandleKeyEvent(std::shared_ptr<MMI::KeyEvent>& keyEvent)
    {
        if (keyEvent == nullptr) {
            WLOGFE("keyEvent is nullptr");
            return;
        }
        WLOGFI("Receive key event, windowId: %{public}u, keyCode: %{public}d",
            window_->GetWindowId(), keyEvent->GetKeyCode());
        if (window_->GetType() == WindowType::WINDOW_TYPE_DIALOG) {
            if (keyEvent->GetAgentWindowId() != keyEvent->GetTargetWindowId()) {
                window_->NotifyTouchDialogTarget();
                keyEvent->MarkProcessed();
                return;
            }
        }
    ​
        bool inputMethodHasProcessed = false;
    #ifdef IMF_ENABLE
        bool isKeyboardEvent = IsKeyboardEvent(keyEvent);
        if (isKeyboardEvent) {
            WLOGFI("dispatch keyEvent to input method");
            inputMethodHasProcessed = MiscServices::InputMethodController::GetInstance()->dispatchKeyEvent(keyEvent);
        }
    #endif // IMF_ENABLE
        if (!inputMethodHasProcessed) {
            WLOGFI("dispatch keyEvent to ACE");
            window_->ConsumeKeyEvent(keyEvent);
        }
    }
  6. 继续跟踪,foundation/window/window_manager/wm/src/window_impl.cpp接收到事件,未做特殊处理,并将事件分发到ArkUI中。

    void WindowImpl::ConsumeKeyEvent(std::shared_ptr<MMI::KeyEvent>& keyEvent)
    {
        int32_t keyCode = keyEvent->GetKeyCode();
        int32_t keyAction = keyEvent->GetKeyAction();
        WLOGFI("KeyCode: %{public}d, action: %{public}d", keyCode, keyAction);
        if (keyCode == MMI::KeyEvent::KEYCODE_BACK && keyAction == MMI::KeyEvent::KEY_ACTION_UP) {
            HandleBackKeyPressedEvent(keyEvent);
        } else {
            std::shared_ptr<IInputEventConsumer> inputEventConsumer;
            {
                std::lock_guard<std::recursive_mutex> lock(mutex_);
                inputEventConsumer = inputEventConsumer_;
            }
            if (inputEventConsumer != nullptr) {
                WLOGFI("Transfer key event to inputEventConsumer");
                (void)inputEventConsumer->OnInputEvent(keyEvent);
            } else if (uiContent_ != nullptr) {
                WLOGFI("Transfer key event to uiContent");
                (void)uiContent_->ProcessKeyEvent(keyEvent);
            } else {
                WLOGFE("There is no key event consumer");
            }
        }
    }
  7. 跟踪ArkUI对事件的处理,发现foundation/arkui/ace_engine/frameworks/core/pipeline/pipeline_context.cpp中,设置当第一次点击TAB时会把isTabKeyPressed_属性设置为true

    bool PipelineContext::OnKeyEvent(const KeyEvent& event)
    {
        CHECK_RUN_ON(UI);
        if (!rootElement_) {
            LOGE("the root element is nullptr");
            EventReport::SendAppStartException(AppStartExcepType::PIPELINE_CONTEXT_ERR);
            return false;
        }
        if (!isKeyEvent_ && SystemProperties::GetDeviceType() == DeviceType::PHONE) {
            if (KeyCode::KEY_DPAD_UP <= event.code && event.code <= KeyCode::KEY_DPAD_RIGHT) {
                if (event.action == KeyAction::UP) {
                    SetIsKeyEvent(true);
                }
            } else if (event.code == KeyCode::KEY_ENTER) {
                if (event.action == KeyAction::CLICK) {
                    SetIsKeyEvent(true);
                }
            }
        }
        rootElement_->HandleSpecifiedKey(event);
    ​
        SetShortcutKey(event);
    ​
        pressedKeyCodes = event.pressedCodes;
        isKeyCtrlPressed_ = !pressedKeyCodes.empty() && (pressedKeyCodes.back() == KeyCode::KEY_CTRL_LEFT ||
                                                            pressedKeyCodes.back() == KeyCode::KEY_CTRL_RIGHT);
        if ((event.code == KeyCode::KEY_CTRL_LEFT || event.code == KeyCode::KEY_CTRL_RIGHT) &&
            event.action == KeyAction::UP) {
            if (isOnScrollZoomEvent_) {
                zoomEventA_.type = TouchType::UP;
                zoomEventB_.type = TouchType::UP;
                LOGI("Send TouchEventA(%{public}f, %{public}f, %{public}zu)", zoomEventA_.x, zoomEventA_.y,
                    zoomEventA_.type);
                OnTouchEvent(zoomEventA_);
                LOGI("Send TouchEventB(%{public}f, %{public}f, %{public}zu)", zoomEventB_.x, zoomEventB_.y,
                    zoomEventB_.type);
                OnTouchEvent(zoomEventB_);
                isOnScrollZoomEvent_ = false;
            }
        }
    ​
        if (event.code == KeyCode::KEY_TAB && event.action == KeyAction::DOWN && !isTabKeyPressed_) {
            isTabKeyPressed_ = true;
        }
        if (!eventManager_->DispatchTabIndexEvent(event, rootElement_, GetLastPage())) {
            return eventManager_->DispatchKeyEvent(event, rootElement_);
        }
        return true;
    }
  8. 查找isTabKeyPressed_调用位置,在foundation/arkui/ace_engine/frameworks/core/components/button/button_element.cpp文件中,发现获得焦点需要判断isTabKeyPressed_属性为true

    void ButtonElement::OnFocus()
    {
        if (!button_) {
            return;
        }
        button_->HandleFocusEvent(true);
        button_->PlayFocusAnimation(true);
        auto context = context_.Upgrade();
        if (context && context->GetIsTabKeyPressed()) {
            button_->ChangeStatus(RenderStatus::FOCUS);
        }
    }

6 知识分享

焦点控制时,KeyEvent 仅支持上、下、左、右和Tab按键进行焦点选择。

switch (keyEvent.code) {
    case KeyCode::TV_CONTROL_UP:
        LOGI("Node: %{public}s request next focus by Key-'UP'", GetFrameName().c_str());
        return RequestNextFocus(true, true, GetRect());
    case KeyCode::TV_CONTROL_DOWN:
        LOGI("Node: %{public}s request next focus by Key-'DOWN'", GetFrameName().c_str());
        return RequestNextFocus(true, false, GetRect());
    case KeyCode::TV_CONTROL_LEFT:
        LOGI("Node: %{public}s request next focus by Key-'LEFT'", GetFrameName().c_str());
        return RequestNextFocus(false, !AceApplicationInfo::GetInstance().IsRightToLeft(), GetRect());
    case KeyCode::TV_CONTROL_RIGHT:
        LOGI("Node: %{public}s request next focus by Key-'RIGHT'", GetFrameName().c_str());
        return RequestNextFocus(false, AceApplicationInfo::GetInstance().IsRightToLeft(), GetRect());
    case KeyCode::KEY_TAB: {
        auto context = NG::PipelineContext::GetCurrentContext();
        bool ret = false;
        if (keyEvent.pressedCodes.size() == 1) {
            LOGI("Node: %{public}s request next focus by Key-'TAB'", GetFrameName().c_str());
            ret = RequestNextFocus(false, false, GetRect()) || RequestNextFocus(true, false, GetRect());
        } else if (keyEvent.IsShiftWith(KeyCode::KEY_TAB)) {
            LOGI("Node: %{public}s request next focus by Key-'SHIFT-TAB'", GetFrameName().c_str());
            ret = RequestNextFocus(false, true, GetRect()) || RequestNextFocus(true, true, GetRect());
        }
        return ret;
    }
    default:
        return false;
}

 

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐