定制界面组件

App开发中会使用大量UI组件,Hippy SDK已经包含了一些基本UI,比如View, Text, Image等。而用户若想进行自定义组件也十分简单。

组件扩展

我们以创建MyView为例,从头介绍如何扩展一个组件。

本文仅介绍ios端工作,前端工作请查看对应的文档。

扩展一个UI组件需要包括以下工作:

  1. 创建对应的ViewManager
  2. 注册类并绑定前端组件
  3. 绑定View属性及方法
  4. 创建对应的shadowView和View

创建对应的ViewManager

ViewManager是对应的视图管理组件,负责前端视图和终端视图直接进行属性、方法的调用。SDK中最基础的ViewManager是HippyViewManager,封装了基本的方法,负责管理HippyView。用户自定的ViewManager必须继承自HippyViewManager

HippyMyViewManager.h

  1. @interface HippyMyViewManager:HippyViewManager
  2. @end

HippyMyViewManager.m

  1. @implementation HippyMyViewManager
  2. HIPPY_EXPORT_MODULE(MyView)
  3. HIPPY_EXPORT_VIEW_PROPERTY(backgroundColor, UIColor)
  4. HIPPY_REMAP_VIEW_PROPERTY(opacity, alpha, CGFloat)
  5. HIPPY_CUSTOM_VIEW_PROPERTY(overflow, CSSOverflow, HippyView)
  6. {
  7. if (json) {
  8. view.clipsToBounds = [HippyConvert CSSOverflow:json] != CSSOverflowVisible;
  9. } else {
  10. view.clipsToBounds = defaultView.clipsToBounds;
  11. }
  12. }
  13. - (HippyView *)view {
  14. return [HippyMyView new];
  15. }
  16. - (HippyShadowView *)shadowView {
  17. return [HippyShadowView new];
  18. }
  19. HIPPY_EXPORT_METHOD(focus:(nonnull NSNumber *)reactTag) {
  20. // do sth
  21. }
  22. HIPPY_EXPORT_METHOD(focus:(nonnull NSNumber *)reactTag callback:(HippyResponseSenderBlock)callback) {
  23. // do sth
  24. NSArray *result = xxx;
  25. callback(result);
  26. }
  27. HIPPY_EXPORT_METHOD(focus:(nonnull NSNumber *)reactTag resolver:(HIPPYPromiseResolveBlock)resolve rejecter:(HIPPYPromiseRejectBlock)reject) {
  28. // do sth
  29. if (success) {
  30. resolve(result);
  31. }
  32. else {
  33. reject(@"errorcode", @"errormessage", error);
  34. }
  35. }
  36. @end

类型导出

HIPPY_EXPORT_MODULE()将HippyMyViewManager类注册,前端在对MyView进行操作时会通过HippyMyViewManager进行实例对象指派。HIPPY_EXPORT_MODULE()中的参数可选。代表的是ViewManager对应的View名称。若用户不填写,则默认使用类名称。但是SDK中有个特殊处理逻辑,若参数中的字符串以’Manager’结尾,那SDK在根据字符串查找对应的View时会将删除结尾’Manager’后的字符串作为View名称

参数导出

HIPPY_EXPORT_VIEW_PROPERTY将终端View的参数和前端参数绑定。当前端设定参数值时,会自动调用setter方法设置到终端对应的参数。HIPPY_REMAP_VIEW_PROPERTY()负责将前端对应的参数名和终端对应的参数名对应起来。以上述代码为例,前端的opacity参数对应终端的alpha参数。此宏一共包含三个参数,第一个为前端参数名,第二个为对应的终端参数名称,第三个为参数类型。另外,此宏在设置终端参数时使用的是keyPath方法,即终端可以使用keyPath参数。HIPPY_CUSTOM_VIEW_PROPERTY()允许终端自行解析前端参数。SDK将前端传递过来的原始json类型数据传递给函数体(用户可以使用HippyConvert类中的方法解析对应的 数据),用户获取后自行解析。

这个方法带有两个隐藏参数-view, defaultView。view是指当前前端要求渲染的view。default指当前端渲染参数为nil时创建的一个临时view,使用其默认参数赋值。

方法导出

HIPPY_EXPORT_METHOD能够使前端随时调用终端对应的方法。前端通过三种模式调用,非别是callNative, callNativeWithCallbackId, callNativeWithPromise。终端对应三种方式时,函数体写法可以参照上面的示例。

  • callNative 此方法不需要终端返回任何值。

  • callNativeWithCallbackId 此方法需要终端在函数体中以单个block形式返回数据。block类型为HippyResponseSenderBlock,参数为一个NSArray变量。

  • callNativeWithPromise 此方法对应的前端使用的是promise写法,对应到终端需要业务根据自身情况返回resolve block或者reject block。其中resolve block数据类型为HippyPromiseResolveBlock, 参数为一个可以被json化的对象。如果参数为nil,则JS端会将其转化为undefined。reject block数据类型为HippyPromiseRejectBlock,参数包括一个错误码,错误信息,以及错误实例对象(NSError)。

一个ViewManager可以管理一种类型的多个实例,为了在ViewManager中区分当前操作的是哪个View,每一个导出方法对应的第一个参数都是View对应的tag值,用户可根据这个tag值找到对应操作的view。

由于导出方法并不会在主线程中调用,因此如果用户需要进行UI操作,则必须将其分配至主线程。推荐在导出方法中使用[self.bridge, uiManager addUIBlock:]方法。其中的block类型为HippyViewManagerUIBlock

typedef void (^HippyViewManagerUIBlock)(HippyUIManager *uiManager, NSDictionary<NSNumber *, UIView *> *viewRegistry)。第二个参数为字典,其中的key就是对应的view tag值,value就是对应的view。

创建shadowView和View

在OC层,HippyUIManager负责将JS层的解析结果,映射到OC层的视图层级,HippyShadowView它不是真正展现的视图,只是一个映射结果而已,每一个HippyShadowView对应一个真正的视图,但它已经完成了基本的布局。

HippyView会根据HippyShadowView的映射结果构建真正的View视图。因此对于大多数情况下的自定义view manager来说,直接创建一个HippyShadowView即可。

HippyUIManager将调用[HippyMyViewManager view]方法去创建一个真正的view,用户需要实现这个方法并返回自己所需要的HippyMyView。

到此,一个简单的HippyMyViewManager与HippyMyView创建完成。