安卓端

如何编写一个普通组件

下面以QRCTLinearGradient组件举例,来演示如何适配一个fabric组件

  1. QRCTLinearGradient的manager如下:

public class QRCTLinearGradientManager ... {
  @ReactProp(name="colors")
  public void setColors(QRCTLinearGradientView gradientView, ReadableArray colors) {
    ...
  }

  @ReactProp(name="locations")
  public void setLocations(QRCTLinearGradientView gradientView, ReadableArray locations) {
    ...
  }

  @ReactProp(name="startPoint")
  public void setStartPosition(QRCTLinearGradientView gradientView, ReadableMap startPos) {
    ...
  }

  @ReactProp(name="endPoint")
  public void setEndPosition(QRCTLinearGradientView gradientView, ReadableMap endPos) {
    ...
  }
}

可以看到,在以前的版本,js组件的属性和 java 之间,是靠 @ReactProp(name=PROP_END_POS)建立联系的,js代码修改一个prop,反映到native层就是调用相应的代码。

  1. 到了fabric,上层js需要与C++建立和上述类似的联系,这个就需要通过spec来实现。在js_specs目录下添加名为QRCTLinearGradientNativeComponent.js的文件, 开始编写 Spec 文件。

QRCTLinearGradientNativeComponent.js同样是一个js文件,因此我们先import相关包:
// @flow
import type {ViewProps} from 'react-native/Libraries/Components/View/ViewPropTypes';
import type {HostComponent} from 'react-native';
import { ViewStyle } from 'react-native';
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';


##### 添加NativeProps:

type NativeProps = $ReadOnly<{|
...ViewProps,
|}>;


##### 根据之前提到的@ReactProp,为NativeProps添加属性:

type NativeProps = $ReadOnly<{|
	...ViewProps,
	locations: $ReadOnlyArray<Float>,
	colors: $ReadOnlyArray<Float>,
	startPoint: CGPoint,
	endPoint: CGPoint,
|}>;
注意startPoint: CGPoint这句,方法setStartPosition接受的是一个ReadableMap,但是在定义的时候,必须在js里面定义一个类型:
type CGPoint = $ReadOnly<{|
    x: Float,
    y: Float
|}>;

这个可能是适配时比较麻烦的地方,从java的实现上,一个ReadableMap没有任何type field的具体信息,想要定义一个type,需要从文档和java的接口实现上找出有多少需要添加的field。

最后,声明这个fabric组件的名字:
export default (codegenNativeComponent<NativeProps>(
    'QRCTLinearGradient',
): HostComponent<NativeProps>);
整个spec代码如下:
// @flow
import type {ViewProps} from 'react-native/Libraries/Components/View/ViewPropTypes';
import type {HostComponent} from 'react-native';
import { ViewStyle } from 'react-native';
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
// import { number, string } from 'yargs';
type CGPoint = $ReadOnly<{|
    x: Float,
    y: Float
|}>;

type NativeProps = $ReadOnly<{|
...ViewProps,
colors: $ReadOnlyArray<Float>,
startPoint: CGPoint,
endPoint: CGPoint,
locations: $ReadOnlyArray<Float>
borderRadii: $ReadOnlyArray<Float>,
|}>;

export default (codegenNativeComponent<NativeProps>(
    'QRCTLinearGradient',
): HostComponent<NativeProps>);
  1. 写完后,编译工程,就会在$buildDir/generated/source目录生成相应的java和c++文件

  2. 下一步,建立C++和Java的联系,回到QRCTLinearGradientManager这个类,让原来的类继承接口QRCTLinearGradientManagerInterface.java(由codegen生成,见步骤 3):

