由于最近在做一个Flutter项目,需要使用到车牌识别的功能,并且要求识别功能使用本地的识别SDK。于是在网上找到一个原生的车牌识别的库。
原文地址:
https://www.jianshu.com/p/94784c3bf2c1
原项目Demo地址:
https://github.com/AleynP/LPR
感谢LPR作者提供SDK及Demo支持!
按照以上内容部署好原生项目代码,然后下面我们来提供控件的移植。主要分为以下几个步骤:
①.包装ScannerView;
②.编写ScannerView的FlutterPlugin(kotlin实现);
③.编写ScannerView的Dart部分实现;
④.测试用例-Demo.
- 移植第一步,使用xml包装ScannerView,如果不这样包装,可能会在Flutter中使用时报错“layout_height”相关的问题。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent">
<com.boyou.materialmanager.core.widget.scanner.ScannerView
android:id="@+id/scanner_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
对xml进行view的kotlin实现:
class PlateRecognitionView : ConstraintLayout {
private var scannerView: ScannerView? = null
constructor(context: Context) : super(context){
init(context, null)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs){
init(context, attrs)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr){
init(context, attrs)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes){
init(context, attrs)
}
private fun init(ctx: Context, attrs: AttributeSet?){
View.inflate(ctx, R.layout.view_for_plate_recognition, this)
scannerView = findViewById(R.id.scanner_view)
}
// 设置控制相机的生命周期的LifecycleOwner
fun setLifeRecycle(lifecycleOwner: LifecycleOwner) = scannerView?.setLifeRecycle(lifecycleOwner)
fun setScannerOptions(options: ScannerOptions, flashMode: Int) = scannerView?.setScannerOptions(options, flashMode)
// 设置闪光灯效果
fun setFlashMode(flashMode: Int) = scannerView?.setFlashMode(flashMode)
// 设置识别结构的监听事件
fun setOnScannerOCRListener(onResult: (String)->Unit) = scannerView?.setOnScannerOCRListener { onResult(it) }
fun start() = scannerView?.start()
fun release() = scannerView?.release()
}
- 创建Kotlin <---> Flutter 双向通讯的组件
2.1. 实现PlatformView接口,编写相关的通讯方法等逻辑,如下:
class PlateRecognitionPlatformViewFactory(private val binaryMessenger: BinaryMessenger)
: PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context?, viewId: Int, args: Any?): PlatformView =
PlateRecognitionPlatformView(context!!, viewId, binaryMessenger)
}
class PlateRecognitionPlatformView(private val ctx: Context, viewId: Int, binaryMessenger: BinaryMessenger)
: PlatformView, MethodChannel.MethodCallHandler, EventChannel.StreamHandler, LifecycleOwner {
private var lifecycle: LifecycleRegistry
private var scannerView: PlateRecognitionView? = null
private var methodChannel: MethodChannel? = null
private var eventChannel: EventChannel? = null
private var eventSink: EventChannel.EventSink? = null
init {
methodChannel = MethodChannel(binaryMessenger, "PlateRecognitionView$viewId-CN").apply {
setMethodCallHandler(this@PlateRecognitionPlatformView)
}
eventChannel = EventChannel(binaryMessenger, "PlateRecognitionView$viewId-ET").apply {
setStreamHandler(this@PlateRecognitionPlatformView)
}
lifecycle = LifecycleRegistry(this)
}
override fun getView(): View {
if (scannerView == null)
scannerView = PlateRecognitionView(ctx).apply {
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT)
setLifeRecycle(this@PlateRecognitionPlatformView)
}
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START)
return scannerView!!
}
override fun onFlutterViewAttached(flutterView: View) {
super.onFlutterViewAttached(flutterView)
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
}
override fun onFlutterViewDetached() {
super.onFlutterViewDetached()
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
}
override fun dispose() {
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
if (eventChannel != null) {
eventChannel?.setStreamHandler(null)
eventChannel = null
}
if (methodChannel != null) {
methodChannel?.setMethodCallHandler(null)
methodChannel = null
}
if (scannerView != null)
scannerView?.release()
scannerView = null
}
override fun getLifecycle(): Lifecycle = lifecycle
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
this.eventSink = events
}
override fun onCancel(arguments: Any?) {
this.eventSink = null
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"initSpotCamera" -> {
initSpotCamera(call.argument<Int>("flashMode") ?: ImageCapture.FLASH_MODE_AUTO)
result.success(null)
}
"setFlashMode" -> {
setFlashMode(call.argument<Int>("flashMode") ?: ImageCapture.FLASH_MODE_OFF)
result.success(null)
}
"restart" -> {
scannerView?.start()
result.success(null)
}
"resume" -> {
resume()
result.success(null)
}
"pause" -> {
pause()
result.success(null)
}
else -> result.notImplemented()
}
}
/** 初始化识别相机 **/
private fun initSpotCamera(flashMode: Int) {
scannerView?.apply {
setScannerOptions(ScannerOptions.Builder()
.setTipText("请将识别车牌放入框内")
.setFrameCornerColor(-0xd93101)
.setLaserLineColor(-0xd93101)
.build(), flashMode)
setOnScannerOCRListener { cardNum -> eventSink?.success(cardNum) }
}
}
private fun resume(){
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
}
private fun pause() {
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
}
private fun setFlashMode(flashMode: Int) = scannerView?.setFlashMode(flashMode)
}
2.2. 编写Flutter插件,实现FlutterPlugin接口
class PlateRecognitionViewPlugin : FlutterPlugin, ActivityAware {
private var appContext: Context? = null
private var activity: Activity? = null
private lateinit var flutterBinding: FlutterPlugin.FlutterPluginBinding
private var methodChannel: MethodChannel? = null
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
appContext = binding.applicationContext
flutterBinding = binding
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
if (methodChannel != null) {
methodChannel?.setMethodCallHandler(null)
methodChannel = null
}
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
flutterBinding.platformViewRegistry.registerViewFactory("PlateRecognitionView-Plugin",
PlateRecognitionPlatformViewFactory(flutterBinding.binaryMessenger))
}
override fun onDetachedFromActivityForConfigChanges() {
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
}
override fun onDetachedFromActivity() {}
}
- 编写Flutter组件,实现与Native组件通讯
typedef void OnPlateCongnitionViewCreated();
/// 车牌识别控件视图
class PlateCongnitionView extends StatefulWidget {
PlateCongnitionView({
Key key,
@required this.width,
@required this.height,
this.background,
@required this.onViewCreated,
}) : super(key: key);
final double width;
final double height;
final Color background;
final OnPlateCongnitionViewCreated onViewCreated;
@override
State<StatefulWidget> createState() => PlateConginitionViewState();
}
class PlateConginitionViewState extends State<PlateCongnitionView> {
MethodChannel _methodChannel;
EventChannel _eventChannel;
bool _flashLightEnabled = false;
/// 初始化操作
/// [flashMode]-闪光灯模式
void initSpotCamera(int flashMode) {
_flashLightEnabled = flashMode == FLASH_MODE_ON;
_methodChannel?.invokeMethod('initSpotCamera', {'flashMode': flashMode});
}
/// 获取闪光灯是否已打开
bool get flashLightEnabled => _flashLightEnabled;
/// 设置闪光灯是否已打开
set flashLightEnabled(bool value) {
_flashLightEnabled = value;
_methodChannel?.invokeMethod(
'setFlashMode', {'flashMode': value ? FLASH_MODE_ON : FLASH_MODE_OFF});
}
/// 重新识别
void restart() => _methodChannel?.invokeMethod('restart');
/// 唤醒
void resume() {
_methodChannel?.invokeMethod('resume');
restart(); //重新调用识别功能
}
/// 暂停
void pause() => _methodChannel?.invokeMethod('pause');
/// 接收识别结果事件
void onReceiveResult(void listen(String cardNum)) {
_eventChannel
?.receiveBroadcastStream()
?.listen((data) => listen(data as String));
}
/// 视图创建成功事件
void _onViewCreated(id) {
_methodChannel = MethodChannel('PlateRecognitionView$id-CN');
_eventChannel = EventChannel('PlateRecognitionView$id-ET');
widget.onViewCreated();
}
@override
Widget build(BuildContext context) => Container(
width: widget.width,
height: widget.height,
color: widget.background,
child: AndroidView(
viewType: 'PlateRecognitionView-Plugin',
creationParamsCodec: StandardMessageCodec(),
onPlatformViewCreated: _onViewCreated));
@override
void dispose() {
if (_methodChannel != null) _methodChannel = null;
if (_eventChannel != null) _eventChannel = null;
super.dispose();
}
}
4.测试用例,注意控住相机生命周期
/// 闪光灯自动模式
const int FLASH_MODE_AUTO = 0;
/// 闪光灯打开
const int FLASH_MODE_ON = 1;
/// 闪光灯关闭
const int FLASH_MODE_OFF = 2;
///车牌识别页面
class PlateCongnitionPage extends StatefulWidget {
PlateCongnitionPage({Key key}) : super(key: key);
@override
_PlateCongnitionPageState createState() => _PlateCongnitionPageState();
}
class _PlateCongnitionPageState extends State<PlateCongnitionPage>
with WidgetsBindingObserver {
/// 车牌视图View的Key
final GlobalKey<PlateConginitionViewState> _plateCongnitionKey = GlobalKey();
/// 闪光灯是否打开
bool _isFlashLightEnabled = false;
/// 是否显示了识别结果对话框
bool _isShownPlateCongnitionResultDialog = false;
/// 车牌识别状态View
PlateConginitionViewState get _plateCongnitionState =>
_plateCongnitionKey.currentState;
/// 更改闪光灯状态
void _onChangeFlashLightState(v) {
setState(() => _isFlashLightEnabled = v);
_plateCongnitionState.flashLightEnabled = v;
AppConfigUtil().setIsFlashLightOn(v);
}
/// 车牌识别视图建立事件
void _onPlateCongnitionViewCreated() async {
bool isFlashLightEnabled = await AppConfigUtil().isFlashLightOn;
_plateCongnitionState
..initSpotCamera(isFlashLightEnabled ? FLASH_MODE_ON : FLASH_MODE_OFF)
..onReceiveResult(_showPlateCongnitionResultDialog);
setState(() => _isFlashLightEnabled = isFlashLightEnabled);
}
/// 显示车牌识别结果对话框
///
/// [plateNum]-车牌号
void _showPlateCongnitionResultDialog(String plateNum) async {
if (!_isShownPlateCongnitionResultDialog) {
_isShownPlateCongnitionResultDialog = true;
_plateCongnitionState.pause();
var result = await showDialog(
context: context,
useSafeArea: false,
barrierDismissible: false,
builder: (_) => PlateCongnitionResultPage(plateNum: plateNum));
if (result != null && result is VehicleInfoEntity) {
Navigator.pop(context, result);
_isShownPlateCongnitionResultDialog = false;
return;
}
_plateCongnitionState.resume();
_isShownPlateCongnitionResultDialog = false;
}
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
Widget build(BuildContext context) => WillPopScope(
onWillPop: () async => Future.value(true),
child: Scaffold(
appBar: AppBar(
title: Text('车牌识别'),
backgroundColor: Colors.lightBlue,
centerTitle: true),
body: _buildContentView()));
/// 构建主体视图控件
Widget _buildContentView() => Stack(children: [
PlateCongnitionView(
key: _plateCongnitionKey,
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height -
kToolbarHeight -
MediaQuery.of(context).padding.top,
onViewCreated: _onPlateCongnitionViewCreated),
Positioned(
left: 0,
right: 0,
bottom: 20,
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('闪光灯',
style: TextStyle(fontSize: 12, color: Colors.white)),
CupertinoSwitch(
activeColor: Colors.lightBlue,
trackColor: Colors.grey,
value: _isFlashLightEnabled,
onChanged: _onChangeFlashLightState)
])))
]);
@override
void didChangeAppLifecycleState(AppLifecycleState state) async {
if (state == AppLifecycleState.resumed) {
if (!_isShownPlateCongnitionResultDialog) {
bool isFlashLightEnabled = await AppConfigUtil().isFlashLightOn;
setState(() => _isFlashLightEnabled = isFlashLightEnabled);
_plateCongnitionState?.resume();
if (isFlashLightEnabled)
_plateCongnitionState?.flashLightEnabled = true;
}
} else if (state == AppLifecycleState.inactive) {
_plateCongnitionState?.pause();
}
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
}
网友评论