前些日子写了一个flutter_barcode_sdk,配合已经存在的Flutter camera插件可以实现摄像头扫码 - 从Java层获取摄像头数据,通过Dart传递,再到Java去做解码。这个过程可以优化。这篇文章的目的是把摄像头控制和二维码扫描合并到Android代码里,然后封装出一整个Flutter插件。
基于Android TextView的Flutter自定义Widget根据官方文档,我们先做一个最简单的自定义widget。
-
创建插件工程:
flutter create --org com.dynamsoft --template=plugin --platforms=android -a java flutter_qrcode_scanner
-
打开lib/flutter_qrcode_scanner.dart文件,把自动生成的代码替换成:
class ScannerView extends StatefulWidget { const ScannerView({Key? key}) : super(key: key); @override StatecreateState() => _ScannerViewState(); } class _ScannerViewState extends State { @override void initState() { super.initState(); } @override Widget build(BuildContext context) { const String viewType = 'com.dynamsoft.flutter_qrcode_scanner/nativeview'; final Map creationParams = {}; return AndroidView( viewType: viewType, creationParams: creationParams, creationParamsCodec: const StandardMessageCodec(), ); } } -
在Android目录下创建一个NativeView.java文件。创建一个TextView,并在getView()中返回:
import android.content.Context; import android.graphics.Color; import android.view.View; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.plugin.platform.PlatformView; import java.util.Map; class NativeView implements PlatformView { @NonNull private final TextView textView; NativeView(@NonNull Context context, int id, @Nullable MapcreationParams) { textView = new TextView(context); textView.setTextSize(72); textView.setBackgroundColor(Color.rgb(255, 255, 255)); textView.setText("Rendered on a native Android view (id: " + id + ")"); } @NonNull @Override public View getView() { return textView; } @Override public void dispose() {} } 接下来创建NativeViewFactory.java,用于初始化NativeView:
import android.content.Context; import android.view.View; import androidx.annotation.Nullable; import androidx.annotation.NonNull; import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.StandardMessageCodec; import io.flutter.plugin.platform.PlatformView; import io.flutter.plugin.platform.PlatformViewFactory; import java.util.Map; class NativeViewFactory extends PlatformViewFactory { @NonNull private final BinaryMessenger messenger; NativeViewFactory(@NonNull BinaryMessenger messenger) { super(StandardMessageCodec.INSTANCE); this.messenger = messenger; } @NonNull @Override public PlatformView create(@NonNull Context context, int id, @Nullable Object args) { final MapcreationParams = (Map ) args; return new NativeView(context, id, creationParams); } } 在自动生成的FlutterQrcodeScannerPlugin.java中,注册NativeViewFactory:
@Override public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) { channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "flutter_qrcode_scanner"); channel.setMethodCallHandler(this); flutterPluginBinding.getPlatformViewRegistry().registerViewFactory( "com.dynamsoft.flutter_qrcode_scanner/nativeview", new NativeViewFactory(flutterPluginBinding.getBinaryMessenger())); }
这样一个最简单的自定义Flutter Text Widget就完成了。我们可以修改example/lib/main.dart,然后运行看下效果:
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_qrcode_scanner/flutter_qrcode_scanner.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State createState() => _MyAppState();
}
class _MyAppState extends State {
String _platformVersion = 'Unknown';
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: Center(
child: ScannerView(),
),
),
);
}
}
flutter run把Flutter Text Widget改成Flutter Camera Widget
如果以上Widget没有问题,我们开始改造。
首先在android/build.gradle中添加:
rootProject.allprojects {
repositories {
google()
mavenCentral()
maven {
url "https://download.dynamsoft.com/maven/dce/aar"
}
}
}
android {
compileSdkVersion 30
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
minSdkVersion 21
}
}
dependencies {
implementation 'com.dynamsoft:dynamsoftcameraenhancer:2.0.0@aar'
}
这里用到现成封装好的Camera SDK,你也可以用别的,比如Google官方的CameraX。
在NativeView.java中导入:
import com.dynamsoft.dce.*;
用DCECameraView替换TextView。同时,使用CameraEnhancer来加载DCECameraView:
private final DCECameraView cameraView;
private CameraEnhancer cameraEnhancer;
NativeView(BinaryMessenger messenger, @NonNull Activity context, int id,
@Nullable Map creationParams) {
this.context = context;
cameraEnhancer = new CameraEnhancer(context);
cameraView = new DCECameraView(context);
cameraEnhancer.setCameraView(cameraView);
try {
cameraEnhancer.open();
} catch (Exception e) {
e.printStackTrace();
}
}
为了切换摄像头的状态,我们需要监控应用的生命周期:
class NativeView implements PlatformView, Application.ActivityLifecycleCallbacks {
@Override
public void onActivityResumed(Activity activity) {
try {
cameraEnhancer.open();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onActivityPaused(Activity activity) {
try {
cameraEnhancer.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
在NativeViewFactory.java中,我们把传入的Context换成Activity:
class NativeViewFactory extends PlatformViewFactory {
@NonNull private final BinaryMessenger messenger;
@NonNull private Activity activity;
NativeViewFactory(@NonNull BinaryMessenger messenger, Activity activity) {
super(StandardMessageCodec.INSTANCE);
this.messenger = messenger;
this.activity = activity;
}
@NonNull
@Override
public PlatformView create(@NonNull Context context, int id, @Nullable Object args) {
final Map creationParams = (Map) args;
return new NativeView(messenger, activity, id, creationParams);
}
最后修改FlutterQrcodeScannerPlugin:
public class FlutterQrcodeScannerPlugin implements FlutterPlugin, ActivityAware {
private Activity activity;
private FlutterPluginBinding flutterPluginBinding;
@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
this.flutterPluginBinding = flutterPluginBinding;
}
@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
this.flutterPluginBinding = null;
}
@Override
public void onAttachedToActivity(ActivityPluginBinding activityPluginBinding) {
bind(activityPluginBinding);
}
@Override
public void onReattachedToActivityForConfigChanges(ActivityPluginBinding activityPluginBinding) {
bind(activityPluginBinding);
}
@Override
public void onDetachedFromActivityForConfigChanges() {
activity = null;
}
@Override
public void onDetachedFromActivity() {
activity = null;
}
private void bind(ActivityPluginBinding activityPluginBinding) {
activity = activityPluginBinding.getActivity();
flutterPluginBinding.getPlatformViewRegistry().registerViewFactory(
"com.dynamsoft.flutter_qrcode_scanner/nativeview",
new NativeViewFactory(flutterPluginBinding.getBinaryMessenger(), activity));
}
}
现在重新运行示例代码,之前的Text View应该变成了Camera View。
集成barcode SDK,实现支持摄像头二维码扫描的Flutter插件摄像头Widget做好了,接下来就是增加二维码识别功能。这里要进一步对Java和Dart的代码进行改造。
第一步还是修改build.gradle文件,添加barcode SDK:
rootProject.allprojects {
repositories {
google()
mavenCentral()
maven {
url "https://download.dynamsoft.com/maven/dce/aar"
}
maven {
url "https://download.dynamsoft.com/maven/dbr/aar"
}
}
}
android {
compileSdkVersion 30
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
minSdkVersion 21
}
}
创建QRCideScanner.java文件来实现摄像头和二维码识别的逻辑:
public class QRCodeScanner {
private CameraEnhancer mCameraEnhancer;
private BarcodeReader reader;
private Activity context;
private DCECameraView cameraView;
private DetectionHandler handler;
public interface DetectionHandler {
public void onDetected(List
在NativeView中,我们增加MethodChannel来触发接口调用:
class NativeView implements PlatformView, MethodCallHandler, Application.ActivityLifecycleCallbacks, QRCodeScanner.DetectionHandler {
@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
switch (call.method) {
case "startScanning":
qrCodeScanner.startScan();
result.success(null);
break;
case "stopScanning":
qrCodeScanner.stopScan();
result.success(null);
break;
case "setLicense":
final String license = call.argument("license");
qrCodeScanner.setLicense(license);
result.success(null);
break;
case "setBarcodeFormats":
final int formats = call.argument("formats");
qrCodeScanner.setBarcodeFormats(formats);
result.success(null);
break;
default:
result.notImplemented();
}
}
}
二维码识别回调函数是在工作线程中执行的,而结果回传需要在主线程中做,所以使用runOnUiThread:
@Override public void onDetected(List> data) { context.runOnUiThread(new Runnable() { @Override public void run() { methodChannel.invokeMethod("onDetected", data); } }); }
Dart这边增加一个ScannerViewController来接收返回结果:
class _ScannerViewState extends State{ ScannerViewController? _controller; @override void initState() { super.initState(); } @override Widget build(BuildContext context) { const String viewType = 'com.dynamsoft.flutter_qrcode_scanner/nativeview'; final Map creationParams = {}; return AndroidView( viewType: viewType, onPlatformViewCreated: _onPlatformViewCreated, creationParams: creationParams, creationParamsCodec: const StandardMessageCodec(), ); } void _onPlatformViewCreated(int id) { _controller = ScannerViewController(id); widget.onScannerViewCreated(_controller!); } } class ScannerViewController { late MethodChannel _channel; final StreamController > _streamController = StreamController
>(); Stream
> get scannedDataStream => _streamController.stream; ScannerViewController(int id) { _channel = MethodChannel('com.dynamsoft.flutter_qrcode_scanner/nativeview_$id'); _channel.setMethodCallHandler((call) async { switch (call.method) { case 'onDetected': if (call.arguments != null) { List
data = fromPlatformData(call.arguments as List); _streamController.sink.add(data); } break; } }); } }
此外,增加Java层的调用函数:
FuturestartScanning() async { await _channel.invokeMethod('startScanning'); } Future stopScanning() async { await _channel.invokeMethod('stopScanning'); } /// Apply for a 30-day FREE trial license: https://www.dynamsoft.com/customer/license/trialLicense Future setLicense(String license) async { await _channel.invokeMethod('setLicense', {'license': license}); }
到此,整个插件完成了。我们修改示例代码,并在build.gradle中把最小Android版本设置成21:
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_camera_qrcode_scanner/flutter_qrcode_scanner.dart';
import 'package:flutter_camera_qrcode_scanner/dynamsoft_barcode.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
State createState() => _MyAppState();
}
class _MyAppState extends State {
ScannerViewController? controller;
String _barcodeResults = '';
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('QR Code Scanner'),
),
body: Stack(children: [
ScannerView(onScannerViewCreated: onScannerViewCreated),
Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
height: 100,
child: SingleChildScrollView(
child: Text(
_barcodeResults,
style: TextStyle(fontSize: 14, color: Colors.white),
),
),
),
Container(
height: 100,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
MaterialButton(
child: Text('Start Scan'),
textColor: Colors.white,
color: Colors.blue,
onPressed: () async {
controller!.startScanning();
}),
MaterialButton(
child: Text("Stop Scan"),
textColor: Colors.white,
color: Colors.blue,
onPressed: () async {
controller!.stopScanning();
})
]),
),
],
)
]),
),
);
}
void onScannerViewCreated(ScannerViewController controller) {
setState(() {
this.controller = controller;
});
controller.setLicense('LICENSE-KEY');
controller.startScanning(); // auto start scanning
controller.scannedDataStream.listen((results) {
setState(() {
_barcodeResults = getBarcodeResults(results);
});
});
}
String getBarcodeResults(List results) {
StringBuffer sb = new StringBuffer();
for (BarcodeResult result in results) {
sb.write(result.format);
sb.write("n");
sb.write(result.text);
sb.write("nn");
}
if (results.isEmpty) sb.write("No QR Code Detected");
return sb.toString();
}
@override
void dispose() {
controller?.dispose();
super.dispose();
}
}
在使用这个插件的时候,Dart层要做的事情就是接收识别结果,并通过UI绘制出来。
flutter_camera_qrcode_scanner
源码https://github.com/yushulx/flutter_qrcode_scanner



