HarmonyOS 应用开发-ArkUI事件机制

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. 代码分析总结

整体的类图关系如下:

为了能让大家更好的学习鸿蒙(HarmonyOS NEXT)开发技术,这边特意整理了《鸿蒙开发学习手册》(共计890页),希望对大家有所帮助:https://qr21.cn/FV7h05

《鸿蒙开发学习手册》:

如何快速入门:https://qr21.cn/FV7h05

  1. 基本概念
  2. 构建第一个ArkTS应用
  3. ……

开发基础知识:https://qr21.cn/FV7h05

  1. 应用基础知识
  2. 配置文件
  3. 应用数据管理
  4. 应用安全管理
  5. 应用隐私保护
  6. 三方应用调用管控机制
  7. 资源分类与访问
  8. 学习ArkTS语言
  9. ……

基于ArkTS 开发:https://qr21.cn/FV7h05

  1. Ability开发
  2. UI开发
  3. 公共事件与通知
  4. 窗口管理
  5. 媒体
  6. 安全
  7. 网络与链接
  8. 电话服务
  9. 数据管理
  10. 后台任务(Background Task)管理
  11. 设备管理
  12. 设备使用信息统计
  13. DFX
  14. 国际化开发
  15. 折叠屏系列
  16. ……

鸿蒙开发面试真题(含参考答案):https://qr18.cn/F781PH

鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH

1.项目开发必备面试题
2.性能优化方向
3.架构方向
4.鸿蒙开发系统底层方向
5.鸿蒙音视频开发方向
6.鸿蒙车载开发方向
7.鸿蒙南向开发方向

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/528777.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++ primer 第十八章

C语言的三大特性&#xff1a;异常处理、命名空间、多重继承。 1.异常处理 异常处理机制允许我们能够将问题的检测与解决过程分离开来。 1.1、抛出异常 在C语言中&#xff0c;我们通过抛出一条表达式来引发一个异常。 当执行一个throw时&#xff0c;程序的控制权从throw转移…

T527 Qt 触摸 ----- TSLIB

