欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Flutter?阻止系統(tǒng)鍵盤彈出的優(yōu)雅方式

 更新時間:2022年11月15日 14:32:02   作者:法的空間  
這篇文章主要為大家介紹了Flutter?阻止系統(tǒng)鍵盤彈出的優(yōu)雅方式詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

前言

開篇先吐槽一下,輸入框和文本,一直都是官方每個版本改動的重點,先不說功能上全不全的問題,每次版本升級,必有 breaking change 。對于 extended_text_field | Flutter Package (flutter-io.cn)extended_text | Flutter Package (flutter-io.cn) 來說,新功能都是基于官方的代碼,每次版本升級,merge 代碼就一個字,頭痛,已經有了躺平的想法了。(暫時不 merge 了,能運行就行,等一個穩(wěn)定點的官方版本,準備做個重構,重構一個相對更好 merge 代碼的結構。)

系統(tǒng)鍵盤彈出的原因

吐槽完畢,我們來看一個常見的場景,就是自定義鍵盤。要想顯示自己自定義的鍵盤,那么必然需要隱藏系統(tǒng)的鍵盤。方法主要有如下:

  • 在合適的時機調用,SystemChannels.textInput.invokeMethod<void>('TextInput.hide')。
  • 系統(tǒng)鍵盤為啥會彈出來,是因為某些代碼調用了 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 方法的地方全部注釋掉。這樣子確實系統(tǒng)鍵盤就不會再彈出來了。

在實際開發(fā)過程中,兩種方法都有自身的問題:

第一種方法會導致系統(tǒng)鍵盤上下,會造成布局閃爍,而且調用這個方法的時機也很容易造成額外的 bug 。

第二種方法,就跟我吐槽的一樣,復制官方代碼真的是吃力不討好的一件事情,版本遷移的時候,沒人愿意再去復制一堆代碼。如果你使用的是三方的組件,你可能還需要去維護三方組件的代碼。

攔截系統(tǒng)鍵盤彈出信息

實際上,系統(tǒng)鍵盤是否彈出,完全是因為 SystemChannels.textInput.invokeMethod<void>('TextInput.show') 的調用,但是我們不可能去每個調用該方法地方去做處理,那么這個方法執(zhí)行后續(xù),我們有辦法攔截嗎? 答案當然是有的。

FlutterFramework 層發(fā)送信息 TextInput.showFlutter 引擎是通過 MethodChannel, 而我們可以通過重載 WidgetsFlutterBindingcreateBinaryMessenger 方法來處理FlutterFramework 層通過 MethodChannel 發(fā)送的信息。

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 層發(fā)送信息到 Flutter 引擎,會走這個方法,這也是我們需要的處理的方法。

  • setMessageHandler

Flutter 引擎 發(fā)送信息到 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);
  }
}

接下來我們可以根據(jù)我們的需求處理 send 方法了。當 channelSystemChannels.textInput 的時候,根據(jù)方法名字來攔截 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);
  }

現(xiàn)在交給我們最后問題就是怎么確定這次消息需要被攔截?當需要發(fā)送 TextInput.show 消息的時候,必定有某個 FocusNode 處于 Focus 的狀態(tài)。那么可以根據(jù)這個 FocusNode 做區(qū)分。

我們定義個一個特別的 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;
}

這樣子,我們就可以根據(jù)以下代碼進行判斷。

  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();
  }

當然,怎么實現(xiàn)自定義鍵盤,可以根據(jù)自己的情況來決定,比如如果你的鍵盤需要頂起布局的話,你完全可以寫成下面的布局。

Column(
  children: <Widget>[
    // 你的頁面
    Expanded(child: Container()),
    // 你的自定義鍵盤
    Container(),
  ],
);

結語

通過對 createBinaryMessenger 的重載,我們實現(xiàn)對系統(tǒng)鍵盤彈出的攔截,避免我們對官方代碼的依賴。其實 SystemChannels 當中,還有些其他的系統(tǒng)的 channel,我們也能通過相同的方式去對它們進行攔截,比如可以攔截按鍵。

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

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

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

以上就是Flutter 阻止系統(tǒng)鍵盤彈出的優(yōu)雅方式的詳細內容,更多關于Flutter 阻止系統(tǒng)鍵盤彈出的資料請關注腳本之家其它相關文章!

相關文章

最新評論