下面以QRCTLinearGradient组件举例,来演示如何适配一个fabric组件
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层就是调用相应的代码。
// @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,
|}>;
type CGPoint = $ReadOnly<{|
x: Float,
y: Float
|}>;
这个可能是适配时比较麻烦的地方,从java的实现上,一个ReadableMap没有任何type field的具体信息,想要定义一个type,需要从文档和java的接口实现上找出有多少需要添加的field。
export default (codegenNativeComponent<NativeProps>(
'QRCTLinearGradient',
): HostComponent<NativeProps>);
// @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>);
public class QRCTLinearGradientManager ...
implements QRCTLinearGradientManagerInterface<QRCTLinearGradientView> { ...
注意下,我们的QRNVideoManagerInterface是根据js spec生成的,因此里面的方法名字叫做setStartPoint,但是我们之前的代码叫做setStartPosition,因此会报错。需要把setStartPosition修改成setStartPoint
private final ViewManagerDelegate<QRCTLinearGradientView> mDelegate = new QRCTLinearGradientManagerDelegate<>(this);
@Override
protected ViewManagerDelegate<QRCTLinearGradientView> getDelegate() {
return mDelegate;
}
auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry();
这句下面添加一行:
providerRegistry->add(concreteComponentDescriptorProvider<QRCTLinearGradientComponentDescriptor>());
其中QRCTLinearGradientComponentDescriptor也是生成的代码,见步骤3中的ComponentDescriptors.h(不要忘了在代码中include这个头文件)。
RN组件的消息发送分为两部分:一部分为Native→js的消息发送,另一部分为js→Native消息发送,下面以框架的RNCViewPager(com.reactnativecommunity.viewpager.ReactViewPagerManager)组件为例,分别介绍如何实现两类的消息传输。
需要在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>
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];
}