public class QRCTLinearGradientManager ...
  implements QRCTLinearGradientManagerInterface<QRCTLinearGradientView> { ...

注意下,我们的QRNVideoManagerInterface是根据js spec生成的,因此里面的方法名字叫做setStartPoint,但是我们之前的代码叫做setStartPosition,因此会报错。需要把setStartPosition修改成setStartPoint

  1. 为添加delegate(QRCTLinearGradientManagerDelegate同样由sepc生成):

private final ViewManagerDelegate<QRCTLinearGradientView> mDelegate = new QRCTLinearGradientManagerDelegate<>(this);

  @Override
  protected ViewManagerDelegate<QRCTLinearGradientView> getDelegate() {
    return mDelegate;
  }
  1. 最后,在C++中组册这个组件。找到MainComponentsRegistry.cpp这个文件(由基础架构的基底工程生成,见步骤a),在

auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry();

这句下面添加一行:

providerRegistry->add(concreteComponentDescriptorProvider<QRCTLinearGradientComponentDescriptor>());

其中QRCTLinearGradientComponentDescriptor也是生成的代码,见步骤3中的ComponentDescriptors.h(不要忘了在代码中include这个头文件)。

  1. 正常情况下就结束了适配过程,可以编译运行查看效果了。

如何在组件上加入事件

RN组件的消息发送分为两部分:一部分为Native→js的消息发送,另一部分为js→Native消息发送,下面以框架的RNCViewPager(com.reactnativecommunity.viewpager.ReactViewPagerManager)组件为例,分别介绍如何实现两类的消息传输。

  1. Native→js的消息发送

需要在Native端注册消息,实现方法为getExportedCustomDirectEventTypeConstants,RNCViewPager注册了三个消息,分别为onPageScroll、onPageScrollStateChanged、onPageSelected,具体实现如下

@Override
//声名Native要发送哪些消息
public Map getExportedCustomDirectEventTypeConstants() {
  return MapBuilder.of(
     "topPageScroll", MapBuilder.of("registrationName", "onPageScroll"),
      "topPageScrollStateChanged", MapBuilder.of("registrationName", "onPageScrollStateChanged"),
      "topPageSelected", MapBuilder.of("registrationName", "onPageSelected"));
}

//注意 topXXX onXXX是成对出现的, Native→js发送消息

QEventDispatcher.dispatchEvent(ReactViewPager.this.reactContext, ReactViewPager.this.getId(), "topPageSelected", PageSelectedEvent.serializeEventData(position));

QEventDispatcher.dispatchEvent(ReactViewPager.this.reactContext, ReactViewPager.this.getId(), "topPageScrollStateChanged", PageSelectedEvent.serializeEventData(position));

QEventDispatcher.dispatchEvent(ReactViewPager.this.reactContext, ReactViewPager.this.getId(), "topPageSelected", PageSelectedEvent.serializeEventData(position));

有时候发送时间的代码使用的是EventDispatcher:

mEventDispatcher.dispatchEvent(new GFloatEvent(getId(), GFloatEvent.TOP_CLOSE, JsonUtils.toJsonString(option)));

js收到消息

<ViewPager style={styles.viewPager} initialPage={0} onPageSelected={(value)=>{
            console.log("onPageSelected values = "+ value)
        }}
        onPageScrollStateChanged={(value)=>{
            console.log("onPageScrollStateChanged values = "+ value)
        }} 
        onPageScroll={(value)=>{
            console.log("onPageScroll values = "+ value)
        }}
         
        >
            <View key="1" style={{borderRadius: 20, backgroundColor: '#64d6fe', justifyContent: 'center'}}>
                <Text style={{alignSelf: 'center'}}>First page</Text>
            </View>
            <View key="2" style={{borderRadius: 20, backgroundColor: '#ffb7d1', justifyContent: 'center'}}>
                <Text style={{alignSelf: 'center'}}>Second page</Text>
            </View>
        </ViewPager>
  1. js→Native的消息发送

js往Native发送的消息为 setPage、setPageWithoutAnimation,具体实现如下

Native实现,需要实现getCommandsMap和receiveCommand

@Override
//声明自己要收到哪些消息
public Map<String,Integer> getCommandsMap() {
  return MapBuilder.of(
      KEY_COMMAND_SET_PAGE,
      COMMAND_SET_PAGE,
      KEY_COMMAND_SET_PAGE_WITHOUT_ANIMATION,
      COMMAND_SET_PAGE_WITHOUT_ANIMATION);
}

@Override
//js消息到达后,如何处理这个消息
public void receiveCommand(@NonNull ReactViewPager viewPager, String commandId, @androidx.annotation.Nullable ReadableArray args) {
  Assertions.assertNotNull(viewPager);
  Assertions.assertNotNull(args);
  switch (commandId) {
    case KEY_COMMAND_SET_PAGE: {
      viewPager.setCurrentItemFromJs(args.getInt(0), true);
      return;
    }
    case KEY_COMMAND_SET_PAGE_WITHOUT_ANIMATION: {
      viewPager.setCurrentItemFromJs(args.getInt(0), false);
      return;
    }
    default:
      throw new IllegalArgumentException(String.format(
        "Unsupported command %d received by %s.",
        commandId,
        getClass().getSimpleName()));
  }
}

js暴露消息接口

创建消息命令文件:ViewPagerCommand.js:node_modules/@react-native-community/viewpager/js/ViewPagerCommand.js,里面声明了两个消息setPage、setPageWithoutAnimation

/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow strict-local
 */

// QRN ADD 整个文件是为了适配 Fabric
import codegenNativeCommands from 'react-native/Libraries/Utilities/codegenNativeCommands';

interface NativeCommands {
  +setPage: (
    viewRef: React.ElementRef<ComponentType>,
  ) => void;
  +setPageWithoutAnimation: (
     viewRef: React.ElementRef<ComponentType>,
   ) => void;
}

export default (codegenNativeCommands<NativeCommands>({
  supportedCommands: [
    'setPage', 'setPageWithoutAnimation'
  ],
}): NativeCommands);

js发送消息 viewpager.js:node_modules/@react-native-community/viewpager/js/ViewPager.js

import Commands from './ViewPagerCommand';

/**
     * A helper function to scroll to a specific page in the ViewPager.
     * The transition between pages will be animated.
     */
    // QRN ADD
    setPage = (selectedPage: number) => {
        this._setpage(selectedPage);
    };

    /**
     * A helper function to scroll to a specific page in the ViewPager.
     * The transition between pages will *not* be animated.
     */
    setPageWithoutAnimation = (selectedPage: number) => {
        this._setPageWithoutAnimation(selectedPage);
    };
    _setpage(selectedPage) {
        this._runCommand('setPage', [selectedPage]);
    }
    _setPageWithoutAnimation(selectedPage) {
        this._runCommand('setPageWithoutAnimation', [selectedPage]);
    }
    _runCommand(name, args) {
        Commands[name](this.refs[VIEW_PAGER_REF], this.getUIManagerCommand(name), args);
    }
  getUIManagerCommand(name) {
        return NativeModules.UIManager.RNCViewPager.Commands[name];
    }