在使用RN进行跨平台开发的过程中,经常会设计到跨平台调用相关的内容,而在于RN进行交互的时候,最核心的就是RN提供的Component和Module。
其中,Component是专门将Native的UI暴露出来供JS调用的,而Native Module则是将Native的模块暴露出来供JS调用的,其用途不一样。在实战开发中,由于RN实现的成本比较大,或者没办法实现,而原生是非常容易实现的,这时候就想到了自定义组件。
Component
例如,下面是一个自定义的View原生代码:
public class MyCustomView extends View {
private Paint mPaint;
public MyCustomView(ReactContext context) {
super(context);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(0xffff0000);
}
public void setColor(int color){
mPaint.setColor(color);
invalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(300, 300);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
}
}
然后,创建一个ViewManager共RN调用,ViewManager需要继承自SimpleViewManager或者ViewGroupManager,如果RN有默认的属性,可以在原生端使用ReactProp注解来给原生设置属性值。
public class MyCustomViewManager extends SimpleViewManager<MyCustomView> {
protected static final String REACT_CLASS = "MyCustomView";
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected MyCustomView createViewInstance(ThemedReactContext reactContext) {
return new MyCustomView(reactContext);
}
@ReactProp(name = "color")
public void setColor(MyCustomView view, String color) {
view.setColor(Color.parseColor(color));
}
}
然后创建一个ReactPackage,并将我们自定义的ViewManager添加到createViewManagers中。
public class CustomReactPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new MyCustomViewManager()
);
}
}
然后将我们自定义的ReactPackage在Application中注册。
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new CustomReactPackage()
);
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, false);
}
}
为了方便其他RN开发人员调用,我们将原生组件封装成RN组件后再导出。
import React, {
Component,
PropTypes,
} from 'react';
import {
requireNativeComponent,
View,
UIManager,
} from 'react-native';
const ReactNative = require('ReactNative');
export default class MyCustomView extends Component{
constructor(props){
super(props)
}
render(){
return(
<RCTMyCustomView
{...this.props}
</RCTMyCustomView>);
}
}
MyCustomView.propTypes = {
color: PropTypes.string,
...View.propTypes,
};
var RCTMyCustomView = requireNativeComponent('MyCustomView', MyCustomView);
需要注意的是,如果报ReactNative找不到的错误,请修改ReactNative的导入方式为:
import ReactNative from 'react-native';
到此,我们就可以在需要使用的地方使用该组件了,注意给MyCustomView设置大小。
<MyCustomView
color='#00ff00'
style={{width:300, height:300}}
/>
这样就完成了JS调用原生组件的功能,但是在实战开发中,经常会设计的原生和js事件相关的传递,数据的传递可以参考我之前的文章:React Native原生模块向JS传递数据的几种方式
在与原生进行事件传递时,如果JS要给Native发送事件,Native需要借助getCommandsMap()和receiveCommand()来处理JS向Native发送事件逻辑。如果Native要给JS传递事件,可以使用getExportedCustomDirectEventTypeConstants()和addEventEmitters()。
private static final int CHANGE_COLOR = 1;
/**
* 可以接收的JS发过来的事件,返回来的数据是一组对应了方法名以及方法对应的一个ID(这个ID需要唯一区分)的Map。
*/
@Nullable
@Override
public Map<String, Integer> getCommandsMap() {
return MapBuilder.of("changeColor", CHANGE_COLOR);
}
/**
* 接收JS事件以后的处理。JS会通过一些发送发送相应的指令过来,Native会由receiveCommand来处理。事件过来时才会执行。
*/
@Override
public void receiveCommand(MyCustomView root, int commandId, @Nullable ReadableArray args) {
switch (commandId) {
case CHANGE_COLOR:
root.changeColor();
break;
}
}
/**
* 暴露了在JS中定义的方法,例如下面的"onChangeColor"是定义在JS中的方法。
*
* Returned map should be of the form:
* {
* "onTwirl": {
* "registrationName": "onTwirl"
* }
* }
*/
@Nullable
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return MapBuilder.<String, Object>builder()
.put("changeColor", MapBuilder.of("registrationName", "onChangeColor"))
.build();
}
/**
* 发射入口,相当于将Native的一些事件也注册给JS。
*/
@Override
protected void addEventEmitters(final ThemedReactContext reactContext, final MyCustomView view) {
super.addEventEmitters(reactContext, view);
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher()
.dispatchEvent(new ClickEvent(view.getId()));
}
});
}
在上面的代码中可以看到Native会接受一个1(CHANGE_COLOR)的指令以及会回调一个onChangeColor的方法到JS。RN端借助UIManager.dispatchViewManagerCommand即可将事件发送给原生端,如果发送给module则使用 NativeModules.UIManager.dispatchViewManagerCommand(),下面是完整代码:
const ReactNative = require('ReactNative');
const CUSTOM_VIEW = "custom_view";
export default class MyCustomView extends Component{
constructor(props){
super(props)
this._onChange = this._onChange.bind(this);
}
_changeColor() {
let self = this;
UIManager.dispatchViewManagerCommand(
ReactNative.findNodeHandle(self.refs[CUSTOM_VIEW]),
1,
null
);
}
_onChange() {
if (!this.props.handleClick) {
return;
}
this.props.handleClick();
}
render(){
return(
<RCTMyCustomView
ref={CUSTOM_VIEW}
{...this.props}
onChangeColor={() => this._onChange()}>
</RCTMyCustomView>);
}
}
MyCustomView.propTypes = {
handleClick: PropTypes.func,
color: PropTypes.string,
...View.propTypes,
};
var RCTMyCustomView = requireNativeComponent('MyCustomView', MyCustomView, {
nativeOnly: {onChangeColor: true}
});
注意,ReactNative的导入方式,随着版本的不一样,导入的方式也不一样。
注意上面用到了nativeOnly,有时候有一些特殊的属性,想从原生组件中导出,但是又不希望它们成为对应React封装组件的属性,这时候就可以使用nativeOnly。下面就可以愉快的使用了:
<MyCustomView
ref='view'
color='#00ff00'
handleSizeClick={() => this._handleSizeClick()}
handleClick={() => this._handleClick()}
style={{width:300, height:300}} />

