[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 的坑什么时候能填完 ~~~~

 

posted @ 2021-01-05 18:29  我爱我家喵喵  阅读(655)  评论(0编辑  收藏  举报