3.React Native在Android中自定义Component和Module
React Native最终展示的UI全是Native的UI,将Native的信息封装成React方便的调用。那么Native是如何封装成React调用的?Native和React是如何交互的? ViewManagerUI组件:将Native的UI暴露出来供JS调用。
下面Native的代码自定义了一个View并定义了一个变化的属性color。 public class MyCustomView extends View {
private Paint mPaint;
public MyCustomView(ReactContext context) {
super(context);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(0xffff0000);
}
// 这里相当于可以改变color属性
public void setColor(int color){
mPaint.setColor(color);
invalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
// 测试代码,onMeasure中设置的值通过getWidth()/getHeight()拿到的不一样,问题没找到
setMeasuredDimension(300,300);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(0,0,getWidth(),getHeight(),mPaint);
}
}
创建一个ViewManager。 public class MyCustomViewManager extends SimpleViewManager<MyCustomView> {
protected static final String REACT_CLASS = "MyCustomView";
@Override
public String getName() { // 返回了定义的View Module的名字
return REACT_CLASS;
}
@Override
protected MyCustomView createViewInstance(ThemedReactContext reactContext) {
return new MyCustomView(reactContext); // 创建一个View实例供JS使用。
}
// 设置属性,一定需要加这个注解,不然不认识
@ReactProp(name = "color")
public void setColor(MyCustomView view,String color) {
view.setColor(Color.parseColor(color));
}
}
创建一个ReactPackage,并在Application中使用。 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(); } // 自定义的ViewManager都可以加在这里。 @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Arrays.<ViewManager>asList( new MyCustomViewManager() ); } }
在Application中使用ReactPackage。 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() // 把自定义的ReactPackage加进来
);
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this,/* native exopackage */ false);
}
}
将Native组件封装成JS组件。 import React,{
Component,PropTypes,} from 'react';
import {
requireNativeComponent,View,UIManager,} from 'react-native';
const ReactNative = require('ReactNative'); // ReactNative通过import没用
export default class MyCustomView extends Component{
constructor(props){
super(props)
}
render(){
// {...this.props} 一定需要设置,不让你永远也看不到
return(
<RCTMyCustomView
{...this.props}
</RCTMyCustomView>);
}
}
MyCustomView.propTypes = {
color: PropTypes.string,// 设置color属性
...View.propTypes,// 这里一定需要设置,不然会报错。has no propType for native prop。这个被坑了
};
var RCTMyCustomView = requireNativeComponent('MyCustomView',MyCustomView); // 拿到Native组件
然后就可以愉快的使用了。(最开始没有设置大小,只是在Native的onMeasure中设置了大小,一直没有View出来,被坑了) // 一定需要设置大小,不然永远看不到。 <MyCustomView color='#00ff00' style={{width:300,height:300}} />
如果是第一次使用封装UI Component的话,自己一定需要完整的尝试一遍。
在上面的MyCustomViewManager中实现一些方法就可以了。getCommandsMap()和receiveCommand()用来处理JS向Native发送事件逻辑。getExportedCustomDirectEventTypeConstants()和addEventEmitters()对应了Native向JS发送事件逻辑。 private static final int CHANGE_COLOR = 1;
/** * 可以接收的JS发过来的事件,返回来的数据是一组对应了方法名以及方法对应的一个ID(这个ID需要唯一区分)的Map。 * 这个在进入App的时候就会运行,得到相应的一组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中的方法。 * 这个在进入App的时候就会运行 * * 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。 * * 这个在进入App的时候就会运行。 */
@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) {
// 调用了JS相应的方法。
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher()
.dispatchEvent(new ClickEvent(view.getId()));
}
});
}
在上面的代码中可以看到Native会接受一个1(CHANGE_COLOR)的指令以及会回调一个onChangeColor的方法到JS。那么现在就在JS中实现,把完整的JS代码贴了一遍,注释也写在了里面。 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); // 一定需要这样调用才会把属性绑定过来
}
// 把事件给Native
_changeColor() { // is not a function?没有设置this._onChange = this._onChange.bind(this);的时候
let self = this;
UIManager.dispatchViewManagerCommand(
ReactNative.findNodeHandle(self.refs[CUSTOM_VIEW]),1,// 发送的commandId为1
null
);
}
_onChange() {
if (!this.props.handleClick) {
return;
}
this.props.handleClick();
}
render(){
// 设置ref,没弄明白为什么一定需要设置ref,大概是_changeColor中的findNodeHandle需要
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}
});
注意上面用到了 现在就可以愉快的调用了。 <MyCustomView
ref='view'
color='#00ff00'
handleSizeClick={() => this._handleSizeClick()}
handleClick={() => this._handleClick()}
style={{width:300,height:300}} />
建议初学者好好的实践一遍。 最后的结果 NativeModuleNative模块:定义Native的模块供JS调用。这样的场景会比较的多,比如Toast,在JS中没有Toast这类东西,但是Android/IOS中却很常见。
封装一个moudle供JS调用。注意里面注释。 @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);
}
// Module的名称
@Override
public String getName() {
return "DemoToast";
}
/** * 这里定义的值可以被JS中引用,JS引用的时候.SHORT就会对应到相应的Toast.LENGTH_SHORT */
@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");
}
}
JS将Native module转化成JS组件。 import { NativeModules } from 'react-native';
RCTDemoToast = NativeModules.DemoToast; // 获取到Native Module
var DemoToast = {
/** * 觉得这里不是很好理解,但是这里对应的那个值(SHORT或者LONG)确实 * 是对应了上面Java代码中的getConstants对应的信息。 */
SHORT: RCTDemoToast.SHORT,LONG: RCTDemoToast.LONG,show(message,duration){
RCTDemoToast.show(message,duration,(msg) => {
var str = msg;
});
}
};
module.exports = DemoToast;
@ReactMethod
public void show(String message,duration).show();
callback.invoke("Egos"); // callback回调信息。注意上面的RCTDemoToast.show方法第三个参数。
}
在JS中注册testMethod。Native直接调用JS。 componentWillMount() {
DeviceEventEmitter.addListener('testMethod',(event) => {var s = event;} ); }
下面Native代码发送指令就会执行上面代码。 WritableMap params = Arguments.createMap();
params.putString("xixi","Egos");
sendEvent(getReactApplicationContext(),"testMethod",params);
/** * 也可以直接发送事件给JS代码 */
private void sendEvent(ReactContext reactContext,String eventName,@Nullable WritableMap params) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName,params); // 会回调到上面注册的testMethod。
}
JavaScriptModuleJS模块:com.facebook.react.CoreModulesPackage中有展示出来一些信息,AppRegistry、RCTEventEmitter(对应了RCTNativeAppEventEmitter)等等。 源码中定义的JS Module 相当于执行JS代码的时候会对应去执行Native相应的代码,这部分不是View,不是Native Module。 这部分内容还不是很理解,没有找到合适的例子,后续有问题补充。 思考
public interface ReactPackage {
/** * @return list of native modules to register with the newly created catalyst instance */
List<NativeModule> createNativeModules(ReactApplicationContext reactContext);
/** * @return list of JS modules to register with the newly created catalyst instance. * * IMPORTANT: Note that only modules that needs to be accessible from the native code should be * listed here. Also listing a native module here doesn't imply that the JS implementation of it * will be automatically included in the JS bundle. */
List<Class<? extends JavaScriptModule>> createJSModules();
/** * @return a list of view managers that should be registered with {@link UIManagerModule} */
List<ViewManager> createViewManagers(ReactApplicationContext reactContext);
}
参考Native Modules Native UI Components React Native中文网 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |