Flutter 阻止系统键盘弹出的优雅方式

目录
  • 前言
  • 系统键盘弹出的原因
  • 拦截系统键盘弹出信息
  • 画自己的键盘
  • 结语

前言

开篇先吐槽一下,输入框和文本,一直都是官方每个版本改动的重点,先不说功能上全不全的问题,每次版本升级,必有 breaking change 。对于 extended_text_field | Flutter Package (flutter-io.cn)extended_text | Flutter Package (flutter-io.cn) 来说,新功能都是基于官方的代码,每次版本升级,merge 代码就一个字,头痛,已经有了躺平的想法了。(暂时不 merge 了,能运行就行,等一个稳定点的官方版本,准备做个重构,重构一个相对更好 merge 代码的结构。)

系统键盘弹出的原因

吐槽完毕,我们来看一个常见的场景,就是自定义键盘。要想显示自己自定义的键盘,那么必然需要隐藏系统的键盘。方法主要有如下:

  • 在合适的时机调用,SystemChannels.textInput.invokeMethod<void>('TextInput.hide')
  • 系统键盘为啥会弹出来,是因为某些代码调用了 SystemChannels.textInput.invokeMethod<void>('TextInput.show'),那么我们可以魔改官方代码, 把 TextFieldEditableText 的代码复制出来。

EditableTextState 代码中有一个 TextInputConnection? _textInputConnection;,它会在有需要的时候调用 show 方法。

TextInputConnectionshow,如下。

  /// Requests that the text input control become visible.
  void show() {
    assert(attached);
    TextInput._instance._show();
  }

TextInput_show,如下。

  void _show() {
    _channel.invokeMethod<void>('TextInput.show');
  }

那么问题就简单了,把 TextInputConnection 调用 show 方法的地方全部注释掉。这样子确实系统键盘就不会再弹出来了。

在实际开发过程中,两种方法都有自身的问题:

第一种方法会导致系统键盘上下,会造成布局闪烁,而且调用这个方法的时机也很容易造成额外的 bug

第二种方法,就跟我吐槽的一样,复制官方代码真的是吃力不讨好的一件事情,版本迁移的时候,没人愿意再去复制一堆代码。如果你使用的是三方的组件,你可能还需要去维护三方组件的代码。

拦截系统键盘弹出信息

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

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

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

在 main 方法中初始化这个 binding

class YourBinding extends WidgetsFlutterBinding with TextInputBindingMixin,YourBindingMixin {
 }
 void main() {
   YourBinding();
   runApp(const MyApp());
 }

BinaryMessenger3 个方法需要重载.

class TextInputBinaryMessenger extends BinaryMessenger {
  TextInputBinaryMessenger(this.origin);
  final BinaryMessenger origin;
  @override
  Future<ByteData?>? send(String channel, ByteData? message) {
    // TODO: implement send
    throw UnimplementedError();
  }
  @override
  void setMessageHandler(String channel, MessageHandler? handler) {
    // TODO: implement setMessageHandler
  }
  @override
  Future<void> handlePlatformMessage(String channel, ByteData? data,
      PlatformMessageResponseCallback? callback) {
    // TODO: implement handlePlatformMessage
    throw UnimplementedError();
  }
}
  • send

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

  • setMessageHandler

Flutter 引擎 发送信息到 FlutterFramework 层的回调。在我们的场景中不用处理。

  • handlePlatformMessage

sendsetMessageHandler 二和一,看了下 注释,似乎是服务于 test

  static const MethodChannel platform = OptionalMethodChannel(
      'flutter/platform',
      JSONMethodCodec(),
  );

对于不需要处理的方法,我们做以下处理。

class TextInputBinaryMessenger extends BinaryMessenger {
  TextInputBinaryMessenger(this.origin);
  final BinaryMessenger origin;
  @override
  Future<ByteData?>? send(String channel, ByteData? message) {
    // TODO: 处理我们自己的逻辑
    return origin.send(channel, message);
  }
  @override
  void setMessageHandler(String channel, MessageHandler? handler) {
    origin.setMessageHandler(channel, handler);
  }
  @override
  Future<void> handlePlatformMessage(String channel, ByteData? data,
      PlatformMessageResponseCallback? callback) {
    return origin.handlePlatformMessage(channel, data, callback);
  }
}

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

  static const MethodChannel textInput = OptionalMethodChannel(
      'flutter/textinput',
      JSONMethodCodec(),
  );
  @override
  Future<ByteData?>? send(String channel, ByteData? message) async {
    if (channel == SystemChannels.textInput.name) {
      final MethodCall methodCall =
          SystemChannels.textInput.codec.decodeMethodCall(message);
      switch (methodCall.method) {
        case 'TextInput.show':
          // 处理是否需要滤过这次消息。
          return SystemChannels.textInput.codec.encodeSuccessEnvelope(null);
        default:
      }
    }
    return origin.send(channel, message);
  }

