美文网首页Flutter
flutter:如何优雅的实现扫码枪获取数据源

flutter:如何优雅的实现扫码枪获取数据源

作者: 李小轰 | 来源:发表于2023-01-09 19:08 被阅读0次

    前言

    在往期的分享中,小编介绍了如何通过 flutter 自带的 EditableText 实现扫码枪数据源的获取。大致实现如下:

    • 扫码枪本质上是一个外接的输入设备。
    • 使用 Stack 结合自己的布局控件 childWidgeteditableText 封装,控制隐藏。可通过监听 onSubmitted 获取扫码枪的输入内容。

    痛点问题

    回顾 往期分享 痛点问题 :

    使用 EditableText 的过程中遇到了系统键盘弹出的问题。我们通过 Edit 的焦点来获取扫码枪的输入。但 EditableText 一旦获取了焦点,内部会调用原生层唤起键盘。

    扫码枪触发焦点后,系统键盘自动弹起。这样的失败交互困扰了小编很久。

    • 往期分享中的临时方案
      之前的处理方式是通过定制化源码的方式,将指定版本内的 TextInput.show 手动注释掉。

    PS:这是一个笨方法,只能解燃眉之急,输入框和文本,一直都是官方每个版本改动的重点。指定版本不是长久的方案。

    如何在不改动源码的方式下,动态控制焦点是否触发键盘弹出?

    1.系统键盘弹出的原因

    实际上,系统键盘是否弹出,完全是因为 SystemChannels.textInput.invokeMethod<void>('TextInput.show')的调用,但是我们不可能去每个调用该方法地方去做处理,那么这个方法执行后续,我们有办法拦截吗? 答案当然是有的。

    2. 如何拦截 methodChannel

    Flutter 的 Framework 层发送信息 TextInput.show 到 Flutter 引擎是通过 MethodChannel, 而我们可以通过重载 WidgetsFlutterBindingcreateBinaryMessenger 方法来处理Flutter 的 Framework 层通过 MethodChannel 发送的信息。

    具体代码如下:

    使用 mixin 对 WidgetsFlutterBinding 进行方法重载

    mixin TextInputBindingMixin on WidgetsFlutterBinding {
      @override
      BinaryMessenger createBinaryMessenger() {
        return TextInputBinaryMessenger(super.createBinaryMessenger());
      }
    }
    

    在 main 方法中初始化这个 binding

    class TextInputBinding extends WidgetsFlutterBinding with TextInputBindingMixin {}
    
     void main() {
       TextInputBinding();
       runApp(const MyApp());
     }
    

    自定义 TextInputBinaryMessager 对 methodChannel 进行自定义拦截操作

    class TextInputBinaryMessenger extends BinaryMessenger {
      TextInputBinaryMessenger(this.origin);
    
      final BinaryMessenger origin;
    
      // Flutter 的 Framework 层发送信息到 Flutter 引擎,会走这个方法
      @override
      Future<ByteData?>? send(
        String channel,
        ByteData? message,
      ) {
        //TODO  拦截处理
      }
    
      // Flutter 引擎 发送信息到 Flutter 的 Framework 层的回调,无需处理
      @override
      void setMessageHandler(
        String channel,
        MessageHandler? handler,
      ) {
        ... 省略
      }
    
      //无需处理
      @override
      Future<void> handlePlatformMessage(
        String channel,
        ByteData? data,
        PlatformMessageResponseCallback? callback,
      ) {
        ... 省略
      }
    }
    

    send 方法:flutter 的 framework 层发送信息到 flutter 引擎,会走这个方法,这也是我们需要的处理的方法。

    3. 拦截思路

    可以根据我们的需求处理 send 方法了。当 channelSystemChannels.textInput 的时候,根据方法名字来拦截 TextInput.show

    再定义一个特别的 FocusNode,并且定义好一个属性用于判断(也有那种需要随时改变是否需要拦截信息的需求)。例如 TextInputFocusNode

    import 'package:flutter/material.dart';
    
    class TextInputFocusNode extends FocusNode {
      bool ignoreSystemKeyboardShow = true;
    }
    

    根据思路,我们的拦截方法实现如下:

    @override
      Future<ByteData?>? send(
        String channel,
        ByteData? message,
      ) {
        if (channel == SystemChannels.textInput.name) {
          final methodCall = SystemChannels.textInput.codec.decodeMethodCall(
            message,
          );
          switch (methodCall.method) {
            case 'TextInput.show':
              final FocusNode? focus = FocusManager.instance.primaryFocus;
              if (focus != null &&
                  focus is TextInputFocusNode &&
                  focus.ignoreSystemKeyboardShow) {
                return Future.value(
                  SystemChannels.textInput.codec.encodeSuccessEnvelope(null),
                );
              }
              break;
            default:
              break;
          }
        }
        return origin.send(channel, message);
      }
    

    扫码库更新

    小编已将本次的方案调整重新发布上传,使用方式如下:

    1. 在pubspec.yaml文件中进行引用
    dependencies:
      scan_gun: ^2.0.0
    
    1. 提供 ScanMonitorWidget 作为父节点,嵌套使用:
      ScanMonitorWidget({
        Key? key,
        required ChildBuilder childBuilder,
        TextInputFocusNode? scanNode,
        FocusNode? textFiledNode,
        required void Function(String) onSubmit,
      })
    
    1. 在 main 方法中初始化 TextInputBinding
     void main() {
       TextInputBinding();
       runApp(const MyApp());
     }
    

    参数说明:

    1. childBuilder :
      typedef ChildBuilder = Widget Function(BuildContext context),使用者自己UI作为子节点

    2. scanNode:

    非必传,如果传,可通过 scanNode 监听获取当前扫码可用状态,hasFocus 时为获取焦点

    1. GlobalKey<EditableTextState> scanKey:

    非必传,如果传,可通过 'scanKey' 强制获取获取焦点,保证扫码可用,如下
    scanKey.currentState?.requestKeyboard()

    1. textFiledNode:
      提供外部存在输入框键盘输入与扫码输入同时存在的场景。内部做了焦点切换能力,保证输入框焦点取消后,能马上切换成扫码枪的焦点
    demo

    扩展内容

    1. 有焦点,却获取不到扫码

    小编在开发过程中遇到过一个现象。

    明明 FocusNode 节点拥有焦点,扫码枪进行扫码操作后,输入框确拿不到内容。

    在此记录一下问题所在,如下,以 EditableText 为例,我们看源码中提供一个方法 requestKeyboard ,用于唤起键盘接收键盘输入源

    image.png
    结论:对于输入框来说,获取到键盘输入源需要两个条件
    • 拥有焦点
    • 开启连接
    2. 扫码枪触发联想输入(扫码无响应)

    原因:输入框输入法使用了中文输入法,扫码数据包含 英文 + 数字 时,会出发系统的联想输入,造成扫码无响应、数据错误等问题。

    处理方式:在 EditableText 中添加属性 keyboardType: TextInputType.visiblePassword , 这样便不会触发联想输入。

    以上问题,小编已在新包中进行优化

    相关文章

      网友评论

        本文标题:flutter:如何优雅的实现扫码枪获取数据源

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