Flutter-如何获取TextField中光标的坐标?

时间:2019-12-09 06:32:31

标签: flutter flutter-layout

需要知道TextField中当前光标位置的dx和dy坐标。这是实现提及/标记功能所必需的,其中需要在TextField光标下方的几个像素处显示一个弹出窗口。

3 个答案:

答案 0 :(得分:4)

您可以使用FocusNode来获取文本字段本身的偏移量。然后使用TextPainter类来计算布局宽度,如此post所示,并使用它来定位您的标签。然后也许使用一些叠加逻辑来显示标签,如图here所示。

  1. 创建一个FocusNode对象,并将其附加到文本字段。
  2. 然后在onChanged回调或其TextEditingController的回叫中,继续使用FocusNode.offset.dxFocusNode.offset.dy定位标签的逻辑。
  3. FocusNode仅提供边界矩形偏移量。因此,您将需要一个TextPainter实例来计算新输入文本的宽度。为此,您需要预先定义TextStyle
  4. 同时使用2和3中的值来计算标签的位置,并为视觉美观加上一些额外的偏移量。

以下代码是使用上述技术的示例。 dartpad上提供了该解决方案的实时版本。

// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter  Show Text Tag Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Show Text Tag demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  FocusNode _focusNode = FocusNode();
  GlobalKey _textFieldKey = GlobalKey();
  TextStyle _textFieldStyle = TextStyle(fontSize: 20);

  @override
  void initState() {
    super.initState();
  }

  // Code reference for overlay logic from MTECHVIRAL's video
  // https://www.youtube.com/watch?v=KuXKwjv2gTY

  showOverlaidTag(BuildContext context, String newText) async {

    TextPainter painter = TextPainter(
      textDirection: TextDirection.ltr,
      text: TextSpan(
        style: _textFieldStyle,
        text: newText,
      ),
    );
    painter.layout();


    OverlayState overlayState = Overlay.of(context);
    OverlayEntry suggestionTagoverlayEntry = OverlayEntry(builder: (context) {
      return Positioned(

        // Decides where to place the tag on the screen.
        top: _focusNode.offset.dy + painter.height + 3,
        left: _focusNode.offset.dx + painter.width + 10,

        // Tag code.
        child: Material(
            elevation: 4.0,
            color: Colors.lightBlueAccent,          
            child: Text(
              'Show tag here',
              style: TextStyle(
                fontSize: 20.0,
              ),
            )),
      );
    });
    overlayState.insert(suggestionTagoverlayEntry);

    // Removes the over lay entry from the Overly after 500 milliseconds 
    await Future.delayed(Duration(milliseconds: 500));
    suggestionTagoverlayEntry.remove();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Container(
          child: TextField(
            focusNode: _focusNode,
            key: _textFieldKey,
            style: _textFieldStyle,
            onChanged: (String nextText) {
              showOverlaidTag(context, nextText);
            },
          ),
          width: 400.0,
        ),
      ),
    );
  }
}

下面是它的外观的屏幕快照。如果要使用它,您将不得不调整位置以适合您的需要,以及覆盖的持续时间/可见性逻辑。

enter image description here

答案 1 :(得分:0)

获取输入字段光标或插入符号当前位置的简单方法

TextField(
  controller: _textController,
  onChanged: (value) {
    int cursorPos = _textController.selection.base.offset;
    print(cursorPos); // returns value current position where you just typed
  }
)

答案 2 :(得分:0)

获取当前光标的坐标(也称为 caret)在 Flutter 中的 Textfield,我认为您可以使用 TextPainter > getOffsetForCaret 方法返回偏移 绘制插入符号的位置。然后,您可以从偏移量中获得插入符号的 x 和 y 组件

观察下面代码中的xCarretyCarret,它们对应屏幕上光标的左上角坐标。 您可以通过将 yCarretBottom 添加到 preferredLineHeight 来推断 yCarret 位置。

方法getOffsetForCaret需要一个caretPrototype,我们用Rect.fromLTWHcursorWidth的属性TextField给定的光标宽度。< /p>

Flutter caret example


import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Get cursor (caret) position',
      debugShowCheckedModeBanner: false,
      home: MyHomePage(title: 'Get cursor (caret) position'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, this.title}) : super(key: key);

  final String? title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  GlobalKey _textFieldKey = GlobalKey();
  TextStyle _textFieldStyle = TextStyle(fontSize: 20);
  TextEditingController _textFieldController = TextEditingController();
  late TextField _textField;
  double xCaret = 0.0;
  double yCaret = 0.0;
  double painterWidth = 0.0;
  double painterHeight = 0.0;
  double preferredLineHeight = 0.0;

  @override
  void initState() {
    super.initState();

    /// Listen changes on your text field controller
    _textFieldController.addListener(() {
      _updateCaretOffset(_textFieldController.text);
    });
  }

  void _updateCaretOffset(String text) {
    TextPainter painter = TextPainter(
      textDirection: TextDirection.ltr,
      text: TextSpan(
        style: _textFieldStyle,
        text: text,
      ),
    );
    painter.layout();

    TextPosition cursorTextPosition = _textFieldController.selection.base;
    Rect caretPrototype = Rect.fromLTWH(
        0.0, 0.0, _textField.cursorWidth, _textField.cursorHeight ?? 0);
    Offset caretOffset =
        painter.getOffsetForCaret(cursorTextPosition, caretPrototype);
    setState(() {
      xCaret = caretOffset.dx;
      yCaret = caretOffset.dy;
      painterWidth = painter.width;
      painterHeight = painter.height;
      preferredLineHeight = painter.preferredLineHeight;
    });
  }

  @override
  Widget build(BuildContext context) {
    String text = '''
xCaret: $xCaret
yCaret: $yCaret
yCaretBottom: ${yCaret + preferredLineHeight}
''';

    _textField = TextField(
      controller: _textFieldController,
      keyboardType: TextInputType.multiline,
      key: _textFieldKey,
      style: _textFieldStyle,
      minLines: 1,
      maxLines: 2,
    );

    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title!),
      ),
      body: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            Text(text),
            Padding(
              child: _textField,
              padding: EdgeInsets.all(40),
            ),
          ]),
    );
  }
}