In Flutter, the “cursor” you see when typing into a TextField (or any other text input widget) is actually drawn (painted) by a lower-level rendering object called RenderEditable, which lives inside an EditableText widget. Here is a conceptual overview of how it works:
-
TextField:
The high-level widget most developers use. It wraps lower-level widgets to handle text input, focus, styling, etc. -
EditableText:
The core widget responsible for text editing, text layout, and keyboard interactions. When you create aTextField
, Flutter internally creates anEditableText
to handle editing logic. -
RenderEditable:
TheRenderObject
forEditableText
. This is where most of the painting (drawing) logic for text, selection highlights, and the cursor actually happens.
Inside RenderEditable
, there is a function (or set of functions) that handles drawing the cursor, often referred to in code as the caret:
-
Calculating the caret (cursor) position
- When a user taps or types, Flutter calculates where the next character goes using text layout metrics.
RenderEditable
uses a text layout engine (TextPainter
) to figure out the coordinates of the glyph positions.- Once it knows the exact offset within the text where the cursor should appear, it saves that position for painting.
-
Blinking animation
- By default, Flutter animates the cursor to blink (show and hide).
- When the
EditableText
gains focus, a timer is set up to periodically toggle a boolean (e.g._showCursor
) on and off. - Every time
_showCursor
changes,RenderEditable
is told to repaint.
-
Painting the caret
- On every frame, if
_showCursor
is true, the render object calls an internal method (something like_paintCaret
) to draw the cursor. - This method uses a
Canvas
to paint a simple vertical line (or a rounded-rectangle if you set acursorRadius
) at the computed caret offset. - The width of that line is
cursorWidth
(usually 2.0 logical pixels by default), and the height generally matches the height of the current text line.
- On every frame, if
-
Customization
cursorColor
,cursorWidth
, andcursorRadius
can all be provided byEditableText
(and in turn byTextField
).- The blinking frequency is determined by
cursorBlinkInterval
(defaults to around 500ms).
While the actual Flutter engine code is quite extensive, a simplified flow might look like this:
class RenderEditable extends RenderBox {
bool _showCursor = false;
Offset _caretOffset;
// Called by Flutter when we need to paint the text field:
@override
void paint(PaintingContext context, Offset offset) {
// 1. Paint text content, selection highlights, etc.
// 2. Paint the cursor if needed
if (_showCursor) {
_paintCaret(context.canvas, offset + _caretOffset);
}
}
void _paintCaret(Canvas canvas, Offset caretOffset) {
final Paint paint = Paint()..color = cursorColor;
final double caretHeight = _computeCaretHeight();
final Rect caretRect = Rect.fromLTWH(
caretOffset.dx,
caretOffset.dy,
cursorWidth,
caretHeight,
);
if (cursorRadius != null) {
// Draw a rounded-rect caret
final RRect caretRRect = RRect.fromRectAndRadius(caretRect, cursorRadius);
canvas.drawRRect(caretRRect, paint);
} else {
// Draw a simple rectangle caret
canvas.drawRect(caretRect, paint);
}
}
}
Then, somewhere in EditableTextState
, a Ticker
or periodic callback toggles _showCursor
, causing repaints:
void _startCursorTimer() {
_cursorTimer = Timer.periodic(_cursorBlinkInterval, (Timer timer) {
setState(() {
_showCursor = !_showCursor;
});
});
}
- User focuses the TextField.
Flutter starts the cursor blink timer inEditableTextState
. RenderEditable
is marked for repaint regularly (for blinking).- When
_showCursor
is true,RenderEditable
calls_paintCaret
. - A small vertical rectangle (or rounded rectangle) is drawn at the correct offset where new text will appear.
- Cursor blinks by toggling
_showCursor
on each timer tick, thus showing and hiding the caret.
The cursor in a Flutter TextField
is essentially a blinking vertical line (or rectangle) painted by the RenderEditable
object inside the EditableText
widget. A timer toggles the cursor’s visibility, and the text layout system continuously updates its position based on user input and selection changes.
Key points:
- It’s drawn as part of the normal paint cycle for the text editing widget (
RenderEditable
). - Its blinking is controlled by a timer that triggers a rebuild/repaint every half-second (by default).
- You can customize its color, thickness, radius, and blink rate through the parameters on
TextField
orEditableText
.