Flutter - как получить координаты курсора в текстовом поле?

Необходимо знать координаты dx и dy текущей позиции курсора в TextField. Это требуется для реализации функций упоминаний / тегов, при которых всплывающее окно должно отображаться на несколько пикселей ниже курсора TextField.


person Anirudh Agarwal    schedule 09.12.2019    source источник


Ответы (3)


Вы можете использовать FocusNode, чтобы получить смещение самого текстового поля. Затем используйте класс TextPainter для расчета ширины макета, как показано в этом post и используйте его для размещения своего тега. Затем, возможно, используйте некоторую логику наложения, чтобы показать тег, как показано здесь.

  1. Создайте объект FocusNode и прикрепите его к текстовому полю.
  2. Затем либо в обратном вызове onChanged, либо в обратном вызове его TextEditingController продолжайте логику позиционирования тега с помощью FocusNode.offset.dx и FocusNode.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,
        ),
      ),
    );
  }
}

Снимок экрана, как это выглядит, показан ниже. Вам нужно будет настроить положение в соответствии с вашими потребностями, а также логику продолжительности / видимости наложения, если вы собираетесь его использовать.

введите описание изображения здесь

person Abhilash Chandran    schedule 25.01.2020

Чтобы получить координаты текущего курсора (также называемого кареткой) в текстовом поле во флаттере, я думаю, вы можете использовать TextPainter› getOffsetForCaret, который возвращает смещение, на котором нужно нарисовать курсор. Затем из смещения вы можете получить компоненты x и y каретки.

Обратите внимание на xCarret, yCarret в приведенном ниже коде, которые соответствуют верхней левой координате курсора на экране. Вы можете определить позицию yCarretBottom, добавив preferredLineHeight к yCarret.

Метод getOffsetForCaret нуждается в caretPrototype, который мы создали с Rect.fromLTWH, и шириной курсора, заданной свойством cursorWidth TextField.

Пример флаттер-каретки


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),
            ),
          ]),
    );
  }
}
person Adrien Arcuri    schedule 28.06.2021

Простой способ получить текущую позицию курсора поля ввода или знака вставки

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