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());
}
这里是关键点:
- 通过ViewStackProcessor获取到当前正在处理的frameNode,将前端一路传递过来的回调函数保存到frameNode中。
- 将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
- 基本概念
- 构建第一个ArkTS应用
- ……
开发基础知识:https://qr21.cn/FV7h05
- 应用基础知识
- 配置文件
- 应用数据管理
- 应用安全管理
- 应用隐私保护
- 三方应用调用管控机制
- 资源分类与访问
- 学习ArkTS语言
- ……
基于ArkTS 开发:https://qr21.cn/FV7h05
- Ability开发
- UI开发
- 公共事件与通知
- 窗口管理
- 媒体
- 安全
- 网络与链接
- 电话服务
- 数据管理
- 后台任务(Background Task)管理
- 设备管理
- 设备使用信息统计
- DFX
- 国际化开发
- 折叠屏系列
- ……
鸿蒙开发面试真题(含参考答案):https://qr18.cn/F781PH
鸿蒙开发面试大盘集篇(共计319页):https://qr18.cn/F781PH
1.项目开发必备面试题
2.性能优化方向
3.架构方向
4.鸿蒙开发系统底层方向
5.鸿蒙音视频开发方向
6.鸿蒙车载开发方向
7.鸿蒙南向开发方向