第一步:
我们首先要创建一个RN项目:
react-native init 你的项目名
第二步:
是用Android studio打开RN项目中的Android项目。
在新建一个文件夹用于存放我们封装的控件,结构如下:
第三步:
创建ViewManager,比如TextViewManager,代码如下:
import android.graphics.Color;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.events.RCTEventEmitter;
/**
* Created by sujialong on 2017/9/1.
*/
public class TextViewManager extends SimpleViewManager<TextView> {
@Override
public String getName() {
return "CustomTextView";
}
@Override
protected TextView createViewInstance(ThemedReactContext reactContext) {
final TextView textView = new TextView(reactContext);
// final ThemedReactContext myContext = reactContext;
//注册点击事件
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
WritableMap event = Arguments.createMap();
event.putString("message", "MyMessage哈哈哈--自定义");
ReactContext reactContext = (ReactContext)textView.getContext();
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
textView.getId(),
"topChange",
event);
}
});
return textView;
}
@ReactProp(name="text")
public void setText(TextView textView,String text){
textView.setText(text);
}
@ReactProp(name="textSize")
public void setTextSize(TextView view,float fontSize){
view.setTextSize(fontSize);
}
@ReactProp(name = "textColor",defaultInt = Color.BLACK)
public void setTextColor(TextView view,int textColor){
view.setTextColor(textColor);
}
}
1.getName方法用于js端导出时,使用的控件名称。
2.createViewInstance方法用于创建控件与初始化状态。
3.使用@ReactProp注解的方法,是将js端传入的属性值导出,并且给控件设置该属性值。
@ReactProp
该注解可以传入两个参数:
1. name:js端使用时传入的参数名,必传。
2. defaultBoolean:设置默认值,可选,其他选项:defaultInt,defaultFloat。这些参数必须是对应的基础类型的值(也就是boolean,int, float),这些值会被传递给setter方法,以免JavaScript端某些情况下在组件中移除了对应的属性。
第四步:
注册ViewManager,新建一个class,名为ReactViewPackage,在这个class里面加入以下代码:
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Created by sujialong on 2017/9/1.
*/
public class ReactViewPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Arrays.<NativeModule>asList(
new ToastViewManager(reactContext)
);
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new TextViewManager()
);
}
}
这里面需要我们实现ReactPackage的两个方法:createViewManagers,createNativeModules。以前还有一个叫做createJSModules的方法,现在被移除掉了。其中,createNativeModules是用来添加原生模块的,比如:Toast等。createViewManagers是用来添加原生的UI组件的。
我们第一个创建的TextView是UI组件,所以我们将TextViewManager添加到createViewManagers中,如果没有引入原生模块,可以将createNativeModules方返回空数组:
@Override
public List<NativeModule> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
第五步:
在项目的MainApplication.java文件的getPackages方法中添加,我们封装的原生模块:
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new ReactViewPackage()
);
}
ok,到这一步我们基本的封装已经完毕。
第六步:
需要我们在js端导出所封装的原生组件,如下:
import React, {PureComponent,PropTypes} from 'react';
import {requireNativeComponent,View} from 'react-native';
const CustomTextView = {
name:"CustomTextView",
propTypes:{
"text":PropTypes.string,
"textSize":PropTypes.number,
"textColor":PropTypes.number,
...View.propTypes
}
}
const RCTCustomTextView = requireNativeComponent('CustomTextView',CustomTextView,{
nativeOnly: {onChange: true}
});
export default class MyView extends PureComponent {
_onChange = (event: Event) => {
const onChangeMessage = this.props.onChangeMessage;
onChangeMessage && onChangeMessage(event.nativeEvent);
}
render() {
return (
<RCTCustomTextView {...this.props} onChange={this._onChange}/>
);
}
}
MyView.propTypes = {
onChangeMessage:PropTypes.func,
};
这里,由于我们是封装的TextView,需要使用requireNativeComponent在原生代码中引用。requireNativeComponent通常接受两个参数,第一个参数是原生视图的名字,也就是我们在ViewManager中使用getName方法定义的名字,而第二个参数是一个描述组件接口的对象。组件接口应当声明一个友好的name,用来在调试信息中显示;组件接口还必须声明propTypes字段,用来对应到原生视图上。这个propTypes还可以用来检查用户使用View的方式是否正确。
第六步:
使用所封装好的组件:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
DeviceEventEmitter
} from 'react-native';
import CustomTextView from './src/CustomTextView';
export default class CustomRnView extends Component {
render() {
return (
<View style={styles.container}>
<CustomTextView
style={styles.myTextView}
text="我是封装的原生组件"
textSize={15}
onChangeMessage={(msg)=>{
CustomToastView.show("点到我了----",CustomToastView.SHORT);
CustomToastView.getNativeClass(this._getNativeClass);
this._getNativePromise();
}}/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
myTextView:{
width:300,
height:100,
},
});
第七步:
在项目的根目录下,运行:
react-native run-android
成功啦!!!
第八步:为原生模块添加方法
要给js端调用的方法,需要使用@ReactMethod注解:
@ReactMethod
public void show(String message, int duration){
Toast.makeText(getReactApplicationContext(), message, duration).show();
}
在js端如下调用:
import CustomToastView from './src/CustomToastView';
CustomToastView.show("message",CustomToastView.SHORT);
第九步:为原生代码添加回调函数
1.使用Callback:
import com.facebook.react.bridge.Callback;
//使用回调函数
@ReactMethod
public void getNativeClass(Callback callback){
callback.invoke("使用回调函数");
}
在js端如下调用:
import CustomToastView from './src/CustomToastView';
CustomToastView.getNativeClass((res) => {
alert(res);
});
2.使用Promise:
//使用promise回调
@ReactMethod
public void getArguments(Boolean isResolve,Promise promise){
WritableMap map = Arguments.createMap();
map.putString("name", "Arno");
map.putString("age", "25");
if(isResolve){
promise.resolve(map);
}else{
promise.reject(map.toString());
}
}
在js端使用:
import CustomToastView from './src/CustomToastView';
CustomToastView.getArguments(true)
.then((res) => {
console.log("getArguments---success");
console.log(res);
},(error)=>{
console.log("getArguments---error");
console.log(res);
});
3.给JS发送事件
private static final String TestEventName = "TestEventName";
//发送事件,js端使用事件监听接收
public void setEvent(){
WritableMap params = Arguments.createMap();
params.putString("name", "Jack");
reactContext_
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(TestEventName, params);
}
js端使用DeviceEventEmitter设置监听接收:
import {
DeviceEventEmitter
} from 'react-native';
DeviceEventEmitter.addListener(CustomToastView.TestEventName,(res)=>{
console.log("我是事件监听");
console.log(res);
});
大家也可以先看一下官方文档:http://reactnative.cn/docs/0.47/native-modules-android.html#content
遇到的问题:
1.在js端导出组件后使用的时候,报错:
解决方案:
在requireNativeComponent的第二个参数内,也就是描述组件属性的时候,在propTypes内,加入…View.propTypes:
因为,在这里我只描述了我规定的属性字段,RN其实自己会添加很多默认的属性,使用扩展符合并就可以了。