现在交给我们最后问题就是怎么确定这次消息需要被拦截?当需要发送 TextInput.show 消息的时候,必定有某个 FocusNode 处于 Focus 的状态。那么可以根据这个 FocusNode 做区分。

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

class TextInputFocusNode extends FocusNode {
  /// no system keyboard show
  /// if it's true, it stop Flutter Framework send `TextInput.show` message to Flutter Engine
  bool ignoreSystemKeyboardShow = true;
}

这样子,我们就可以根据以下代码进行判断。

  Future<ByteData?>? send(String channel, ByteData? message) async {
    if (channel == SystemChannels.textInput.name) {
      final MethodCall 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 SystemChannels.textInput.codec.encodeSuccessEnvelope(null);
          }
          break;
        default:
      }
    }
    return origin.send(channel, message);
  }

最后我们只需要为 TextField 传入这个特殊的 FocusNode

final TextInputFocusNode _focusNode = TextInputFocusNode()..debugLabel = 'YourTextField';
  @override
  Widget build(BuildContext context) {
    return TextField(
      focusNode: _focusNode,
    );
  }

画自己的键盘

这里主要讲一下,弹出和隐藏键盘的时机。你可以通过当前焦点的变化的时候,来显示或者隐藏自定义的键盘。

当你的自定义键盘能自己关闭,并且保存焦点不丢失的,你那还应该在 [TextField] 的 onTap 事件中,再次判断键盘是否显示。比如我写的例子中使用的是 showBottomSheet 方法,它是能通过 drag 来关闭自己的。

下面为一个简单的例子,完整的例子在 extended_text_field/no_keyboard.dart at master · fluttercandies/extended_text_field (github.com)

  PersistentBottomSheetController<void>? _bottomSheetController;
  final TextInputFocusNode _focusNode = TextInputFocusNode()..debugLabel = 'YourTextField';
  @override
  void initState() {
    super.initState();
    _focusNode.addListener(_handleFocusChanged);
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: TextField(
          // you must use TextInputFocusNode
          focusNode: _focusNode,
          ),
    );
  }
  void _onTextFiledTap() {
    if (_bottomSheetController == null) {
      _handleFocusChanged();
    }
  }
  void _handleFocusChanged() {
    if (_focusNode.hasFocus) {
      // just demo, you can define your custom keyboard as you want
      _bottomSheetController = showBottomSheet<void>(
          context: FocusManager.instance.primaryFocus!.context!,
          // set false, if don't want to drag to close custom keyboard
          enableDrag: true,
          builder: (BuildContext b) {
            // your custom keyboard
            return Container();
          });
      // maybe drag close
      _bottomSheetController?.closed.whenComplete(() {
        _bottomSheetController = null;
      });
    } else {
      _bottomSheetController?.close();
      _bottomSheetController = null;
    }
  }
  @override
  void dispose() {
    _focusNode.removeListener(_handleFocusChanged);
    super.dispose();
  }

当然,怎么实现自定义键盘,可以根据自己的情况来决定,比如如果你的键盘需要顶起布局的话,你完全可以写成下面的布局。

Column(
  children: <Widget>[
    // 你的页面
    Expanded(child: Container()),
    // 你的自定义键盘
    Container(),
  ],
);

结语

通过对 createBinaryMessenger 的重载,我们实现对系统键盘弹出的拦截,避免我们对官方代码的依赖。其实 SystemChannels 当中,还有些其他的系统的 channel,我们也能通过相同的方式去对它们进行拦截,比如可以拦截按键。

  static const BasicMessageChannel<Object?> keyEvent = BasicMessageChannel<Object?>(
      'flutter/keyevent',
      JSONMessageCodec(),
  );

本文相关代码都在 extended_text_field | Flutter Package (flutter-io.cn)

最最后放上 Flutter Candies 全家桶,真香。

以上就是Flutter 阻止系统键盘弹出的优雅方式的详细内容,更多关于Flutter 阻止系统键盘弹出的资料请关注我们其它相关文章!

(0)

