ReactNative开发——封装原生UI组件
下文我们将制作一个可以用来显示图片的原生UI组件,这个UI组件可以随着手势放大缩小。(封装PhotoView)
PhotoView的开源地址:https://github.com/chrisbanes/PhotoView
一、引入开源库
可以PhotoView开源库作者的引用提示:
1、在 android项目根目录中的 build.gradle中加入:
allprojects {
...
maven { url "https://jitpack.io" }
}
2、在android Moudle中的build.gralde 中加入
dependencies {
....
compile 'com.github.chrisbanes:PhotoView:2.0.0'
compile 'com.github.bumptech.glide:glide:4.0.0-RC0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.0.0-RC0'
}
额,还要引用glide方便我们加载网络图片。
二、在Native端建立一个ViewManager。
1、我们可以直接创建一个类继承com.facebook.react.uimanager.SimpleViewManager
然后重新它的 getName
,createViewInstance
方法
public class PhotoViewManger extends SimpleViewManager<PhotoView> {
private static final String TAG = "PhotoViewManger";
private static final String REACT_CLASS = "PhotoView";
private ThemedReactContext aContext;
private PhotoView photoView;
@Override
public String getName() {
return REACT_CLASS;
}
@Override
protected PhotoView createViewInstance(ThemedReactContext reactContext) {
this.aContext = reactContext;
photoView = new PhotoView(reactContext);
photoView.setImageResource(R.mipmap.ic_launcher);
});
return photoView;
}
}
三、通过@ReactProp(或 @ReactPropGroup)注解来导出属性
例如:
@ReactProp(name = "imgSource")
public void setSource(PhotoView photoView, String source) {
Glide.with(aContext).load(source).into(photoView);
}
这里声明了一个属性,属性名为imgSource
。注意:这个方法必须是public
的,其中第一个时我们createViewInstance
返回的那个View,第二个参数使我们接受的参数类型,可以为boolean
int
float
double
String
Boolean
Integer
com.facebook.react.bridge.ReadableArray
com.facebook.react.bridge.ReadableMap
。
注解中也可以设置默认值defaultInt
defaultDouble
等等。
四、创建一个ReactPackage的实现类
创建一类继承自ReactPackage,并在createViewManagers返回List中加入我们定义的ViewManager。
public class PhotoViewPackage 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 PhotoViewManger());
}
}
五、在ReactNativeHost中加入我们定义的ReactPackage实现类
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 PhotoViewPackage()
);
}
};
六、在React Native端使用
import React from 'react';
import {requireNativeComponent, View} from 'react-native';
let iface = {
name: 'PhotoView',
propTypes: {
imgSource: React.PropTypes.string,
...View.propTypes //支持View组件的所有属性
}
}
var RCTPhotoView = requireNativeComponent('PhotoView', iface);
export default RCTPhotoView;
核心方法:requireNativeComponent('PhotoView', iface)
这个方法可以接收三个参数:
第一个参数:stirng 类型,对应我们Native中定义的那个ViewManager#getName的返回值。
第二个参数:{name?:string,displayName?:string,propTypes:Object}类型,或者ReactClass类型
第三个参数:{nativeOnly?:Object} 类型,用来解决 一些特殊的属性,想从原生组件中导出,但是又不希望它们成为对应React封装组件的属性
封装好之后,我们就可以想使用ReactNative组件那样使用了
class Main extends Component {
render() {
return (
<View style={{flex: 1}}>
<PhotoView
onChange={(obj) => {
Alert.alert('scale',obj.nativeEvent.msg);
}}
style={{flex: 1, width: '100%'}}
imgSource='https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1497959956050&di=d9ac82e7c9ba4fde2836510aeb2f2248&imgtype=0&src=http%3A%2F%2Fimg.tupianzj.com%2Fuploads%2Fallimg%2F160309%2F9-16030Z92137.jpg'
/>
</View>
);
}
}
AppRegistry.registerComponent('HybridUsage', () => Main);
七、事件
下面我们将演示怎么将Native UI 组件的事件导出。
我以导出PhotoView
的OnScaleChangeListener
为例
1、给native发送事件
photoView.setOnScaleChangeListener(new OnScaleChangedListener() {
@Override
public void onScaleChange(float scaleFactor, float focusX, float focusY) {
WritableMap map = Arguments.createMap();
map.putInt("target", photoView.getId());
map.putString("msg", "ScaleInfo: scaleFactor:" + scaleFactor
+ ",focusX" + focusX + ",focusY" + focusY);
/**
* {@link com.facebook.react.uimanager.UIManagerModuleConstants}
*/
aContext.getJSModule(RCTEventEmitter.class).receiveEvent(
photoView.getId(), "topChange", map
);
}
});
核心方法:
aContext.getJSModule(RCTEventEmitter.class).receiveEvent(
photoView.getId(), "topChange", map
);
这里第一个参数NavieUi组件的getId,第二个参数为事件的名称 我们传入”topChange”实际对应的的ReactNative端的”onChange”函数
我们可以查看com.facebook.react.uimanager.UIManagerModuleConstants
查看对应关系。
2、ReactNative端
我们给属性多设置一个onChange
方法,用来接受Native UI组件的回调
let iface = {
name: 'PhotoView',
propTypes: {
imgSource: React.PropTypes.string,
//回调
onChange: React.PropTypes.func,
...View.propTypes //支持View组件的所有属性
}
}
var RCTPhotoView = requireNativeComponent('PhotoView', iface);
export default RCTPhotoView;
3、使用
/**
* Created by blueberry on 6/20/2017.
*/
import React, {Component} from 'react';
import {AppRegistry, StyleSheet, View, Text, requireNativeComponent, TextInput, Button, Alert} from 'react-native';
import PhotoView from './PhotoView';
class Main extends Component {
render() {
return (
<View style={{flex: 1}}>
<PhotoView
onChange={(obj) => {
Alert.alert('scale',obj.nativeEvent.msg);
}}
style={{flex: 1, width: '100%'}}
imgSource='https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1497959956050&di=d9ac82e7c9ba4fde2836510aeb2f2248&imgtype=0&src=http%3A%2F%2Fimg.tupianzj.com%2Fuploads%2Fallimg%2F160309%2F9-16030Z92137.jpg'
/>
</View>
);
}
}
AppRegistry.registerComponent('HybridUsage', () => Main);
另外Native端还有一种方法,也可以导出事件
对应的java代码:
photoView.setOnScaleChangeListener(new OnScaleChangedListener() {
@Override
public void onScaleChange(float scaleFactor, float focusX, float focusY) {
// 这里又2中方案 : 1.
aContext.getNativeModule(UIManagerModule.class).getEventDispatcher()
.dispatchEvent(new ReactScaleChangeEvent(photoView.getId(),
"ScaleInfo: scaleFactor:" + scaleFactor
+ ",focusX" + focusX + ",focusY" + focusY));
// // 2.
// WritableMap map = Arguments.createMap();
// map.putInt("target", photoView.getId());
// map.putString("msg", "ScaleInfo: scaleFactor:" + scaleFactor
// + ",focusX" + focusX + ",focusY" + focusY);
//
// /**
// * {@link com.facebook.react.uimanager.UIManagerModuleConstants}
// */
// aContext.getJSModule(RCTEventEmitter.class).receiveEvent(
// photoView.getId(), "topChange", map
// );
}
});
/**
* {@link com.facebook.react.uimanager.UIManagerModuleConstants}
*/
class ReactScaleChangeEvent extends Event<ReactScaleChangeEvent> {
public static final String EVENT_NAME = "topChange"; //会被映射为onChange 具体映射关系参见 UIManagerModuleConstants.java
private String msg;
ReactScaleChangeEvent(int viewId, String msg) {
super(viewId);
this.msg = msg;
}
@Override
public String getEventName() {
return EVENT_NAME;
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
}
private WritableMap serializeEventData() {
WritableMap map = Arguments.createMap();
map.putInt("target", getViewTag());
map.putString("msg", this.msg);
return map;
}
}