HarmonyOS 鸿蒙Next RNOH如何写一个Fabric组件
HarmonyOS 鸿蒙Next RNOH如何写一个Fabric组件
问题如题 我写了一个让内容变灰的组件,但是提供给rn后传递后子view展示不出来,this.descriptor.childrenTags是空的
export function providerArkTsComponentNames() {
return [GRAY_VIEW_TYPE];
}
@Builder
export function buildCustomComponent(ctx: ComponentBuilderContext) {
if (ctx.componentName === GRAY_VIEW_TYPE) {
RNGrayView({ ctx: ctx.rnComponentContext, tag: ctx.tag, buildCustomComponent: buildCustomComponent })
}
}
更多关于HarmonyOS 鸿蒙Next RNOH如何写一个Fabric组件的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
Fabric 自定义组件开发指导
ArkTS版本自定义组件
本节以示例工程中的 MarqueeView 为例,介绍了 Fabric ArkTS 自定义组件的实现步骤。
- 编写 RN 调用 Fabric 组件的代码
编写MarqueeViewNativeComponent.tsx,注意,如果要使用 Codegen ,文件必须以<ModuleName>NativeComponent命名。在文件中使用 codegenNativeComponent 创建 MarqueeView 组件,其中 MarqueeViewProps 里声明了 src 属性和 onStop 事件:
type OnStopEventData = Readonly<{
isStop: boolean
}>;
interface MarqueeViewProps extends ViewProps {
src: string,
onStop?: DirectEventHandler<OnStopEventData>;
}
const MarqueeView = codegenNativeComponent<MarqueeViewProps>(
'MarqueeView'
) as HostComponent<MarqueeViewProps>;
和其他标准组件的创建方式一样,在组件容器内添加 MarqueeView 标签:
<MarqueeView
src="双十一大促,消费是社会再生产过程中的一个重要环节,也是最终环节。它是指利用社会产品来满足人们各种需要的过程。"
style={{height: 180, width: '100%', backgroundColor: 'hsl(210, 80%, 50%)'}}
onStop={(e) => {
SampleTurboModule.rnLog("native调用了RN的 onStop,isStop = "+e.nativeEvent.isStop)
setMarqueeStop(e.nativeEvent.isStop)
}}
/>
- 编写 ArkTS 原生实现代码
Descriptor
Descriptor 的功能是封装 RN 侧组件代码传递到 ArkUI 组件的参数,MarqueeView 对 RN 侧公开了一个 src 参数,用于显示跑马灯的滚动内容。原生侧定义 MarqueeViewDescriptor 代码如下:
export interface MarqueeViewProps extends ViewBaseProps {
src: string
}
export type MarqueeViewDescriptor = Descriptor<"MarqueeView", MarqueeViewProps>;
Descriptor 不需要我们手动创建,由 rnoh 自动生成;组件 tag 也不需要我们手动设置,rnoh 会为组件自动分配 tag。开发者只需要通过 getDescriptor 方法获取对应 tag 的 Descriptor:
this.descriptor = this.ctx.descriptorRegistry.getDescriptor<MarqueeViewDescriptor>(this.tag)
当 RN 侧传递过来的属性参数发生变化时,我们需要更新 Descripotor:
this.unregisterDescriptorChangesListener = this.ctx.descriptorRegistry.subscribeToDescriptorChanges(
this.tag,
(newDescriptor) => {
this.descriptor = (newDescriptor as MarqueeViewDescriptor)
})
RN 调用原生方法
RN 侧调用 UIManager.dispatchViewManagerCommand 向原生发送消息:
UIManager.dispatchViewManagerCommand(
findNodeHandle(nativeRef.current),
'toggleMarqueeState',
[],
)
原生组件通过 commandDispatcher.registerCommandCallback 接收消息并执行对应方法:
this.ctx.commandDispatcher.registerCommandCallback(
this.tag,
(commandName) => {
if (commandName === "toggleMarqueeState") {
this.start = !this.start
console.log("will emitComponentEvent");
}
}
)
原生组件调用 RN 侧方法
RN 侧添加 onStop 方法实现:
<MarqueeView
...
onStop={(e) => {
// 原生组件调用了 RN 侧的 MarqueeView 的 onStop 方法
const isStop = e.nativeEvent.isStop
...
}}
/>
原生侧发送调用 RN 组件事件的消息:
this.ctx.rnInstance.emitComponentEvent(
this.descriptor.tag,
"MarqueeView",
{ type: "onStop", isStop: !this.start }
)
buildCustomComponent
创建 RNSurface 加载 JSBundle 时,传入 buildCustomComponent 用于加载原生 Fabric 组件:
import { RNAbility, ComponentBuilderContext, RNSurface } from "rnoh";
import { MarqueeView } from '../customView/MarqueeView'
[@Builder](/user/Builder)
public buildCustomComponent(ctx: ComponentBuilderContext) {
if (ctx.descriptor.type === MarqueeView.NAME) {
MarqueeView({
ctx: ctx.rnohContext,
tag: ctx.descriptor.tag
})
}
}
...
RNSurface({
...
buildCustomComponent: this.buildCustomComponent,
})
- 编写 Codegen 的 C++ 代码
开发者可以使用 Codegen 生成C++侧的胶水代码,也可以手动实现这部分代码。在本节中会详细介绍如何手动实现这部分代码。
首先创建属性 Props 和 事件 Emitter 两部分的 C++ 类,在 Descriptor 中进行绑定。
实现 MarqueeViewEventEmitRequestHandler 的 handleEvent 方法,根据原生消息的事件名,调用 eventEmitter 向 RN 侧组件发送事件消息。
实现 MarqueeViewJSIBinder 类的属性和事件绑定方法。
实现 MarqueeViewNapiBinder 类的属性映射方法。
将以上文件引入到 SampleTurboModulePackage 的对应方法实现中进行绑定。
Props
创建 Props 的 C++ 文件用于定义 MarqueeView 的 Descriptor 对应的属性。
Props.h:
#include <jsi/jsi.h>
#include <react/renderer/components/view/ViewProps.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/debug/react_native_assert.h>
namespace facebook {
namespace react {
class JSI_EXPORT MarqueeViewProps final : public ViewProps {
public:
MarqueeViewProps() = default;
MarqueeViewProps(const PropsParserContext &context, const MarqueeViewProps &sourceProps, const RawProps &rawProps);
#pragma mark - Props
std::string src{""};
};
} // namespace react
} // namespace facebook
// Props.cpp
#include <react/renderer/components/rncore/Props.h>
#include <react/renderer/core/PropsParserContext.h>
#include <react/renderer/core/propsConversions.h>
#include "Props.h"
namespace facebook {
namespace react {
MarqueeViewProps::MarqueeViewProps(
const PropsParserContext &context,
const MarqueeViewProps &sourceProps,
const RawProps &rawProps): ViewProps(context, sourceProps, rawProps),
src(convertRawProp(context, rawProps, "src", sourceProps.src, {""}))
{}
} // namespace react
} // namespace facebook
MarqueeViewEventEmitter
MarqueeViewEventEmitter.h 中添加 onStop 方法,并自定义了属性结构体:
#include <react/renderer/components/view/ViewEventEmitter.h>
#include <jsi/jsi.h>
namespace facebook {
namespace react {
class JSI_EXPORT MarqueeViewEventEmitter : public ViewEventEmitter {
public:
using ViewEventEmitter::ViewEventEmitter;
struct OnStop {
bool isStop;
};
void onStop(OnStop value) const;
};
} // namespace react
} // namespace facebook
MarqueeViewEventEmitter.cpp 中实现 onStop 事件的发送和参数绑定:
#include "MarqueeViewEventEmitter.h"
namespace facebook {
namespace react {
void MarqueeViewEventEmitter::onStop(OnStop event) const {
dispatchEvent("stop", [event = std::move(event)](jsi::Runtime &runtime) {
auto payload = jsi::Object(runtime);
payload.setProperty(runtime, "isStop", event.isStop);
return payload;
});
}
} // namespace react
} // namespace facebook
MarqueeViewComponentDescriptor.h
将 MarqueeViewProps, MarqueeViewEventEmitter 绑定到 MarqueeViewComponentDescriptor 中:
#include <react/renderer/core/ConcreteComponentDescriptor.h>
#include <react/renderer/components/view/ConcreteViewShadowNode.h>
#include <react/renderer/components/view/ViewShadowNode.h>
#include "MarqueeViewEventEmitter.h"
#include "Props.h"
namespace facebook {
namespace react {
extern const char MarqueeViewComponentName[] = "MarqueeView";
using MarqueeViewShadowNode = ConcreteViewShadowNode<MarqueeViewComponentName, MarqueeViewProps, MarqueeViewEventEmitter>;
using MarqueeViewComponentDescriptor = ConcreteComponentDescriptor<MarqueeViewShadowNode>;
} // namespace react
} // namespace facebook
MarqueeViewEventEmitRequestHandler
handleEvent 方法中根据事件名调用事件消息发送方法 eventEmitter->onStop(event):
class MarqueeViewEventEmitRequestHandler : public EventEmitRequestHandler {
public:
void handleEvent(EventEmitRequestHandler::Context const &ctx) override {
if (ctx.eventName != "MarqueeView") {
return;
}
ArkJS arkJs(ctx.env);
auto eventEmitter = ctx.shadowViewRegistry->getEventEmitter<react::MarqueeViewEventEmitter>(ctx.tag);
if (eventEmitter == nullptr) {
return;
}
MarqueeViewEventType type = getMarqueeViewEventType(arkJs, ctx.payload);
switch (type) {
case MarqueeViewEventType::MARQUEE_VIEW_ON_STOP: {
bool isStop = (bool)arkJs.getBoolean(arkJs.getObjectProperty(ctx.payload, "isStop"));
react::MarqueeViewEventEmitter::OnStop event{isStop};
eventEmitter->onStop(event);
break;
}
default:
break;
}
};
};
MarqueeViewJSIBinder
JSIBinder 是 RN 侧的属性和方法在 JSI 层的实现,主要调用了 object.setProperty(rt, “src”, “string”) 和 events.setProperty(rt, “topStop”, createDirectEvent(rt, “onStop”)) 这两个方法,events.setProperty 中注意 topStop 和 onStop 的命名规则:
#pragma once
#include "RNOHCorePackage/ComponentBinders/ViewComponentJSIBinder.h"
namespace rnoh {
class MarqueeViewJSIBinder : public ViewComponentJSIBinder {
public:
jsi::Object createNativeProps(facebook::jsi::Runtime &rt) override {
auto object = ViewComponentJSIBinder::createNativeProps(rt);
object.setProperty(rt, "src", "string");
return object;
}
jsi::Object createDirectEventTypes(facebook::jsi::Runtime &rt) override {
jsi::Object events(rt);
events.setProperty(rt, "topStop", createDirectEvent(rt, "onStop"));
return events;
}
};
} // namespace rnoh
NapiBinder
实现 C++ 代码和原生组件代码之间的属性映射,其中 .addProperty(“src”, props->src) 为 MarqueeViewDescriptor 的 props 增加了 src 字段;如果未添加该代码,MarqueeView 就需要从 rawProps 中获取 src:
#include "RNOHCorePackage/ComponentBinders/ViewComponentNapiBinder.h"
#include "Props.h"
namespace rnoh {
class MarqueeViewNapiBinder : public ViewComponentNapiBinder {
public:
napi_value createProps(napi_env env, facebook::react::ShadowView const shadowView) override {
napi_value napiViewProps = ViewComponentNapiBinder::createProps(env, shadowView);
if (auto props = std::dynamic_pointer_cast<const facebook::react::MarqueeViewProps>(shadowView.props)) {
return ArkJS(env)
.getObjectBuilder(napiViewProps)
.addProperty("src", props->src)
.build();
}
return napiViewProps;
}
};
} // namespace rnoh
SampleTurboModulePackage
在 SampleTurboModulePackage.h 中添加自定义组件相关的方法声明:
#include "RNOH/Package.h"
namespace rnoh {
class SampleTurboModulePackage : public Package {
public:
std::vector<facebook::react::ComponentDescriptorProvider> createComponentDescriptorProviders();
ComponentNapiBinderByString createComponentNapiBinderByName();
ComponentJSIBinderByString createComponentJSIBinderByName();
EventEmitRequestHandlers createEventEmitRequestHandlers();
};
} // namespace rnoh
使用 MarqueeViewComponentDescriptor、MarqueeViewEventEmitRequestHandler、MarqueeViewNapiBinder、MarqueeViewJSIBinder 在 SampleTurboModulePackage.cpp 中完成对应方法实现:
std::vector<react::ComponentDescriptorProvider> SampleTurboModulePackage::createComponentDescriptorProviders() {
return {
react::concreteComponentDescriptorProvider<react::MarqueeViewComponentDescriptor>(),
};
}
EventEmitRequestHandlers SampleTurboModulePackage::createEventEmitRequestHandlers() {
return {std::make_shared<MarqueeViewEventEmitRequestHandler>()};
}
ComponentNapiBinderByString SampleTurboModulePackage::createComponentNapiBinderByName() {
return {{"MarqueeView", std::make_shared<MarqueeViewNapiBinder>()}};
};
ComponentJSIBinderByString SampleTurboModulePackage::createComponentJSIBinderByName() {
return {{"MarqueeView", std::make_shared<MarqueeViewJSIBinder>()}};
};
- 优化原生ArkTS组件
之前介绍的ArkTS组件实现中,是通过调用对应的属性设置接口完成属性的设置,这种实现方式存在两个缺点:
自定义组件属性过多,影响执行效率:若需要使用系统组件的全量属性方法,则需在封装的自定义组件中注册穷举每个属性值。这样会大大影响每个组件的Build效率。
不利于后期维护:当自定义组件中的系统组件属性发生变更时,自定义组件也需要同步适配。
为了解决上述缺点,ArkTS为每个系统组件提供了动态属性设置的方式,包括attributeModifier属性方法。该方法将组件属性设置分离到系统提供的AttributeModifier接口实现类实例中,通过自定义Class类实现AttributeModifier接口对系统组件属性进行扩展。
export class MarqueeModifier implements AttributeModifier<MarqueeAttribute> {
private constructor() {}
private static instance: MarqueeModifier;
protected descriptor: ViewBaseDescriptor = {} as ViewBaseDescriptor;
public static getInstance(): MarqueeModifier {
if (!MarqueeModifier.instance) {
MarqueeModifier.instance = new MarqueeModifier ();
}
return MarqueeModifier.instance;
}
setDescriptor(descriptor: ViewBaseDescriptor): MarqueeModifier {
this.descriptor = descriptor;
return MarqueeModifier.instance;
}
applyNormalAttribute(instance: MarqueeAttribute): void {
instance.width(this.descriptor.layoutMetrics.frame.size.width);
instance.height(this.descriptor.layoutMetrics.frame.size.height);
instance.position({ y: this.descriptor.layoutMetrics.frame.origin.y, x: this.descriptor.layoutMetrics.frame.origin.x });
if (this.descriptor.props.backgroundColor) {
instance.backgroundColor(this.descriptor.props.backgroundColor);
}
/* ...... 其他需要设置的属性*/
}
}
[@Builder](/user/Builder)
export function marqueeBuilder(ctx: RNOHContext, descriptor: ViewBaseDescriptor) {
Marquee(···) {
.attributeModifier(MarqueeModifier.getInstance().setDescriptor(descriptor))
}
}
``
更多关于HarmonyOS 鸿蒙Next RNOH如何写一个Fabric组件的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
在HarmonyOS(鸿蒙)系统中,编写一个Fabric组件主要涉及到使用ArkUI框架进行UI组件的开发。Fabric组件通常指的是基于声明式UI的组件,这与ArkUI的设计理念相吻合。以下是关于如何编写一个Fabric组件的简要说明:
-
环境准备: 确保你的开发环境已经配置好HarmonyOS SDK,并且已经安装了必要的开发工具,如DevEco Studio。
-
创建项目: 在DevEco Studio中创建一个新的HarmonyOS项目,选择使用ArkUI(eTS或JS)进行开发。
-
定义组件: 在项目的
src/main/ets/pages
目录下创建一个新的.ets
文件(如果是使用eTS语言),或者在src/main/resources/base/common
下创建一个.json
和.hml
文件(如果是使用JS语言)。在文件中定义你的Fabric组件,包括其属性和布局。 -
实现逻辑: 在
.ets
或.js
文件中编写组件的逻辑代码,包括事件处理、数据绑定等。 -
使用组件: 在其他页面或组件中引用你定义的Fabric组件,并传递必要的属性和事件。
-
编译与运行: 使用DevEco Studio编译并运行你的项目,查看Fabric组件的效果。
如果问题依旧没法解决请联系官网客服,官网地址是 https://www.itying.com/category-93-b0.html