• [Flutter] Zefyr 富文本插件安卓和Windows桌面端不响应删除键问题


    由于Flutter的不断升级, 发现Zefyr插件在安卓平台无法正常的响应删除键了,后来测试桌面端也有此问题,在iOS中则正常。(我的Flutter是master分支 1.26.0-2.0.pre.173 )

    动态调试,发现在 input.dart 中, 按下删除键时,updateEditingValue 函数都没有触发,但其它的一些输入则会触发,很怪异。更奇怪的是,在模拟器中调试状态 Zefyr 功能还一切正常,打包放到手机就不行了。

    研究了 TextField 小部件的原码,发现在 RenderEditable 类中, 会在文本框获取焦点后,执行:

    RawKeyboard.instance.addListener(_handleKeyEvent);
    

    也就是给全局的键盘安装一个事件监听。在失去焦点的时候移除此事件处理。

    来看看这个按键处理代码:

      void _handleKeyEvent(RawKeyEvent keyEvent) {
        if (kIsWeb) {
          // On web platform, we should ignore the key because it's processed already.
          return;
        }
    
        if (keyEvent is! RawKeyDownEvent || onSelectionChanged == null)
          return;
        final Set<LogicalKeyboardKey> keysPressed = LogicalKeyboardKey.collapseSynonyms(RawKeyboard.instance.keysPressed);
        final LogicalKeyboardKey key = keyEvent.logicalKey;
    
        final bool isMacOS = keyEvent.data is RawKeyEventDataMacOs;
        if (!_nonModifierKeys.contains(key) ||
            keysPressed.difference(isMacOS ? _macOsModifierKeys : _modifierKeys).length > 1 ||
            keysPressed.difference(_interestingKeys).isNotEmpty) {
          // If the most recently pressed key isn't a non-modifier key, or more than
          // one non-modifier key is down, or keys other than the ones we're interested in
          // are pressed, just ignore the keypress.
          return;
        }
    
        // TODO(ianh): It seems to be entirely possible for the selection to be null here, but
        // all the keyboard handling functions assume it is not.
        assert(selection != null);
    
        final bool isWordModifierPressed = isMacOS ? keyEvent.isAltPressed : keyEvent.isControlPressed;
        final bool isLineModifierPressed = isMacOS ? keyEvent.isMetaPressed : keyEvent.isAltPressed;
        final bool isShortcutModifierPressed = isMacOS ? keyEvent.isMetaPressed : keyEvent.isControlPressed;
        if (_movementKeys.contains(key)) {
          _handleMovement(key, wordModifier: isWordModifierPressed, lineModifier: isLineModifierPressed, shift: keyEvent.isShiftPressed);
        } else if (isShortcutModifierPressed && _shortcutKeys.contains(key)) {
          // _handleShortcuts depends on being started in the same stack invocation
          // as the _handleKeyEvent method
          _handleShortcuts(key);
        } else if (key == LogicalKeyboardKey.delete) {
          _handleDelete(forward: true);
        } else if (key == LogicalKeyboardKey.backspace) {
          _handleDelete(forward: false);
        }
      }

    可以看到,它处理了方向键和 delete, backspace 键,如果是 delete 键则向后删除, backspace 则向前删除,都是调用了 _handleDelete 函数。

      void _handleDelete({ required bool forward }) {
        final TextSelection selection = textSelectionDelegate.textEditingValue.selection;
        final String text = textSelectionDelegate.textEditingValue.text;
        assert(_selection != null);
        if (_readOnly) {
          return;
        }
        String textBefore = selection.textBefore(text);
        String textAfter = selection.textAfter(text);
        int cursorPosition = math.min(selection.start, selection.end);
        // If not deleting a selection, delete the next/previous character.
        if (selection.isCollapsed) {
          if (!forward && textBefore.isNotEmpty) {
            final int characterBoundary = previousCharacter(textBefore.length, textBefore);
            textBefore = textBefore.substring(0, characterBoundary);
            cursorPosition = characterBoundary;
          }
          if (forward && textAfter.isNotEmpty) {
            final int deleteCount = nextCharacter(0, textAfter);
            textAfter = textAfter.substring(deleteCount);
          }
        }
        final TextSelection newSelection = TextSelection.collapsed(offset: cursorPosition);
        if (selection != newSelection) {
          _handleSelectionChange(
            newSelection,
            SelectionChangedCause.keyboard,
          );
        }
        textSelectionDelegate.textEditingValue = TextEditingValue(
          text: textBefore + textAfter,
          selection: newSelection,
        );
      }

    这个 _handleDelete 函数主要就是根据删除方向,将选中的内容清掉并改变光标到正确的位置 ( _handleSelectionChange 函数 ),再将 textSelectionDelegate.textEditingValue 置为最新的值。

    既然官方的 TextField 这样子来处理删除键,虽然我还不知道为什么之前不处理,过去也能正常,但现在将 Zefyr 插件出异常的安卓和win桌面端也这样处理一下应该就可以解决问题了。

     

    修改如下:

    lib/src/widgets/editable_text.dart 文件

    import 'dart:io';
    import 'dart:math' as math;
    import 'package:flutter/foundation.dart';
    
    ......
    
      bool _listenerAttached = false;
    
      void _cancelSubscriptions() {
        _handleUpdateKeyEvent(false);
        _renderContext.removeListener(_handleRenderContextChange);
        widget.controller.removeListener(_handleLocalValueChange);
        _focusNode.removeListener(_handleFocusChange);
        _input.closeConnection();
        _cursorTimer.stop();
      }
    
      void _handleFocusChange() {
        _handleUpdateKeyEvent();
        _input.openOrCloseConnection(_focusNode,
            widget.controller.plainTextEditingValue, widget.keyboardAppearance);
        _cursorTimer.startOrStop(_focusNode, selection);
        updateKeepAlive();
      }
    
      void _handleUpdateKeyEvent([bool value]) {
        if (Platform.isAndroid || Platform.isWindows) {
    // 因为只发现 android 和 windows 会有问题
    if (_focusNode.hasFocus && (value == null || value == true)) { RawKeyboard.instance.addListener(_handleKeyEvent); _listenerAttached = true; } else if (_listenerAttached) { RawKeyboard.instance.removeListener(_handleKeyEvent); _listenerAttached = false; } } } // 主要增加的代码 void _handleKeyEvent(RawKeyEvent keyEvent) { if (kIsWeb) { // On web platform, we should ignore the key because it's processed already. return; } if (keyEvent is! RawKeyDownEvent) { return; } final keysPressed = LogicalKeyboardKey.collapseSynonyms(RawKeyboard.instance.keysPressed); final key = keyEvent.logicalKey; final isMacOS = keyEvent.data is RawKeyEventDataMacOs; if (!_nonModifierKeys.contains(key) || keysPressed.difference(isMacOS ? _macOsModifierKeys : _modifierKeys).length > 1 || keysPressed.difference(_interestingKeys).isNotEmpty) { // If the most recently pressed key isn't a non-modifier key, or more than // one non-modifier key is down, or keys other than the ones we're interested in // are pressed, just ignore the keypress. return; }
    // 只管删除功能,方向键暂时不管,需要可以加上
    if (key == LogicalKeyboardKey.delete) { _handleDelete(forward: true); } else if (key == LogicalKeyboardKey.backspace) { _handleDelete(forward: false); } } void _handleDelete({ @required bool forward }) { final selection = widget.controller.plainTextEditingValue.selection; assert(selection != null); final text = widget.controller.plainTextEditingValue.text; if (text.isEmpty) return; var textBefore = selection.textBefore(text); var textAfter = selection.textAfter(text); var cursorPosition = math.min(selection.start, selection.end); if (selection.isCollapsed) { if (!forward && cursorPosition > 0) { // ignore: invalid_use_of_visible_for_testing_member final characterBoundary = RenderEditable.previousCharacter(cursorPosition, textBefore); final newSelection = TextSelection.collapsed(offset: characterBoundary); widget.controller.replaceText(characterBoundary, cursorPosition - characterBoundary, '', selection: newSelection); } if (forward && textAfter.isNotEmpty) { // ignore: invalid_use_of_visible_for_testing_member final deleteCount = RenderEditable.nextCharacter(0, textAfter); final newSelection = TextSelection.collapsed(offset: cursorPosition); widget.controller.replaceText(cursorPosition, deleteCount, '', selection: newSelection); } } else { final newSelection = TextSelection.collapsed(offset: cursorPosition); widget.controller.replaceText(cursorPosition, math.max(selection.start, selection.end) - cursorPosition, '', selection: newSelection); } } static final Set<LogicalKeyboardKey> _movementKeys = <LogicalKeyboardKey>{ LogicalKeyboardKey.arrowRight, LogicalKeyboardKey.arrowLeft, LogicalKeyboardKey.arrowUp, LogicalKeyboardKey.arrowDown, }; static final Set<LogicalKeyboardKey> _shortcutKeys = <LogicalKeyboardKey>{ LogicalKeyboardKey.keyA, LogicalKeyboardKey.keyC, LogicalKeyboardKey.keyV, LogicalKeyboardKey.keyX, LogicalKeyboardKey.delete, LogicalKeyboardKey.backspace, }; static final Set<LogicalKeyboardKey> _nonModifierKeys = <LogicalKeyboardKey>{ ..._shortcutKeys, ..._movementKeys, }; static final Set<LogicalKeyboardKey> _modifierKeys = <LogicalKeyboardKey>{ LogicalKeyboardKey.shift, LogicalKeyboardKey.control, LogicalKeyboardKey.alt, }; static final Set<LogicalKeyboardKey> _macOsModifierKeys = <LogicalKeyboardKey>{ LogicalKeyboardKey.shift, LogicalKeyboardKey.meta, LogicalKeyboardKey.alt, }; static final Set<LogicalKeyboardKey> _interestingKeys = <LogicalKeyboardKey>{ ..._modifierKeys, ..._macOsModifierKeys, ..._nonModifierKeys, };

    经测试,此问题成功解决。或许有一天,Flutter 底层就处理好了, 或许 Zefyr 作者改好了, 那就更完美了。

    Zefyr 的坑什么时候能填完 ~~~~

  • 相关阅读:
    监控 Linux 性能的 18 个命令行工具
    VS2015中无法查找或打开 PDB 文件
    C1853 编译器错误:fatal error C1853: 'pjtname.pch' precompiled header file is from a previous
    malloc用法
    C语言中i++和++i的区别
    vs未定义的标识符“round”ceil()和floor()
    error C2065: “uint8_t”: 未声明的标识符
    strtol 函数用法
    C++ “string”: 未声明的标识符
    C++ 中c_str()函数
  • 原文地址:https://www.cnblogs.com/yangyxd/p/14237407.html
Copyright © 2020-2023  润新知