Flutter 安全键盘

在 Flutter 中目前官方没有提供快速自定义键盘的解决方案。

但在项目中以及 ICP 测评需要用到安全键盘,比如金额输入、安全密码输入、各种自定义快速输入键盘。

这里以Packages的形式实现了一个自定义安全键盘,已上传 Pub。

实现功能

  1. 适用原生输入框控件
  2. 支持一个页面多个安全键盘输入框
  3. 支持系统键盘与安全键盘混合使用
  4. 支持自动定位到输入框位置

实现思路

主要是通过拦截PlatformChannel实现的,可以无缝对接TextFiled等Flutter自带的输入框,监听输入框各种状态,以及内容变化。

关键代码讲解

  • KeyboardManager

自定义键盘管理类,包括输入类型与键盘的管理,以及输入框拦截、悬浮自定义键盘及生命周期管理等。
主要功能方法 init。

///初始化键盘监听并且传递当前页面的context
static init(BuildContext context) {
  _context = context;
  interceptorInput();
}

///拦截键盘交互
static interceptorInput() {
  if (isInterceptor) return;
  isInterceptor = true;
  BinaryMessages.setMockMessageHandler("flutter/textinput",
      (ByteData data) async {
    var methodCall = _codec.decodeMethodCall(data);
    switch (methodCall.method) {
    ...
    
    return response;
  });
}


在init 方法中对输入框交互进行拦截。

当输入框获焦时会调用TextInput.setClient、TextInput.show。


 case 'TextInput.show':
        if (_currentKeyboard != null) {
          openKeyboard();
          return _codec.encodeSuccessEnvelope(null);
        } else {
          return await _sendPlatformMessage("flutter/textinput", data);
        }
        break
 case 'TextInput.setClient':
        ...
        brea

在TextInput.setClient中根据输入框定义的键盘类型找出对应键盘对应的键盘配置、InputClient,以及初始输入变更监听KeyboardController。
在TextInput.show根据配置的键盘类型,调用 openKeyboard 生成键盘控件。

///显示键盘
static openKeyboard() {
  ///键盘已经打开
  if (_keyboardEntry != null) return;

  _pageKey = GlobalKey();
  _keyboardHeight = _currentKeyboard.getHeight();

  ///根据键盘高度,使键盘滚动到输入框位置
  KeyboardMediaQueryState queryState = _context
          .ancestorStateOfType(const TypeMatcher())
      as KeyboardMediaQueryState;
  queryState.update();

  ...

  ///往Overlay中插入插入OverlayEntry
  Overlay.of(_context).insert(_keyboardEntry);
}

这里通过 Overlay显示一个悬浮窗,并键盘弹出后,滚动到输入框位置。

当输入框输入时,会调用TextInput.setEditingState。

case 'TextInput.setEditingState':
        var editingState = TextEditingValue.fromJSON(methodCall.arguments);
        if (editingState != null && _keyboardController != null) {
          _keyboardController.value = editingState;
          return _codec.encodeSuccessEnvelope(null);
        }
        break

这里设置 KeyboardController 的TextEditingValue,就可以监听输入框的输入变化。

当切换焦点、或自定义键盘输入完成关闭时,会调用TextInput.clearClient、TextInput.hide。

     case 'TextInput.hide':
        if (_currentKeyboard != null) {
          hideKeyboard();
          return _codec.encodeSuccessEnvelope(null);
        } else {
          return await _sendPlatformMessage("flutter/textinput", data);
        }
        break;

      ///切换输入框时,会调用该回调,切换时键盘会隐藏。
      case 'TextInput.clearClient':
        hideKeyboard(animation: false);
        clearKeyboard();
        break

在 clearClient 中清除 InputClient,且隐藏键盘。

  • KeyboardController

键盘输入变更监听,可以参考系统[TextEditingController],主要通过ValueNotifier监听TextEditingValue的值变化,ValueNotifier是一个包含单个值的变更通知器,当它的值改变的时候,会通知它的监听。
扩展了 addText,deleteText,doneAction

///删除一个字符,一般用于键盘的删除键
deleteOne() {
  if (selection.baseOffset == 0) return;
  String newText = '';
  if (selection.baseOffset != selection.extentOffset) {
    newText = selection.textBefore(text) + selection.textAfter(text);
    value = TextEditingValue(
        text: newText,
        selection: selection.copyWith(
            baseOffset: selection.baseOffset,
            extentOffset: selection.baseOffset));
  } else {
    newText = text.substring(0, selection.baseOffset - 1) +
        selection.textAfter(text);
    value = TextEditingValue(
        text: newText,
        selection: selection.copyWith(
            baseOffset: selection.baseOffset - 1,
            extentOffset: selection.baseOffset - 1));
  }
}
/// 在光标位置添加文字,一般用于键盘输入
addText(String insertText) {
  String newText =
      selection.textBefore(text) + insertText + selection.textAfter(text);
  value = TextEditingValue(
      text: newText,
      selection: selection.copyWith(
          baseOffset: selection.baseOffset + insertText.length,
          extentOffset: selection.baseOffset + insertText.length));
}
/// 完成
doneAction() {
  KeyboardManager.sendPerformAction(TextInputAction.done);
}

  • KeyboardMediaQuery

用于键盘弹出的时候控制页面边间,使输入框不被挡住,自动定位到输入框位置。

class KeyboardMediaQuery extends StatefulWidget {
  final Widget child;

  KeyboardMediaQuery({this.child}) : assert(child != null);

  @override
  State createState() => KeyboardMediaQueryState();
}

class KeyboardMediaQueryState extends State {
  @override
  Widget build(BuildContext context) {
    var data = MediaQuery.of(context);

    ///消息传递,更新控件边距
    return MediaQuery(
        child: widget.child,
        data: data.copyWith(
            viewInsets: data.viewInsets
                .copyWith(bottom: KeyboardManager.keyboardHeight)));
  }

  ///通知更新
  void update() {
    setState(() => {});
  }
}

使用方法

  • Step1

在项目pubspec.yaml添加安全键盘依赖

dependencies:
  security_keyboard: ^1.0.2

  • Step2

根据项目需求编写个性化键盘

typedef KeyboardSwitch = Function(SecurityKeyboardType type);

enum SecurityKeyboardType {
  text,
  textUpperCase,
  number,
  numberOnly,
  numberSimple,
  symbol
}

class SecurityKeyboard extends StatefulWidget {
  ///用于控制键盘输出的Controller
  final KeyboardController controller;

  ///键盘类型,默认文本
  final SecurityKeyboardType keyboardType;

  ///定义InputType类型
  static const SecurityTextInputType inputType =
      const SecurityTextInputType(name: 'SecurityKeyboardInputType');

  SecurityKeyboard({this.controller, this.keyboardType});

  ///文本输入类型
  static SecurityTextInputType text =
      SecurityKeyboard._inputKeyboard(SecurityKeyboardType.text);

  ///数字输入类型
  static SecurityTextInputType number =
      SecurityKeyboard._inputKeyboard(SecurityKeyboardType.number);

  ///仅数字输入类型
  static SecurityTextInputType numberOnly =
      SecurityKeyboard._inputKeyboard(SecurityKeyboardType.numberOnly);

  ///仅数字输入类型,且没有键盘提示
  static SecurityTextInputType numberSimple =
      SecurityKeyboard._inputKeyboard(SecurityKeyboardType.numberSimple);

  ///初始化键盘类型,返回输入框类型
  static SecurityTextInputType _inputKeyboard(
      SecurityKeyboardType securityKeyboardType) {
    ///注册键盘的方法
    String inputType = securityKeyboardType.toString();
    SecurityTextInputType securityTextInputType =
        SecurityTextInputType(name: inputType);
    KeyboardManager.addKeyboard(
      securityTextInputType,
      KeyboardConfig(
        builder: (context, controller) {
          return SecurityKeyboard(
            controller: controller,
            keyboardType: securityKeyboardType,
          );
        },
        getHeight: () {
          return SecurityKeyboard.getHeight(securityKeyboardType);
        },
      ),
    );

    return securityTextInputType;
  }

  ///键盘类型
  SecurityKeyboardType get _keyboardType => keyboardType;

  ///编写获取高度的方法
  static double getHeight(SecurityKeyboardType securityKeyboardType) {
    return securityKeyboardType == SecurityKeyboardType.numberSimple
        ? LcfarmSize.dp(192)
        : LcfarmSize.dp(232);
  }


  @override
  _SecurityKeyboardState createState() => _SecurityKeyboardState();
}

class _SecurityKeyboardState extends State {
  ///当前键盘类型
  SecurityKeyboardType currentKeyboardType;

  @override
  void initState() {
    super.initState();
    currentKeyboardType = widget._keyboardType;
  }

  @override
  Widget build(BuildContext context) {
    Widget keyboard;
    switch (currentKeyboardType) {
      case SecurityKeyboardType.number:
        keyboard = NumberKeyboard(
          widget.controller,
          currentKeyboardType,
          keyboardSwitch: (SecurityKeyboardType keyboardType) {
            setState(() {
              currentKeyboardType = keyboardType;
            });
          },
        );
        break;
      case SecurityKeyboardType.numberOnly:
      case SecurityKeyboardType.numberSimple:
        keyboard = NumberKeyboard(widget.controller, currentKeyboardType);
        break;
      case SecurityKeyboardType.symbol:
        keyboard = SymbolKeyboard(widget.controller,
            (SecurityKeyboardType keyboardType) {
          setState(() {
            currentKeyboardType = keyboardType;
          });
        });
        break;
      case SecurityKeyboardType.text:
      case SecurityKeyboardType.textUpperCase:
        keyboard = AlphabetKeyboard(widget.controller, currentKeyboardType,
            (SecurityKeyboardType keyboardType) {
          setState(() {
            currentKeyboardType = keyboardType;
          });
        });
        break;
    }
    return keyboard;
  }
}
代码示例
安全键盘.jpg

将以下代码添加到要使用安全键盘的页面:

class PasswordVerify extends StatelessWidget {
  @override
  Widget buildScaffold(BuildContext context) {
    //构建包含安全键盘视图,用于键盘弹出的时候页面可以滚动到输入框的位置
    return KeyboardMediaQuery(
      child: Builder(builder: (ctx) {
        //初始化键盘监听并且传递当前页面的context
        KeyboardManager.init(ctx);
        return super.buildScaffold(context);
      }),
    );
  }

  • Step4

在TextField keyboardType中设置自定义安全性键盘类型。
只需传递Step1中编写的inputType,就像通常设置键盘输入类型一样。

TextField(
   ...
   keyboardType: SecurityKeyboard.text,
   ...
 )

最后

  如果在使用过程遇到问题,欢迎下方留言交流。

  Pub类库地址

学习资料

  • Flutter 中文网
  • Flutter Packages
  • Flutter 电子书
  • Flutter 社区中文资源网

请大家不吝点赞!因为您的点赞是对我最大的鼓励,谢谢!

你可能感兴趣的:(Flutter 安全键盘)