NativeModule
NativeModule是用来定义Native模块供JS调用的。这样的场景会比较的多,比如Toast,在JS中没有Toast这类东西,但是Android/IOS中却很常见。例如,我们需要调用原生的Toast。
@ReactModule(name = "DemoToast")
public class DemoToastModule extends ReactContextBaseJavaModule {
private static final String DURATION_SHORT_KEY = "SHORT";
private static final String DURATION_LONG_KEY = "LONG";
public DemoToastModule(ReactApplicationContext reactContext){
super(reactContext);
}
@Override
public String getName() {
return "DemoToast";
}
@Nullable
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);
constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
return constants;
}
/**
* 通过Callback回调到JS
*/
@ReactMethod
public void show(String message, int duration, Callback callback) {
Toast.makeText(getReactApplicationContext(), message, duration).show();
callback.invoke("Egos");
}
}
其中,ReactMethod标示的show是给Js调用的,该方法会通过callback回调信息给Js。
然后,我们使用NativeModules将Native module转化成具体的JS组件,并导出以供第三方调用。
import { NativeModules } from 'react-native';
RCTDemoToast = NativeModules.DemoToast;
var DemoToast = {
SHORT: RCTDemoToast.SHORT,
LONG: RCTDemoToast.LONG,
show(message, duration){
RCTDemoToast.show(message, duration, (msg) => {
var str = msg;
});
}
};
module.exports = DemoToast;
然后在第三方使用即可,如果涉及到事件相关的内容。可以使用下面的方式。例如:
componentWillMount() {
DeviceEventEmitter.addListener('testMethod', (event) => {var s = event;} );
}
当Native代码执行sendEvent指令时,就会在RN中执行上面代码。
WritableMap params = Arguments.createMap();
params.putString("xzh","Egos");
sendEvent(getReactApplicationContext(), "testMethod", params);
/**
* 也可以直接发送事件给JS代码
*/
private void sendEvent(ReactContext reactContext,
String eventName, @Nullable WritableMap params) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params);
}
参数WritableMap可以实际需要添加。 |