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.
To change TextField cursor color in Flutter, simply add the cursorColor property and set the color of your choice.
To listen to focus change, you can add a listner to the FocusNode and specify the focusNode to TextField . This gist represents how to ensure a focused node to be visible on the ui. Hope it helps!
TextEditingController class Null safety. A controller for an editable text field. Whenever the user modifies a text field with an associated TextEditingController, the text field updates value and the controller notifies its listeners.
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.
FocusNode
object and attach it to the text field.onChanged
callback or its TextEditingController
's call back proceed with the logic to position your tag using the FocusNode.offset.dx
and FocusNode.offset.dy
.FocusNode
only provides the bounding rect offset. So you will need a TextPainter
instance to calculate the width of the newly entered text. for this you will need TextStyle
defined up ahead.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.
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),
),
]),
);
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With