Flutter TextField 点击时光标如何定位位置

在使用Flutter 输入框TextField组件时,一直好奇组件是如何定位到光标位置,在学习了解TextField组件源码后,对这个问题有初步了解。

查看

TextField 源码 _CupertinoTextFieldState build方法

    return Semantics(
      enabled: enabled,
      onTap: !enabled || widget.readOnly ? null : () {
        if (!controller.selection.isValid) {
          controller.selection = TextSelection.collapsed(offset: controller.text.length);
        }
        _requestKeyboard();
      },
      onDidGainAccessibilityFocus: handleDidGainAccessibilityFocus,
      child: IgnorePointer(
        ignoring: !enabled,
        child: Container(
          decoration: effectiveDecoration,
          color: !enabled && effectiveDecoration == null ? disabledColor : null,
          child: _selectionGestureDetectorBuilder.buildGestureDetector(
            behavior: HitTestBehavior.translucent,
            child: Align(
              alignment: Alignment(-1.0, _textAlignVertical.y),
              widthFactor: 1.0,
              heightFactor: 1.0,
              child: _addTextDependentAttachments(paddedEditable, textStyle, placeholderStyle),
            ),
          ),
        ),
      ),
    );

该build方法中使用了_selectionGestureDetectorBuilder.buildGestureDetector方法,这里是一个手势识别器。也就是这个方法里面监听处理鼠标点击(Web中使用)和点击手势。

  Widget buildGestureDetector({
    Key? key,
    HitTestBehavior? behavior,
    required Widget child,
  }) {
    return TextSelectionGestureDetector(
      key: key,
      onTapDown: onTapDown,
      onForcePressStart: delegate.forcePressEnabled ? onForcePressStart : null,
      onForcePressEnd: delegate.forcePressEnabled ? onForcePressEnd : null,
      onSecondaryTap: onSecondaryTap,
      onSecondaryTapDown: onSecondaryTapDown,
      onSingleTapUp: onSingleTapUp,
      onSingleTapCancel: onSingleTapCancel,
      onSingleLongTapStart: onSingleLongTapStart,
      onSingleLongTapMoveUpdate: onSingleLongTapMoveUpdate,
      onSingleLongTapEnd: onSingleLongTapEnd,
      onDoubleTapDown: onDoubleTapDown,
      onDragSelectionStart: onDragSelectionStart,
      onDragSelectionUpdate: onDragSelectionUpdate,
      onDragSelectionEnd: onDragSelectionEnd,
      behavior: behavior,
      child: child,
    );
  }

这里我们重点查看单击onSingleTapUp方法。

 void onSingleTapUp(TapUpDetails details) {
    if (_isShiftTapping) {
      _isShiftTapping = false;
      return;
    }

    if (delegate.selectionEnabled) {
      switch (defaultTargetPlatform) {
        case TargetPlatform.iOS:
        case TargetPlatform.macOS:
          switch (details.kind) {
            case PointerDeviceKind.mouse:
            case PointerDeviceKind.stylus:
            case PointerDeviceKind.invertedStylus:
              // Precise devices should place the cursor at a precise position.
              renderEditable.selectPosition(cause: SelectionChangedCause.tap);
              break;
            case PointerDeviceKind.touch:
            case PointerDeviceKind.unknown:
              // On macOS/iOS/iPadOS a touch tap places the cursor at the edge
              // of the word.
              renderEditable.selectWordEdge(cause: SelectionChangedCause.tap);
              break;
          }
          break;
        case TargetPlatform.android:
        case TargetPlatform.fuchsia:
        case TargetPlatform.linux:
        case TargetPlatform.windows:
          renderEditable.selectPosition(cause: SelectionChangedCause.tap);
          break;
      }
    }
  }

重点方法   renderEditable.selectPosition(cause: SelectionChangedCause.tap);

  void selectPosition({ required SelectionChangedCause cause }) {
    selectPositionAt(from: _lastTapDownPosition!, cause: cause);
  }

  /// Select text between the global positions [from] and [to].
  ///
  /// [from] corresponds to the [TextSelection.baseOffset], and [to] corresponds
  /// to the [TextSelection.extentOffset].
  void selectPositionAt({ required Offset from, Offset? to, required SelectionChangedCause cause }) {
    assert(cause != null);
    assert(from != null);
    _layoutText(minWidth: constraints.minWidth, maxWidth: constraints.maxWidth);
    final TextPosition fromPosition = _textPainter.getPositionForOffset(globalToLocal(from - _paintOffset));
    final TextPosition? toPosition = to == null
      ? null
      : _textPainter.getPositionForOffset(globalToLocal(to - _paintOffset));

    final int baseOffset = fromPosition.offset;
    final int extentOffset = toPosition?.offset ?? fromPosition.offset;

    final TextSelection newSelection = TextSelection(
      baseOffset: baseOffset,
      extentOffset: extentOffset,
      affinity: fromPosition.affinity,
    );
    _setSelection(newSelection, cause);
  }
selectPositionAt 这个方法中,通过点击位置计算判断光标应该显示在文本第几个字符。

细节:

final TextPosition fromPosition = _textPainter.getPositionForOffset(globalToLocal(from - _paintOffset));

这里用到了TextPainer.getPositionForOffset方法

  /// Returns the position within the text for the given pixel offset.
  TextPosition getPositionForOffset(Offset offset) {
    assert(!_debugNeedsLayout);
    return _paragraph!.getPositionForOffset(offset);
  }

这个方法就是根据传入的offset计算在字符第几个字符中。

计算好后生成一个TextSelection对象,传递给 RenderEditable

final TextSelection newSelection = TextSelection(
  baseOffset: baseOffset,
  extentOffset: extentOffset,
  affinity: fromPosition.affinity,
);

class _Editable extends MultiChildRenderObjectWidget {}

_Editable 也就是输入框重点的RenderObject,主要负责绘制工作

  @override
  void updateRenderObject(BuildContext context, RenderEditable renderObject) {
    renderObject
      ///省略
      ..selection = value.selection
      ///省略
  }

 这里的value.selection 也就是上面的TextSelection对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值