前些日子写了一个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 State
createState() => _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 Map
creationParams) { 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 Map
creationParams = (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 StatecreateState() => _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 MapcreationParams) { 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 MapcreationParams = (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 StatecreateState() => _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
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)