Need to know the dx and dy coordinates of the current cursor position in the TextField. This is required to implement the mentions/tag functionality, wherein a popup needs to be shown a few pixel below the cursor of the TextField.
2 Answers
You can use the FocusNode
to gain the offset of the text field itself.Then use the TextPainter
class to calculated the layout width as shown in this post and use it to position your tag. Then perhaps use some overlay logic to show the tag as shown here.
- Create a
FocusNode
object and attach it to the text field. - Then either in
onChanged
callback or itsTextEditingController
's call back proceed with the logic to position your tag using theFocusNode.offset.dx
andFocusNode.offset.dy
. FocusNode
only provides the bounding rect offset. So you will need aTextPainter
instance to calculate the width of the newly entered text. for this you will needTextStyle
defined up ahead.- Using both the values from 2 and 3 calculate the position of your tag with some extra offset for visual aesthetics.
Following code is a sample using the above techniques. A live version of this solution is available in this 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,
),
),
);
}
}
A screen shot of how this looks like is shown below.You will have to adjust the position to suit your needs and also the duration / visibility logic of the overlay if you are going to use it.

- 6,803
- 3
- 32
- 50
-
could you expand your answer to work with multiline textfields? – Alb Dec 06 '21 at 10:08
-
The problem is where you don't press Enter then and write multiline and line break down to for example 5 lines the `painter.heigh` remain like height of 1 line. https://stackoverflow.com/questions/70748383/flutter-calculate-height-of-text-line-inside-text-filed @Abhilash Chandran – Cyrus the Great Jan 18 '22 at 06:21
-
This also only works if the caret is at the end of the text input. If you move the caret somewhere else, the overlay will still show at the end of the line. The correct answer is the one below: https://stackoverflow.com/a/68165106/3466729 – Gpack Aug 16 '23 at 02:01
To get the coordinates of the current cursor (also called caret) in a Textfield in flutter, I think you can use TextPainter > getOffsetForCaret method which return the offset at which to paint the caret. Then, from the offset you can get the The x and y component of the caret.
Observe the xCarret
, yCarret
in the code below which correspond to the top left coordinate of the cursor on the screen.
You can deduce the yCarretBottom
position by adding the preferredLineHeight
to yCarret
.
The method getOffsetForCaret
need a caretPrototype
which we made with Rect.fromLTWH
and the width of the cursor given by the property cursorWidth
of the 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),
),
]),
);
}
}

- 30,962
- 25
- 85
- 135

- 1,962
- 1
- 16
- 30
-
I implemented this and placed an `Icon` inside a `Positioned` inside a `Stack` to display something at the position of the cursor. Although your answer is the best performing answer so far, it is a bit flawed. After inserting more text, the `Icon` will be moving further right than the cursor is. – Alb Dec 01 '21 at 16:08
-
The problem is where you don't press Enter then and write multiline and line break down to for example 5 lines the painter.heigh remain like height of 1 line. @Adrien Arcuri – Cyrus the Great Jan 18 '22 at 06:22