美文网首页Flutterndk
Flutter-车牌号识别插件(Android实现)

Flutter-车牌号识别插件(Android实现)

作者: Cosecant | 来源:发表于2020-09-06 23:55 被阅读0次

    由于最近在做一个Flutter项目,需要使用到车牌识别的功能,并且要求识别功能使用本地的识别SDK。于是在网上找到一个原生的车牌识别的库。

    原文地址:
    https://www.jianshu.com/p/94784c3bf2c1

    原项目Demo地址:
    https://github.com/AleynP/LPR
    感谢LPR作者提供SDK及Demo支持!

    按照以上内容部署好原生项目代码,然后下面我们来提供控件的移植。主要分为以下几个步骤:
    ①.包装ScannerView;
    ②.编写ScannerView的FlutterPlugin(kotlin实现);
    ③.编写ScannerView的Dart部分实现;
    ④.测试用例-Demo.

    1. 移植第一步,使用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()
    
    }
    
    1. 创建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() {}
    
    }
    
    1. 编写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();
      }
    }
    

    相关文章

      网友评论

        本文标题:Flutter-车牌号识别插件(Android实现)

        本文链接:https://www.haomeiwen.com/subject/wughektx.html