最近在做项目时发现有多处扫码业务实现,虽然 RN 系统中已有 react-native camera 插件来支持此项功能,但是实际使用体验并不好,扫码灵敏度并不是很好,因此我们一直以来的方案都是直接底层创建activity 来实现基于zxing的底层扫码功能,但是最近多种业务场景使得页面布局有所变化,因此急需一个集成了扫码功能的扫码框的原生 ui 组件。
实现后的样子
官网其实有现成关于原生 ui 组件的实现方式附上链接,https://reactnative.cn/docs/0.59/native-components-android,下面开始我对扫码组件的实现
?xml version="1.0" encoding="UTF-8"?
java 端实现
新建VideoViewManager类,并继承SimpleViewManager,SimpleViewManager类需要传入一个泛型,该泛型继承Android的View,也就是说该泛型是要使用android 平台的哪个View就传入该View,比如,我要使用android的QRCodeView,这个泛型就传入QRCodeView。getName 返回组件名称(可以加前缀 RCT),createViewInstance 方法返回实例对象,可以在初始化对象时设置一些属性。。相关的代码如下:
public class VideoViewManager extends SimpleViewManager<QRCodeView>{
@Override
public String getName() {
return "RCTQRCodeView";
}
@Override
protected QRCodeView createViewInstance(ThemedReactContext reactContext) {
QRCodeView video = new QRCodeView(reactContext);
return video;
}
}
其中,可以通过 @ReactProp(或 @ReactPropGroup)注解来导出属性的设置方法。该方法有两个参数,第一个参数是泛型 View 的实例对象,第二个参数是要设置的属性值。方法的返回值类型必须为 void,而且访问控制必须被声明为 public。组件的每一个属性的设置都会调用 Java 层被对应 ReactProp 注解的方法,@ReactProp 注解必须包含一个字符串类型的参数 name。这个参数指定了对应属性在 JavaScript 端的名字。例
@ReactProp(name = "source")
public void setSource(RCTVideoView videoView,@Nullable String source){
if(source != null){
videoView.setVideoURI(Uri.parse(source));
videoView.start();
}
}
QRCodeViewManager 类的完整代码如下:
public class QRCodeViewManager extends SimpleViewManager<QRCodeView> {
private ThemedReactContext mContext;
private static final String GIFVIEW_MANAGER_NAME = "RCTQRCodeView";
@Override
public String getName() {
return GIFVIEW_MANAGER_NAME;
}
/**
* 此处创建View实例,并返回
*
* @param reactContext
* @return
*/
@Override
protected QRCodeView createViewInstance(ThemedReactContext reactContext) {
this.mContext = reactContext;
XmlPullParser parser = reactContext.getResources().getLayout(R.layout.activity_scan);
AttributeSet attributes = Xml.asAttributeSet(parser);
LogUtils.d("RCTQRCodeView parser", parser, reactContext.getCurrentActivity(), this);
int type;
try {
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
LogUtils.d("the xml file is error!\n");
}
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
RCTQRCodeView imageView = new RCTQRCodeView(reactContext, attributes );
imageView.startCamera();
imageView.startSpotAndShowRect();
return imageView;
}
@Override
public Map getExportedCustomDirectEventTypeConstants() {
return MapBuilder.of(
"onScanQRCodeSuccess", MapBuilder.of("registrationName", "onScanQRCodeSuccess"));
}
}
由于我们使用了 ZXing来进行扫码,所以先要对 zxingVIew进行继承并利用react提供的LifecycleEventListener来实现生命周期等功能
private static class RCTQRCodeView extends ZXingView implements LifecycleEventListener, QRCodeView.Delegate {
private ThemedReactContext themedReactContext;
private QRCodeView mQRCodeView = null;
public RCTQRCodeView(ThemedReactContext reactContext, AttributeSet attributeSet) {
super(reactContext, attributeSet);
reactContext.addLifecycleEventListener(this);
themedReactContext = reactContext;
mHandler = new Handler();
mQRCodeView = (ZXingView) findViewById(R.id.zxingscanview);
mQRCodeView.setDelegate(this);
}
@Override
public void onHostResume() {
LogUtils.d("RCTQRCodeView onHostResume");
}
@Override
public void onHostPause() {
LogUtils.d("RCTQRCodeView onHostPause");
}
/**
* 播放系统提示音
*/
private void hint() {
Uri uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
Ringtone rt = RingtoneManager.getRingtone(themedReactContext, uri);
rt.play();
}
private void vibrate() {
Vibrator vibrator = (Vibrator) themedReactContext.getSystemService(VIBRATOR_SERVICE);
vibrator.vibrate(200);
}
@Override
public void onHostDestroy() {
LogUtils.d("RCTQRCodeView onHostDestroy");
}
private void dispatchEvent(String eventName,WritableMap eventData){
ReactContext reactContext = (ReactContext) getContext();
reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(
getId(),
eventName,
eventData
);
}
@Override
public void onScanQRCodeSuccess(String result) {
LogUtils.d("RCTQRCodeView onScanQRCodeSuccess", result);
hint();
vibrate();
WritableMap event = Arguments.createMap();
event.putString("qrCode", result);
dispatchEvent("onScanQRCodeSuccess", event);
super.stopSpot();
}
@Override
public void onScanQRCodeOpenCameraError() {
}
}
接着我们需要将 UI 组件注册到系统中去。创建 VideoViewPackage,并注册到 ReactNativeHost。
class VideoViewPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(new QRCodeViewManager());
}
}
Js 端实现
先创建一个扫码组件
import React, {Component} from 'react';
import { View, requireNativeComponent } from 'react-native';
class QRCodeView extends Component {
_onScanQRCodeSuccess = (event) => {
if (!this.props.onScanQRCodeSuccess) {
return;
}
this.props.onScanQRCodeSuccess(event.nativeEvent.qrCode);
}
render() {
return <RCTQRCodeView {...this.props} onScanQRCodeSuccess={this._onScanQRCodeSuccess} />;
}
}
QRCodeView.name = "QRCodeView";
QRCodeView.propTypes = {
// onScanQRCodeSuccess: PropTypes.func,
// style: View.propTypes.style,
...View.propTypes,
};
let RCTQRCodeView = requireNativeComponent('RCTQRCodeView',QRCodeView,{
nativeOnly: {onChange: true}
});
module.exports = QRCodeView;
之后再页面中使用即可
<QRCodeView
style={{
position: 'absolute',
height: screenHeight,
width: screenWidth,
}}
onScanQRCodeSuccess={(e) => this.onScanQRCodeSuccess(e)}
/>