由于最近在做一個Flutter項目框仔,需要使用到車牌識別的功能洼裤,并且要求識別功能使用本地的識別SDK殖蚕。于是在網上找到一個原生的車牌識別的庫冒滩。
原文地址:
http://www.reibang.com/p/94784c3bf2c1
原項目Demo地址:
https://github.com/AleynP/LPR
感謝LPR作者提供SDK及Demo支持微驶!
按照以上內容部署好原生項目代碼,然后下面我們來提供控件的移植开睡。主要分為以下幾個步驟:
①.包裝ScannerView;
②.編寫ScannerView的FlutterPlugin(kotlin實現);
③.編寫ScannerView的Dart部分實現因苹;
④.測試用例-Demo.
- 移植第一步,使用xml包裝ScannerView篇恒,如果不這樣包裝扶檐,可能會在Flutter中使用時報錯“l(fā)ayout_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)
// 設置識別結構的監(jiān)聽事件
fun setOnScannerOCRListener(onResult: (String)->Unit) = scannerView?.setOnScannerOCRListener { onResult(it) }
fun start() = scannerView?.start()
fun release() = scannerView?.release()
}
- 創(chuàng)建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));
}
/// 視圖創(chuàng)建成功事件
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;
/// 車牌識別狀態(tài)View
PlateConginitionViewState get _plateCongnitionState =>
_plateCongnitionKey.currentState;
/// 更改閃光燈狀態(tài)
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();
}
}