一、调试 1、驱动路径 bsp/drivers/input/ctp/gt9xx/gt9xx_ts.c 2、硬件接口 挂载在TWI0下 3、中断复位脚 4、设备树 &twi0 {clock-frequency <400000>;pinctrl-0 <&twi0_pins_default>;pinctrl-1 <&twi0_pins_sleep>;pinctrl-names &quo…

SpringBoot 定时任务实践、定时任务按指定时间执行

Q1. springboot怎样创建定时任务&#xff1f; 很显然&#xff0c;人人都知道&#xff0c;Scheduled(cron ".....") Q2. 如上所示创建了定时任务却未能执行是为什么&#xff1f; 如果你的cron确定没写错的话 cron表达式是否合法&#xff0c;可参考此处&#xff0c…

MAC苹果电脑如何使用Homebrew安装iperf3

一、打开mac终端 找到这个终端打开 二、终端输入安装Homebrew命令 Homebrew官网地址&#xff1a;https://brew.sh/ 复制这个命令粘贴到mac的终端窗口&#xff0c;然后按回车键 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/in…

ModuleNotFoundError: No module named ‘ultralytics.utils‘

项目场景he 问题描述 提示&#xff1a;这里简述项目相关背景&#xff1a; model YOLO(modelr./yolov8m-cls.pt) 加载预训练模型时报错。 ModuleNotFoundError: No module named ultralytics.utils warning: bug: 原因分析&#xff1a; 很可能是提前下载的预训练模型出了…

川川本人著作《Python3编程从零基础到实战》

在数字时代&#xff0c;Python已经成为了一种极为强大和灵活的编程语言&#xff0c;它的应用范围从网站开发到数据科学&#xff0c;再到机器学习和人工智能。无论你是一名编程新手还是希望深化已有技能的开发者&#xff0c;《Python3编程从零基础到实战》将成为你通往Python世界…

css面试题---场景应用

1、实现一个三角形 css一般用border属性实现三角形。 div {width: 0;height: 0;border: 100px solid;border-color: orange blue red green; } // 三角形一 div {width: 0;height: 0;border-top: 50px solid red;border-right: 50px solid transparent;border-left: 50px soli…

SAP HCM 0000与0001信息类型不一致导致 DUMP解决方案

今天遇到一个很奇怪的问题&#xff0c;就是执行PA20与工资核算的时候&#xff0c;系统down掉&#xff0c;不知道什么问题&#xff0c;后来想想遇到这样标准程序界面down掉&#xff0c;无非就是数据问题&#xff0c;增强问题。所以就需要向这个方向靠&#xff0c;HCM核心的信息类…

线性、逻辑回归算法学习

1、什么是一元线性回归 线性&#xff1a;两个变量之间的关系是一次函数&#xff0c;也是数据与数据之间的关系。 回归&#xff1a;人们在测试事物的时候因为客观条件所限&#xff0c;求的都是测试值&#xff0c;而不是真实值&#xff0c;为了无限接近真实值&#xff0c;无限次的…

【自然语言处理八-transformer实现翻译任务-一(输入)】

自然语言处理八-transformer实现翻译任务-一&#xff08;输入&#xff09; transformer架构数据处理部分模型的输入数据(图中inputs outputs outputs_probilities对应的label)以处理英中翻译数据集为例的代码 positional encoding 位置嵌入代码 鉴于transfomer的重要性&#xf…

java——网络编程

什么是计算机网络 把分布在不同地理区域的计算机设备&#xff0c;通过通信设备和线路(网线&#xff0c;光纤&#xff0c;交换机)连接&#xff0c;最终实现数据的传输和资源的共享。 实现不同计算机之间的联系&#xff0c;必须有介质连接 最早的网络&#xff1a;美国五角大楼一…

Go语言不能常量取址!?

题如下图 在软件开发中&#xff0c;常量是一种重要的编程元素&#xff0c;它们在程序中起到固定值的作用被大量使用 Go语言中的常量取址 在 Go 语言中&#xff0c;常量是无法被取址的。这意味着我们不能使用取址操作符 & 来获取常量的地址。例如&#xff1a; const a …

WinRAR功能之【锁定压缩文件】

今天来分享一下WinRAR解压缩软件的“锁定压缩文件”功能&#xff0c;这个功能可以保护压缩包里文件的完整性&#xff0c;也就是不能随意增加、删除以及修改压缩包里的文件。我们可以用两种方式来设置&#xff0c;一起来看看吧&#xff01; 方式1&#xff1a;在压缩文件的时候&a…

Gretel引领革命:发布全球最大开源Text-to-SQL数据集,开启AI培训新篇章|TodayAI

在合成数据行业中处于领先地位的Gretel公司&#xff0c;近日宣布了一项重大突破&#xff0c;推出了世界上最大的开源Text-to-SQL数据集&#xff0c;这一举措将加速AI模型的培训过程&#xff0c;并为全球各行各业的企业解锁新的可能性。 这个数据集包含了超过100,000个精心制作…

msvcp140.dll文件丢失能有什么办法可以解决,提供几种解决办法

哦豁&#xff01;当您打开计算机或想要启动某个应用程序时&#xff0c;意外地弹出一个令人不安的错误提示&#xff1a;“msvcp140.dll文件不见啦&#xff01;”这意味着您的操作系统找不到名为“msvcp140.dll”的重要文件&#xff0c;可能导致一些应用程序无法正常运行。但是别…

集合类多线程(JUC)

一、集合框架概述 集合&#xff1a; 是广义上的集合&#xff0c;简单理解就是容器。注意&#xff1a;集合只能存放对象类型的数据。 集合框架&#xff1a; 指的是 java.util 包中定义的各种容器类、相关的工具类、接口的统称。不同的容器存储不同结 构的数据。使用时要进行导包…

智慧园区水电能源监控管理系统

随着智慧城市的快速发展&#xff0c;智慧园区作为城市智能化的重要组成部分&#xff0c;其能源监控管理系统显得尤为关键。智慧园区水电能源监控管理系统&#xff0c;是利用先进的信息技术和自动控制技术&#xff0c;对园区内的水电能源使用进行实时监控、管理和优化的综合性智…

React 开发者必备技能之Redux基础入门实例

首先,我们需要安装 Redux 及其相关依赖: npm install redux react-reduxredux 是 Redux 库的核心部分,提供了创建 Store、Reducer、Action 等功能。react-redux 是 React 和 Redux 的集成库,用于在 React 组件中使用 Redux。 安装完成后,我们开始创建 Redux 应用程序: 创建 …

HarmonyOS 应用开发-应用异常处理案例

介绍 本示例介绍了通过应用事件打点hiAppEvent获取上一次应用异常信息的方法&#xff0c;主要分为应用崩溃、应用卡死以及系统查杀三种。 效果图预览 使用说明&#xff1a; 点击构建应用崩溃事件&#xff0c;3s之后应用退出&#xff0c;然后打开应用进入应用异常页面&#x…

分享|创业老阳推荐的Temu蓝海项目到底怎么样?

在当今竞争激烈的创业市场中&#xff0c;寻找一个具有潜力的蓝海项目成为了众多创业者的梦想。近日&#xff0c;创业老阳推荐的Temu蓝海项目引起了广泛关注。那么&#xff0c;这个项目到底怎么样呢?让我们一起来探讨一下。 首先&#xff0c;Temu蓝海项目在定位上具有显著优势 …