相关推荐

  • flutter Bloc add两次只响应一次问题解析

    目录 问题描述 原因分析 处理方式 问题描述 连续调用两次addEvent,结果最终只能响应一次,第二次事件无法响应. @override Stream<SomeState> mapEventToState(SomeEvent event) async*{ if(event is InCreaseEvent){ state.num ++; yield state; } } someBloc.add(InCreaseEvent()); someBloc.add(InCreaseEvent());

  • 封装flutter状态管理工具示例详解

    目录 引言 RxBinder 代码实现 Demo 完美运行 引言 关于 Flutter 状态管理,公司项目使用的是Bloc方案.Bloc 其实本质上是 provider 的封装扩展库,整体通过 InheritedWidget .Notifier 外加 Stream中转实现状态变更通知. 关于 Bloc 实现原理,有兴趣的同学可以观看这篇文章 Bloc原理解析 RxBinder 撇开Bloc内部实现策略,小轰尝试基于数据驱动模型,自定义一套状态管理工具.构思如下: 主要成员如下: RxBinder

  • Flutter在项目中使用动画不使用包实现详解

    目录 前言 正文 1 按下按钮柔软的感觉 2 想要一个像 Instagram 一样的喜欢按钮吗? 3 动画页面过渡 4 动画文字 5 更改/闪动文本样式 前言 动画对于 web 和移动应用程序都非常重要.但是在移动应用程序中不应该使用夸张的动画.简单但是很多动画使你的应用程序更好用.以至于当你点击一个按钮时,一种平滑的感觉或者页面过渡都会影响到你. 正文 1 按下按钮柔软的感觉 class _CustomButtonState extends State<CustomButton> with

  • Flutter runApp到渲染上屏分析详解

    目录 起源 分析准备 ensureInitialized scheduleAttachRootWidget scheduleWarmUpFrame 总结 起源 flutter作为一个跨平台的框架,在绘制上体现出了它跨平台的良好性能.那么,它是如何从runApp()后 绘制上屏的呢?本文将与你一起去探索这一过程. ps: 为了思维不中断, 本文仅对整体流程作分析,不会深入分析具体实现 我们运行一个flutter app ,入口一定是从runApp() 中进行的. 那么flutter 在runApp

  • Flutter 移动程序安全性提高的八个建议

    目录 正文 1. Obfuscate code 混淆代码 2. background snapshots 后台快照 3. Stay up-to-date 更新程序 4. Flushing in-memory cache 刷新内存缓存 5. local authentication 本地认证 6. Secure Storage 安全储存 7. Restrict network traffic 限制网络流量 8. jail-breaking protection 越狱保护 正文 这里有一些步骤,我们

  • flutter Bloc 实现原理示例解析

    目录 序言 1. 事件流 > 状态流 (中转) 2. 使用 BlocBuilder 实时监听状态变更, 如何实现的呢? 总结 扩展 序言 在flutter开发中,我们使用 bloc 框架,基于状态变更进行响应式开发.本篇文章,小轰将 bloc 核心业务块进行拆解简化,聊一聊它的实现思想,bloc 核心能力分为如下两点: 添加事件 event,将 '事件流' 转换为 '状态流' state 监听 bloc 流,每次 state 状态变更,通知 widget 更新 下面,用自定义Bloc的方式,来给

  • Flutter 阻止系统键盘弹出的优雅方式

    目录 前言 系统键盘弹出的原因 拦截系统键盘弹出信息 画自己的键盘 结语 前言 开篇先吐槽一下,输入框和文本,一直都是官方每个版本改动的重点,先不说功能上全不全的问题,每次版本升级,必有 breaking change .对于 extended_text_field | Flutter Package (flutter-io.cn) 和 extended_text | Flutter Package (flutter-io.cn) 来说,新功能都是基于官方的代码,每次版本升级,merge 代码就

  • iOS项目开发键盘弹出遮挡输入框问题解决方案

    在iOS或Android等移动端开发过程中,经常遇到很多需要我们输入信息的情况,例如登录时要输入账号密码.查询时要输入查询信息.注册或申请时需要填写一些信息等都是通过我们键盘来进行输入的,在iOS开发过程中,一般用于进行输入信息的有两类:UITextField和UITextView,前者是单行输入文本框,后者是可滑动的多行输入文本框,在这整个开发过程中,我们需要控制键盘的弹出和收起.在输入结束的时候获取输入的信息,此外,我们还需要保证在键盘弹起的时候不遮挡我们输入的文本框.今天,我们就主要来说一

  • Android软键盘弹出时的界面控制方法

    本文实例讲述了Android软键盘弹出时的界面控制方法.分享给大家供大家参考,具体如下: 有时候androidactivity弹出软键盘后布局改变 下面有三种模式可以改变软键盘弹出以后的显示形式 模式一:压缩模式软键盘弹出以后,会压缩原先的大小 我们可以在AndroidManifet.xml中对Activity进行设置.如: android:windowSoftInputMode="stateUnchanged|adjustResize" 模式二:平移模式 软键盘弹出以后,不会压缩原先

  • Android 软键盘弹出时把原来布局顶上去的解决方法

    键盘弹出时,会将布局底部的导航条顶上去. 解决办法: 在mainfest.xml中,在和导航栏相关的activity中加: <activity android:name=".filing.MainActivity" android:windowSoftInputMode="adjustResize|stateHidden" /> windowSoftInputMode 属性解释: 活动的主窗口如何与包含屏幕上的软键盘窗口交互.这个属性的设置将会影响两件事

  • 解决Android软键盘弹出覆盖h5页面输入框问题

    之前我们在使用vue进行 h5 表单录入的过程中,遇到了Android软键盘弹出,覆盖 h5页面 输入框 问题,在此进行回顾并分享给大家: 系统:Android 条件:当输入框在可视区底部或者偏下的位置 触发条件:输入框获取焦点,弹出软键盘 表现:软键盘 覆盖 h5页面中的输入框 问题分析: 1.发现问题:当前页面中box为flex布局,内容为上下固定高,中间自适应(中间区域内容过多会出现滚动条,input框在wrapper的底部),input获取焦点,手机键盘弹出,input未上移到可视区内,

  • 键盘弹出时会覆盖文本框怎么解决

    在一些网站登陆界面,我们经常会见到,键盘的出现与隐藏操作,那么基于代码是如何实现的呢?下面小编写了具体代码介绍,特此分享到我们平台,供大家参考 先给大家展示下效果图: 具体代码如下所示: #import "ViewController.h" #import "UIView+FrameExtension.h" // 可以自己写,以后用着方便 #define kDeviceHeight [UIScreen mainScreen].bounds.size.height @

  • Android监听软键盘弹出与隐藏的两种方法

    需求: 现在有一个需求是点击一行文本框,弹出一个之前隐藏的输入框,输入完成后按返回键或者其他的东西隐藏键盘和输入框,将输入框的内容填充到文本框中. 实现: 拿到这个需求的第一反应就是写一个监听来监听键盘的显示和隐藏来控制输入框的显示和隐藏,控制文本框中的内容. 所以我做了如下操作: 指定android:windowSoftInputMode="adjustResize|stateAlwaysHidden"这个的做法是为了让键盘弹出时改变布局. 让Activity实现Layoutchan

  • Android判断软键盘弹出并隐藏的简单完美解决方法(推荐)

    最近项目中有一个编辑框,下面是个ListView.在触发编辑框弹出软键盘后,ListView还能滑动,并且ListView的item还能响应单击.这样的体验效果很不好.于是便想在滑动或单击item时判断键盘是否弹出,若弹出,则把它隐藏. 网上一搜,发现Android并没有直接提供软键盘的弹出与隐藏判断,一些解决方案诸如判断父控件的高度或者判断 if(getWindow().getAttributes().softInputMode==WindowManager.LayoutParams.SOFT

  • 手机软键盘弹出时影响布局的解决方法

    移动端软键盘弹出时影响布局该如何解决 1)前段时间作的移动端页面,因为高度只有1页所以用了height:100%;width:100%; ios下点击输入框弹出软键盘时,布局没有大影响. 当输入框可能被软键盘压到时,在一部分安卓下却出了问题,华为自带浏览器和UC会有布局上的问题. 原因可能是因为,软键盘弹出时,改变了height,使height:100%,不能达到原来的高度. 解决办法: $(document).ready(function () { $('body').height($('bo

  • Android实现登陆页logo随键盘收放动态伸缩(完美解决键盘弹出遮挡控件的问题)

    在最近的两个项目中,项目需求要求我们实现 /*登陆页面的内容能够随着键盘的弹出而被顶上去,避免键盘遮挡住登陆按钮*/ 这样的效果,宝宝心里苦呀,本来半天搞定的事还非得折腾一下,好吧我妥协,毕竟我还是一只非常注重用户体验的猿. 那就做吧,初步定下的方案是输入框和登陆按钮大小不变,在键盘弹出的时候让logo的大小和位置进行改变,从而给键盘腾出位置,当然在键盘收起的时候还要给它还原一下,就像什么都没发生一样,嗯对,就是这样,说了这么多,放张图先感受一下效果吧: 接下来上正餐,布局上比较简单,注意给图片

随机推荐