Fabric 自定义组件开发指导

HarmonyOS官方账号
发布于 2024-9-29 09:36
浏览
0收藏

​1.编写 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>;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

​和其他标准组件的创建方式一样,在组件容器内添加 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) 
  }} 
/>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

​2.编写ArkTS原生实现代码

Descriptor 的功能是封装 RN 侧组件代码传递到 ArkUI 组件的参数,MarqueeView 对 RN 侧公开了一个 src 参数,用于显示跑马灯的滚动内容。原生侧定义 MarqueeViewDescriptor 代码如下:

export interface MarqueeViewProps extends ViewBaseProps { 
  src: string 
} 
 
export type MarqueeViewDescriptor = Descriptor<"MarqueeView", MarqueeViewProps>;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

Descriptor 不需要我们手动创建,由 rnoh 自动生成;组件 tag 也不需要我们手动设置,rnoh 会为组件自动分配 tag。开发者只需要通过 getDescriptor 方法获取对应 tag 的 Descriptor:

this.descriptor = this.ctx.descriptorRegistry.getDescriptor<MarqueeViewDescriptor>(this.tag)
  • 1.

当 RN 侧传递过来的属性参数发生变化时,我们需要更新 Descripotor:

this.unregisterDescriptorChangesListener = this.ctx.descriptorRegistry.subscribeToDescriptorChanges(this.tag, (newDescriptor) => { 
    this.descriptor = (newDescriptor as MarqueeViewDescriptor) 
})
  • 1.
  • 2.
  • 3.

RN 调用原生方法

RN 侧调用 UIManager.dispatchViewManagerCommand 向原生发送消息:

UIManager.dispatchViewManagerCommand( 
  findNodeHandle(nativeRef.current), 
  'toggleMarqueeState', 
  [], 
)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

原生组件通过 commandDispatcher.registerCommandCallback 接收消息并执行对应方法:

this.ctx.commandDispatcher.registerCommandCallback(this.tag, (commandName) => { 
  if (commandName === "toggleMarqueeState") { 
    this.start = !this.start 
    console.log("will emitComponentEvent"); 
  } 
})
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

原生组件调用 RN 侧方法

RN 侧添加 onStop 方法实现:

<MarqueeView 
  ... 
  onStop={(e) => { 
    // 原生组件调用了 RN 侧的 MarqueeView 的 onStop 方法 
    const isStop = e.nativeEvent.isStop 
    ... 
  }} 
/>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

原生侧发送调用 RN 组件事件的消息:

this.ctx.rnInstance.emitComponentEvent( 
  this.descriptor.tag, 
  "MarqueeView", 
  { type: "onStop", isStop: !this.start } 
)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

buildCustomComponent

创建 RNSurface 加载 JSBundle 时,传入 buildCustomComponent 用于加载原生 Fabric 组件:

import { RNAbility, ComponentBuilderContext, RNSurface } from "rnoh"; 
import { MarqueeView } from '../customView/MarqueeView' 
 
@Builder 
public buildCustomComponent(ctx: ComponentBuilderContext) { 
  if (ctx.descriptor.type === MarqueeView.NAME) { 
    MarqueeView({ 
      ctx: ctx.rnohContext, 
      tag: ctx.descriptor.tag 
    }) 
  } 
} 
... 
 
RNSurface({ 
  ... 
  buildCustomComponent: this.buildCustomComponent, 
})
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

3. 编写 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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.

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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

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; 
        } 
    }; 
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.

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 { 
    facebook::jsi::Object createNativeProps(facebook::jsi::Runtime &rt) override { 
        auto object = ViewComponentJSIBinder::createNativeProps(rt); 
        object.setProperty(rt, "src", "string"); 
        return object; 
    } 
 
    facebook::jsi::Object createDirectEventTypes(facebook::jsi::Runtime &rt) override { 
        facebook::jsi::Object events(rt); 
        events.setProperty(rt, "topStop", createDirectEvent(rt, "onStop")); 
        return events; 
    } 
}; 
} // namespace rnoh
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

SampleTurboModulePackage

在 SampleTurboModulePackage.h 中添加自定义组件相关的方法声明:

#include "RNOH/Package.h" 
 
namespace rnoh { 
class SampleTurboModulePackage : public Package { 
    public: 
        std::vector<facebook::react::ComponentDescriptorProvider> createComponentDescriptorProviders() override; 
        ComponentNapiBinderByString createComponentNapiBinderByName() override; 
        ComponentJSIBinderByString createComponentJSIBinderByName() override; 
        EventEmitRequestHandlers createEventEmitRequestHandlers() override; 
    }; 
} // namespace rnoh
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

