HarmonyOS 应用开发-ArkUI事件机制

作者: 迪士尼在逃程序员 | 来源:发表于2024-08-15 15:32 被阅读0次

    ArkUI提供了事件机制,这些事件提供了不同的信息用于处理程序交互逻辑,ArKUI事件按照功能来讲,可以分为以下几种:

    • 点击事件
    • 触摸事件
    • 挂载卸载事件
    • 拖拽事件
    • 按键事件
    • 焦点事件
    • 鼠标事件
    • 组件区域变化事件
    • 组件可见区域变化事件
    • 组件快捷键事件

    按照事件触发后是否向父节点传递,事件可以分为:

    • 冒泡事件:是当一个组件上的事件被触发后,该事件向父节点传递。
    • 非冒泡事件:是当一个组件上的事件被触发后,该事件不会向父节点传递。

    示例讲解

    下面以组件区域变化事件为例,结合代码进行分析。

    组件区域变化事件介绍

    当UI组件大小、位置发生变化时触发该事件,应用可以通过该事件箭头UI组件的大小位置变化,该事件属于非冒泡事件。

    示例代码

    // xxx.ets
    @Entry
    @Component
    struct AreaExample {
      @State value: string = 'Text'
      @State sizeValue: string = ''
    
      build() {
        Column() {
          Text(this.value)
            .backgroundColor(Color.Green).margin(30).fontSize(20)
            .onClick(() => {
              this.value = this.value + 'Text'
            })
            .onAreaChange((oldValue: Area, newValue: Area) => {
              console.info(`Ace: on area change, oldValue is ${JSON.stringify(oldValue)} value is ${JSON.stringify(newValue)}`)
              this.sizeValue = JSON.stringify(newValue)
            })
          Text('new area is: \n' + this.sizeValue).margin({ right: 30, left: 30 })
        }
        .width('100%').height('100%').margin({ top: 30 })
      }
    }
    

    示例效果:

    每次点击Text框,Text组件区域大小都发生变化,每次将位置信息打印到控制台。

    在上述示例代码中,Text组件通过绑定onAreaChange实现了对组件区域变化事件的监听,onAreaChange事件的入参位一个回调函数,该回调函数在Text组件区域发生变化时进行调用。

    代码实现原理分析

    1. 编译后生成的JS代码

    "use strict";
    class AreaExample extends ViewPU {
        constructor(parent, params, __localStorage, elmtId = -1) {
            super(parent, __localStorage, elmtId);
            this.__value = new ObservedPropertySimplePU('Text', this, "value");
            this.__sizeValue = new ObservedPropertySimplePU('', this, "sizeValue");
            this.setInitiallyProvidedValue(params);
        }
        setInitiallyProvidedValue(params) {
            if (params.value !== undefined) {
                this.value = params.value;
            }
            if (params.sizeValue !== undefined) {
                this.sizeValue = params.sizeValue;
            }
        }
        updateStateVars(params) {
        }
        purgeVariableDependenciesOnElmtId(rmElmtId) {
            this.__value.purgeDependencyOnElmtId(rmElmtId);
            this.__sizeValue.purgeDependencyOnElmtId(rmElmtId);
        }
        aboutToBeDeleted() {
            this.__value.aboutToBeDeleted();
            this.__sizeValue.aboutToBeDeleted();
            SubscriberManager.Get().delete(this.id__());
            this.aboutToBeDeletedInternal();
        }
        get value() {
            return this.__value.get();
        }
        set value(newValue) {
            this.__value.set(newValue);
        }
        get sizeValue() {
            return this.__sizeValue.get();
        }
        set sizeValue(newValue) {
            this.__sizeValue.set(newValue);
        }
        initialRender() {
            this.observeComponentCreation((elmtId, isInitialRender) => {
                ViewStackProcessor.StartGetAccessRecordingFor(elmtId);
                Column.create();
                Column.width('100%');
                Column.height('100%');
                Column.margin({ top: 30 });
                if (!isInitialRender) {
                    Column.pop();
                }
                ViewStackProcessor.StopGetAccessRecording();
            });
            this.observeComponentCreation((elmtId, isInitialRender) => {
                ViewStackProcessor.StartGetAccessRecordingFor(elmtId);
                Text.create(this.value);
                Text.backgroundColor(Color.Green);
                Text.margin(30);
                Text.fontSize(20);
                Text.onClick(() => {
                    this.value = this.value + 'Text';
                });
                Text.onAreaChange((oldValue, newValue) => {
                    console.info(`Ace: on area change, oldValue is ${JSON.stringify(oldValue)} value is ${JSON.stringify(newValue)}`);
                    this.sizeValue = JSON.stringify(newValue);
                });
                if (!isInitialRender) {
                    Text.pop();
                }
                ViewStackProcessor.StopGetAccessRecording();
            });
            Text.pop();
            this.observeComponentCreation((elmtId, isInitialRender) => {
                ViewStackProcessor.StartGetAccessRecordingFor(elmtId);
                Text.create('new area is: \n' + this.sizeValue);
                Text.margin({ right: 30, left: 30 });
                if (!isInitialRender) {
                    Text.pop();
                }
                ViewStackProcessor.StopGetAccessRecording();
            });
            Text.pop();
            Column.pop();
        }
        rerender() {
            this.updateDirtyElements();
        }
    }
    ViewStackProcessor.StartGetAccessRecordingFor(ViewStackProcessor.AllocateNewElmetIdForNextComponent());
    loadDocument(new AreaExample(undefined, {}));
    ViewStackProcessor.StopGetAccessRecording();
    //# sourceMappingURL=Index.js.map
    

    代码分为两部分,一部分为定义了AreaExample的类,第二部分是js的入口执行程序,详细内容参考ArkUI框架学习。我们这里重点关注Text.onAreaChange方法。

    2. JS与C++代码的绑定关系

    Text组件对应到ace_engine后端引擎中的JSText类:

    
    class JSText : public JSContainerBase {
    public:
        static void JSBind(BindingTarget globalObj);
        static void Create(const JSCallbackInfo& info);
        static void SetWidth(const JSCallbackInfo& info);
        static void SetHeight(const JSCallbackInfo& info);
        static void SetFont(const JSCallbackInfo& info);
        static void GetFontInfo(const JSCallbackInfo& info, Font& font);
        static void SetFontSize(const JSCallbackInfo& info);
        static void SetFontWeight(const std::string& value);
        static void SetTextColor(const JSCallbackInfo& info);
        static void SetTextShadow(const JSCallbackInfo& info);
        static void SetTextOverflow(const JSCallbackInfo& info);
        static void SetMaxLines(const JSCallbackInfo& info);
        static void SetTextIndent(const JSCallbackInfo& info);
        static void SetFontStyle(int32_t value);
        static void SetAlign(const JSCallbackInfo& info);
        static void SetTextAlign(int32_t value);
        static void SetLineHeight(const JSCallbackInfo& info);
        static void SetFontFamily(const JSCallbackInfo& info);
        static void SetMinFontSize(const JSCallbackInfo& info);
        static void SetMaxFontSize(const JSCallbackInfo& info);
        static void SetLetterSpacing(const JSCallbackInfo& info);
        static void SetTextCase(int32_t value);
        static void SetBaselineOffset(const JSCallbackInfo& info);
        static void SetDecoration(const JSCallbackInfo& info);
        static void SetCopyOption(const JSCallbackInfo& info);
        static void SetHeightAdaptivePolicy(int32_t value);
        static void JsOnClick(const JSCallbackInfo& info);
        static void JsRemoteMessage(const JSCallbackInfo& info);
        static void JsOnDragStart(const JSCallbackInfo& info);
        static void JsOnDragEnter(const JSCallbackInfo& info);
        static void JsOnDragMove(const JSCallbackInfo& info);
        static void JsOnDragLeave(const JSCallbackInfo& info);
        static void JsOnDrop(const JSCallbackInfo& info);
        static void JsFocusable(const JSCallbackInfo& info);
        static void JsDraggable(const JSCallbackInfo& info);
        static void JsMenuOptionsExtension(const JSCallbackInfo& info);
    
    private:
        static RefPtr<TextComponentV2> GetComponent();
    };
    

    查看代码发现JSText中未直接定义onAreaChange方法,在JSText::bind中也未绑定相关的方法,结合onAreaChange方法不止在Text组件中使用,也可以在其他组件中使用,因此继续往JSText继承的基类中找。

    class JSContainerBase : public JSViewAbstract, public JSInteractableView {
    public:
        static void Pop();
        static void JSBind(BindingTarget globalObj);
    };
    

    继续往上层找,发现在JSViewAbstract中定义了JsOnAreaChange方法:

    class JSViewAbstract {
        // 忽略掉其他不相关代码片段
         static void JsOnAreaChange(const JSCallbackInfo& info);
    }
    

    在 JSViewAbstract::JSBind中实现了JS代码与C++代码的绑定关系

    void JSViewAbstract::JSBind(BindingTarget globalObj)
    {
        JSClass<JSViewAbstract>::Declare("JSViewAbstract");
        
        JSClass<JSViewAbstract>::StaticMethod("onAreaChange", &JSViewAbstract::JsOnAreaChange);
    
    }
    

    3. ArkTS onAreaChange接口定义

    接口在interface/sdk-js/api/@internal/component/ets/common.d.ts中进行定义,两个参数,一个位变化前的组件区域信息,另一个为变化后的组件区域信息。

    /**
    * This callback is triggered when the size or position of this component change finished.
    *
    * @param { function } event event callback.
    * @returns { T }
    * @syscap SystemCapability.ArkUI.ArkUI.Full
    * @crossplatform
    * @since 10
    */
    onAreaChange(event: (oldValue: Area, newValue: Area) => void): T;
    

    4.前端回调函数保存逻辑

    JSViewAbstract::JsOnAreaChange代码实现:

    void JSViewAbstract::JsOnAreaChange(const JSCallbackInfo& info)
    {
        if (info[0]->IsUndefined() && IsDisableEventVersion()) {
            LOGD("JsOnAreaChange callback is undefined");
            ViewAbstractModel::GetInstance()->DisableOnAreaChange();
            return;
        }
        std::vector<JSCallbackInfoType> checkList { JSCallbackInfoType::FUNCTION };
        if (!CheckJSCallbackInfo("JsOnAreaChange", info, checkList)) {
            return;
        }
        auto jsOnAreaChangeFunction = AceType::MakeRefPtr<JsOnAreaChangeFunction>(JSRef<JSFunc>::Cast(info[0]));
    
        auto onAreaChanged = [execCtx = info.GetExecutionContext(), func = std::move(jsOnAreaChangeFunction)](
                                 const Rect& oldRect, const Offset& oldOrigin, const Rect& rect, const Offset& origin) {
            JAVASCRIPT_EXECUTION_SCOPE_WITH_CHECK(execCtx);
            ACE_SCORING_EVENT("onAreaChange");
            func->Execute(oldRect, oldOrigin, rect, origin);
        };
        ViewAbstractModel::GetInstance()->SetOnAreaChanged(std::move(onAreaChanged));
    }
    
    

    关键点:
    JSCallbackInfo为从JS前端传过来的参数,对参数合法性进行校验后,从这里面获取到绑定到onAreaChange上的回调函数以及回调函数,然后继续往ViewAbstractModel中传。

    ViewAbstractModel是一个纯虚接口类,其实现有ViewAbstractModelNG和ViewAbstractModelImpl两个类,其中ViewAbstractModelNG位新的架构下的实现类,我们重点关注这个类的实现。

    void SetOnAreaChanged(
        std::function<void(const Rect& oldRect, const Offset& oldOrigin, const Rect& rect, const Offset& origin)>&&
            onAreaChanged) override
    {
        auto areaChangeCallback = [areaChangeFunc = std::move(onAreaChanged)](const RectF& oldRect,
                                      const OffsetF& oldOrigin, const RectF& rect, const OffsetF& origin) {
            areaChangeFunc(Rect(oldRect.GetX(), oldRect.GetY(), oldRect.Width(), oldRect.Height()),
                Offset(oldOrigin.GetX(), oldOrigin.GetY()), Rect(rect.GetX(), rect.GetY(), rect.Width(), rect.Height()),
                Offset(origin.GetX(), origin.GetY()));
        };
        ViewAbstract::SetOnAreaChanged(std::move(areaChangeCallback));
    }
    
    

    这里也没有做更多的东西,继续调用ViewAbstract::SetOnAreaChanged方法。
    看下ViewAbstract::SetOnAreaChanged实现:

    void ViewAbstract::SetOnAreaChanged(
        std::function<void(const RectF& oldRect, const OffsetF& oldOrigin, const RectF& rect, const OffsetF& origin)>&&
            onAreaChanged)
    {   
        auto pipeline = PipelineContext::GetCurrentContext();
        CHECK_NULL_VOID(pipeline);
        auto frameNode = ViewStackProcessor::GetInstance()->GetMainFrameNode();
        CHECK_NULL_VOID(frameNode);
        frameNode->SetOnAreaChangeCallback(std::move(onAreaChanged));
        pipeline->AddOnAreaChangeNode(frameNode->GetId());
    }
    
    

    这里是关键点:

    1. 通过ViewStackProcessor获取到当前正在处理的frameNode,将前端一路传递过来的回调函数保存到frameNode中。
    2. 将frameNode的ID加入到渲染管线PipeContext中保存。
    // FrameNode中保存回调函数,将回调函数保存到事件总线eventHub_
    void FrameNode::SetOnAreaChangeCallback(OnAreaChangedFunc&& callback)
    {
        if (!lastFrameRect_) {
            lastFrameRect_ = std::make_unique<RectF>();
        }
        if (!lastParentOffsetToWindow_) {
            lastParentOffsetToWindow_ = std::make_unique<OffsetF>();
        }
        eventHub_->SetOnAreaChanged(std::move(callback));
    }
    
    // 事件总线中保存回调函数
    void EventHub::SetOnAreaChanged(OnAreaChangedFunc&& onAreaChanged)
    {
        onAreaChanged_ = std::move(onAreaChanged);
    }
    
    //管线中保存FrameNode
    void PipelineContext::AddOnAreaChangeNode(int32_t nodeId)
    {
        onAreaChangeNodeIds_.emplace(nodeId);
    }
    

    6.回调函数调用处理

    从上面的分析看,最终回调函数是保存在EventHub中的onAreaChanged_,那么只需要根据onAreaChanged_在哪里调用,然后再一路查看调用点,就可以看到详细的调用栈,这里不再贴详细的代码,直接贴出来最终的调用链:

    PipelineContext::FlushVsync->PipelineContext::HandleOnAreaChangeEvent->FrameNode::TriggerOnAreaChangeCallback->EventHub::FireOnAreaChanged
    

    从调用栈可以看到,在接受到底层vsync事件时,会触发PipelineContext::FlushVsync,之后按照上述的调用链,最终调用到前台绑定到Text.onAreaChange上的回调函数。

    7. 代码分析总结

    整体的类图关系如下:

    写在最后

    如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙

    • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
    • 关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
    • 想要获取更多完整鸿蒙最新学习知识点,请移步前往小编:https://gitee.com/MNxiaona/733GH/blob/master/jianshu

    相关文章

      网友评论

        本文标题:HarmonyOS 应用开发-ArkUI事件机制

        本文链接:https://www.haomeiwen.com/subject/jkqtxjtx.html