使用 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>()}}; 
};
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

4. 优化原生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; 
 
  //提供单例方法获取MarqueeModifier实例。 
  public static getInstance(): MarqueeModifier { 
    if (!MarqueeModifier.instance) { 
      MarqueeModifier.instance = new MarqueeModifier (); 
    } 
    return MarqueeModifier.instance; 
  } 
 
  //提供方法设置该组件的描述信息,后面通过解析该描述信息得到该组件实例需要注册的属性和事件。 
  setDescriptor(descriptor: ViewBaseDescriptor): MarqueeModifier { 
    this.descriptor = descriptor; 
    return MarqueeModifier.instance; 
  } 
 
  //接口方法,ArkUI会调用该方法完成最终的MarqueeAttribute操作。 
  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 
export function marqueeBuilder(ctx: RNOHContext, descriptor: ViewBaseDescriptor) { 
  Marquee(···) { 
    //通过AttributeModifier方法动态获取该组件实例化需要注册的属性和事件 
    .attributeModifier(MarqueeModifier.getInstance().setDescriptor(descriptor)) 
  } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.

如何创建C-API自定义组件

​创建一个 Fabric 组件需要实现以下的代码:

  • ComponentInstance:ComponentInstance文件是该自定义组件所有的逻辑集合,也是自定义组件主要需要实现的部分,简单理解就是,一个自定义组件文件对应一个ComponentInstance。
  • Package文件:作用是声明创建该自定义组件的声明,指定创建Instance时会根据Packeage文件内的名称对应,从而生成对应的Instance对象。
  • Props:Props是ComponentInstance的props参数声明,具体作用是JS的参数传递,在自定义组件内部由父组件往子组件发送数据的时候也需要用到props。
  • EventEmitter:EventEmitter是ComponentInstance的事件声明,主要作用是获取前端设置的事件回调,在组件内部以合适的时机触发。
  • ShadowNode:ShadowNode是ComponentInstance创建时所需要声明的其中一个类,将对应的Props,EventEmitter和ComponentName(就是创建ComponentInstance的名字)组合起来。

(1)编写 RN 调用 Fabric 组件的代码

本节以 ButtonView 为例,介绍了 Fabric C-API 自定义组件的实现步骤。

编写ButtonViewNativeComponent.tsx,注意,如果要使用 Codegen ,文件必须以<ModuleName>NativeComponent命名。在文件中使用 codegenNativeComponent 创建 ButtonView 组件,其中 ButtonViewProps 里声明了 buttonText 属性和 onButtonClick 事件:​

export type OnButtonClickEventData = Readonly<{ 
    isButtonClick: boolean, 
    type: string, 
}>; 
 
export interface ButtonViewProps extends ViewProps { 
    buttonText: string, 
    onButtonClick?: DirectEventHandler<OnButtonClickEventData>; 
} 
 
export default codegenNativeComponent<ButtonViewProps>( 
    'CustomButtonView', 
) as HostComponent<ButtonViewProps>;
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

和其他标准组件的创建方式一样,在组件容器内添加 ButtonView 标签:

<ButtonView 
  buttonText={"ButtonView: " + (buttonClick ? 'Click' : 'No Click')} 
  ref={nativeRefButton} 
  style= {{height: 50}} 
  onButtonClick={(e) => { 
    setButtonClick(e.nativeEvent.isButtonClick); 
  }} 
/>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

(2)编写C-API 原生实现代码

C-API组件结构

原生端自定义组件主要包含两个部分,ButtonViewComponentInstance.cpp 以及 ButtonViewNode.cpp,其中ButtonViewComponentInstance为JS侧ButtonView组件对应的原生端实例,它继承RN框架中的组件实例模板类CppComponentInstance并转换为自己组件实现的ShadowNode类型,它应该重写基类上的一些通用方法,包括onChildInserted插入子节点、getLocalRootArkUINode获取根节点以及onPropsChanged设置组件属性等,也可以实现组件的特有方法。同时它继承ButtonViewNode中声明的代理类ButtonViewNodeDelegate,并重写代理类上的onXXX事件用于上报事件。CppComponentInstance是所有自定义组件ComponentInstance的父类,所有组件都继承于这个类,这个类包含了对组件进行操作的一些基础方法。

// ButtonViewComponentInstance.h 
#include "RNOH/CppComponentInstance.h" 
#include "ButtonViewNode.h" 
#include "ButtonViewComponentDescriptor.h" 
... 
 
namespace rnoh { 
    class ButtonViewComponentInstance : public CppComponentInstance<facebook::react::ButtonViewShadowNode>, public ButtonViewNodeDelegate { 
    private: 
        ButtonViewNode m_buttonViewNode; 
        ... 
    public: 
        // 重写CppComponentInstance中的通用方法 
        ButtonViewComponentInstance(Context context); 
        void onChildInserted(ComponentInstance::Shared const &childComponentInstance, std::size_t index) override; 
        void onChildRemoved(ComponentInstance::Shared const &childComponentInstance) override; 
        ButtonViewNode &getLocalRootArkUINode() override; 
        void onPropsChanged(SharedConcreteProps const &props) override; 
        // 处理Command命令 
        void handleCommand(std::string const &commandName, folly::dynamic const &args) override; 
        ... 
        // 重写ButtonViewNodeDelegate的事件上报方法 
        void onButtonClick() override; 
 
        ... 
     }; 
} // namespace rnoh
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

ButtonViewNode对应具体的鸿蒙原生组件节点,它作为成员被组件实例持有,当设置属性或处理指令的时候,ButtonViewComponentInstance会调用ButtonViewNode上实现的具体方法,以ButtonView在RN侧暴露的buttonText属性为例,在JS侧设置或更新属性的时候,框架会调用ButtonViewComponentInstance上的onPropsChanged方法,这个方法中会去调用ButtonViewNode上的setLabel方法去设置资源。

#include "RNOH/arkui/ArkUINode.h" 
#include "RNOH/arkui/NativeNodeApi.h" 
... 
 
namespace rnoh { 
 
    // 代理类的声明 
    class ButtonViewNodeDelegate { 
    public: 
        virtual ~ButtonViewNodeDelegate() = default; 
        // 组件的事件接口声明 
        virtual void onButtonClick(){}; 
    }; 
 
    class ButtonNode : public ArkUINode { 
      protected: 
        ButtonNodeDelegate* m_buttonNodeDelegate; 
 
      public: 
        ButtonNode(); 
        ~ButtonNode(); 
 
        // 实现Node的通用方法 
        void insertChild(ArkUINode& child, std::size_t index); 
        void removeChild(ArkUINode& child); 
        void onNodeEvent(ArkUI_NodeEventType eventType, EventArgs& eventArgs) override; 
        ButtonNode& setButtonNodeDelegate(ButtonNodeDelegate* buttonNodeDelegate); 
        ··· 
        // 实现组件的属性设置方法 
        ButtonNode& setLabel(const std::string &src); 
    }; 
 
} // namespace rnoh
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.

属性

​在 RN 侧设置的属性,会通过props传递到ComponentInstance中。通过ComponentInstance中的onPropsChanged可以获取到变化后的 Props。在onPropsChanged中需要做一次diff判断,然后就可以调用ComponentInstance保存的ComponentNode对象所实现的各种属性设置方法:

// ButtonViewComponentInstance.cpp 
void ButtonViewComponentInstance::onPropsChanged( 
    SharedConcreteProps const& props) { 
  CppComponentInstance::onPropsChanged(props); 
  if (!m_props || props->buttonText != m_props->buttonText) { 
    m_buttonNode.setLabel(props->buttonText); 
  } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

在ComponentNode中需要定义并实现对应属性的设置:

// ButtonNode.cpp 
ButtonNode& ButtonNode::setLabel(const std::string &src) { 
  ArkUI_AttributeItem labelItem = {.string = src.c_str()}; 
  maybeThrow(NativeNodeApi::getInstance()->setAttribute( 
      m_nodeHandle, NODE_BUTTON_LABEL, &labelItem)); 
  return *this; 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

指令

RN 侧需要调用 UIManager.dispatchViewManagerCommand 向原生发送消息:

UIManager.dispatchViewManagerCommand( 
  findNodeHandle(nativeRef.current), 
  'changeButtonText', 
  ['changeButtonText'], 
)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

RN框架内部已经封装好了指令通道,原生端只需在instance层重写实现handleCommand方法,根据接收指令名调用node层实现的对应方法即可,传入的参数以object对象的形式记录在args中:

void ButtonViewComponentInstance::handleCommand( 
    std::string const& commandName, 
    folly::dynamic const& args) { 
  if (commandName == "changeButtonText") { 
      m_buttonNode.setLabel(args[0].asString()); 
    } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

事件

RN侧添加 onButtonClick 事件回调监听实现:

<ButtonView 
  ··· 
  onButtonClick={(e) => { 
    // 原生组件调用了 RN 侧的 ButtonView 的 onButtonClick 方法 
    setButtonClick(e.nativeEvent.isButtonClick); 
  }} 
/>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

原生端在instance层重写事件代理类中的onButtonClick方法,并在该方法中调用ButtonViewEventEmitter.h的对应方法,将事件传到RN侧:

void ButtonViewComponentInstance::onButtonClick() { 
  facebook::react::ButtonViewEventEmitter::OnButtonClick m_onButtonClick; 
  m_onButtonClick.isButtonClick = true; 
  m_onButtonClick.type = "custom"; 
  m_eventEmitter->onButtonClick(m_onButtonClick); 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

事件的接收入口在对应的node类中,node类在构造的时候需要注册组件需要监听的事件,并重写onNodeEvent方法,在该方法中调用instance层实现的具体事件方法:

// 组件监听的事件枚举 
static constexpr ArkUI_NodeEventType Button_NODE_EVENT_TYPES[] = { 
    NODE_ON_CLICK}; 
··· 
// 在构造Node的时候注册事件监听 
ButtonNode::ButtonNode() 
    : ArkUINode(NativeNodeApi::getInstance()->createNode( 
          ArkUI_NodeType::ARKUI_NODE_BUTTON)) { 
  for (auto eventType : Button_NODE_EVENT_TYPES) { 
    maybeThrow(NativeNodeApi::getInstance()->registerNodeEvent( 
        m_nodeHandle, eventType, eventType, this)); 
  } 
} 
··· 
// 重写onNodeEvent上报事件 
void ButtonNode::onNodeEvent( 
    ArkUI_NodeEventType eventType, EventArgs& eventArgs) { 
  if (eventType == ArkUI_NodeEventType::NODE_ON_CLICK) { 
    if (m_buttonNodeDelegate != nullptr) { 
      m_buttonNodeDelegate->onButtonClick(); 
    } 
  } 
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

(3)编写 Codegen 的 C++ 代码

由于当前C-API版本的Codegen尚未实现,所以C-API版本的组件需要开发者手动添加在其他平台上由 Codegen 生成的 C++ 代码。

  • 首先创建属性 Props 和 事件 Emitter 两部分的 C++ 类,在 Descriptor 中进行绑定并注册Node类型。
  • 实现 ButtonViewJSIBinder 类的属性和事件绑定方法。
  • 将以上文件引入到 SampleTurboModulePackage 的对应方法实现中进行绑定。

Props

创建 Props 的 C++ 文件用于定义 ButtonView 的属性。Props.h:

#pragma once 
 
#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 ButtonViewProps final : public ViewProps { 
  public: 
    ButtonViewProps() = default; 
    ButtonViewProps(const PropsParserContext &context, const ButtonViewProps &sourceProps, const RawProps &rawProps); 
 
#pragma mark - Props 
 
    std::string buttonText{""}; 
}; 
 
} // 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 { 
ButtonViewProps::ButtonViewProps( 
    const PropsParserContext &context,  
    const ButtonViewProps &sourceProps,  
    const RawProps &rawProps): ViewProps(context, sourceProps, rawProps), 
 
    buttonText(convertRawProp(context, rawProps, "buttonText", sourceProps.buttonText, {""})) 
      {} 
} // namespace react 
} // namespace facebook
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.

ButtonViewEventEmitter

ButtonViewEventEmitter.h 中添加 onButtonClick 方法,并自定义了属性结构体:

#pragma once 
 
#include <react/renderer/components/view/ViewEventEmitter.h> 
#include <jsi/jsi.h> 
 
namespace facebook { 
namespace react { 
 
class JSI_EXPORT ButtonViewEventEmitter : public ViewEventEmitter { 
  public: 
    using ViewEventEmitter::ViewEventEmitter; 
    struct OnButtonClick { 
      bool isButtonClick; 
      std::string type; 
    }; 
 
    void onButtonClick(OnButtonClick value) const; 
}; 
 
} // namespace react 
} // namespace facebook 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

ButtonViewEventEmitter.cpp 中实现 onButtonClick 事件的发送和参数绑定:

#include "ButtonViewEventEmitter.h" 
 
namespace facebook { 
namespace react { 
 
void ButtonViewEventEmitter::onButtonClick(OnButtonClick event) const { 
    dispatchEvent("topButtonClick", [event = std::move(event)](jsi::Runtime &runtime) { 
        auto payload = jsi::Object(runtime); 
        payload.setProperty(runtime, "isButtonClick", event.isButtonClick); 
        payload.setProperty(runtime, "type", event.type); 
        return payload; 
    }); 
} 
 
} // namespace react 
} // namespace facebook 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

ButtonViewComponentDescriptor.h

将 ButtonViewProps, ButtonViewEventEmitter 绑定到 ButtonViewShadowNode中:

#include <react/renderer/core/ConcreteComponentDescriptor.h> 
#include <react/renderer/components/view/ConcreteViewShadowNode.h> 
#include <react/renderer/components/view/ViewShadowNode.h> 
#include "ButtonViewEventEmitter.h" 
#include "Props.h" 
 
namespace facebook { 
namespace react { 
 
const char ButtonViewComponentName[] = "ButtonView"; 
 
  using ButtonViewShadowNode = ConcreteViewShadowNode<ButtonViewComponentName, ButtonViewProps, ButtonViewEventEmitter>; 
  using ButtonViewComponentDescriptor = ConcreteComponentDescriptor<ButtonViewShadowNode>; 
 
} // namespace react 
} // namespace facebook 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

ButtonViewJSIBinder

JSIBinder 是 RN 侧的属性和方法在 JSI 层的实现,主要调用了 object.setProperty(rt, "buttonText", "string") 和 events.setProperty(rt, "topButtonClick", createDirectEvent(rt, "onButtonClick")); 这两个方法,events.setProperty 中注意 topButtonClick 和 onButtonClick 的命名规则:

#pragma once 
#include "RNOHCorePackage/ComponentBinders/ViewComponentJSIBinder.h" 
 
namespace rnoh { 
    class ButtonViewJSIBinder : public ViewComponentJSIBinder { 
        facebook::jsi::Object createNativeProps(facebook::jsi::Runtime &rt) override { 
            auto object = ViewComponentJSIBinder::createNativeProps(rt); 
            object.setProperty(rt, "buttonText", "string"); 
            return object; 
        } 
 
        facebook::jsi::Object createDirectEventTypes(facebook::jsi::Runtime &rt) override { 
            facebook::jsi::Object events(rt); 
 
            events.setProperty(rt, "topButtonClick", createDirectEvent(rt, "onButtonClick")); 
            return events; 
        } 
    }; 
} // namespace rnoh
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

SampleTurboModulePackage

在SampleTurboModulePackage.h中添加自定义组件相关的方法声明:

#include "RNOH/Package.h" 
 
namespace rnoh { 
class SampleTurboModulePackage : public Package { 
    public: 
        ComponentInstanceFactoryDelegate::Shared createComponentInstanceFactoryDelegate() override; 
        ComponentJSIBinderByString createComponentJSIBinderByName() override; 
        // ArkTS版本使用的通用方法 
        ··· 
    }; 
} // namespace rnoh
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

创建ButtonViewPackageComponentInstanceFactoryDelegate对象,使用ButtonViewPackageComponentInstanceFactoryDelegate、ButtonViewJSIBinder、ButtonViewComponentInstance 在 SampleTurboModulePackage.cpp 中完成对应方法实现:

class ButtonViewPackageComponentInstanceFactoryDelegate : public ComponentInstanceFactoryDelegate { 
    public: 
        using ComponentInstanceFactoryDelegate::ComponentInstanceFactoryDelegate; 
 
        ComponentInstance::Shared create(ComponentInstance::Context ctx) override { 
            if (ctx.componentName == "CustomButtonView") { 
                return std::make_shared<ButtonViewComponentInstance>(std::move(ctx)); 
            } 
            return nullptr; 
        } 
}; 
··· 
ComponentJSIBinderByString SampleTurboModulePackage::createComponentJSIBinderByName() { 
    return { 
        {"MarqueeView", std::make_shared<MarqueeViewJSIBinder>(),}, 
        {"ButtonView", std::make_shared<ButtonViewJSIBinder>()} 
    }; 
}; 
 
ComponentInstanceFactoryDelegate::Shared SampleTurboModulePackage::createComponentInstanceFactoryDelegate() override { 
    return std::make_shared<ButtonViewPackageComponentInstanceFactoryDelegate>(); 
} 
···
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

(4)在自定义组件中使用其他自定义组件

在自定义的组件中,可能并不是由一个基础组件实现的,而是由多个基础组件相互组合实现,例如自定义的Scroll,内部可以持有一个Stack组件。每一个组件的ComponentInstance中,都持有各个组件的node对象,node中提供了修改属性发送事件等各种操作。所以开发者可以在同一个ComponentInstance中记录多个node对象,通过重写getLocalRootArkUINode()方法,用于定义对外暴露的组件节点;重写onChildInserted()方法,用于将子节点插入组件的容器类中;并在ComponentInstance中增加多个node之间相互处理的逻辑,即可完成开发。​


分类
收藏
回复
举报


回复
    相关推荐
    这个用户很懒,还没有个人简介
    觉得TA不错?点个关注精彩不错过
    帖子
    视频
    声望
    粉丝
    社区精华内容