Skip to content

Commit cd77140

Browse files
authored
TextField and last input character should visible on the screen when the cursor is not shown (flutter#74722)
1 parent 791edc2 commit cd77140

File tree

2 files changed

+66
-11
lines changed

2 files changed

+66
-11
lines changed

packages/flutter/lib/src/rendering/editable.dart

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
383383
TextSelectionDelegate textSelectionDelegate;
384384

385385
Rect? _lastCaretRect;
386+
late Rect _currentCaretRect;
386387

387388
/// Track whether position of the start of the selected text is within the viewport.
388389
///
@@ -2124,7 +2125,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
21242125
return Offset(pixelPerfectOffsetX, pixelPerfectOffsetY);
21252126
}
21262127

2127-
void _paintCaret(Canvas canvas, Offset effectiveOffset, TextPosition textPosition) {
2128+
void _paintCaretIfNeeded(Canvas canvas, Offset effectiveOffset, TextPosition textPosition) {
21282129
assert(_textLayoutLastMaxWidth == constraints.maxWidth &&
21292130
_textLayoutLastMinWidth == constraints.minWidth,
21302131
'Last width ($_textLayoutLastMinWidth, $_textLayoutLastMaxWidth) not the same as max width constraint (${constraints.minWidth}, ${constraints.maxWidth}).');
@@ -2171,18 +2172,24 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
21712172
}
21722173

21732174
caretRect = caretRect.shift(_getPixelPerfectCursorOffset(caretRect));
2175+
_currentCaretRect = caretRect;
2176+
2177+
if (!_showCursor.value)
2178+
return;
21742179

21752180
if (cursorRadius == null) {
21762181
canvas.drawRect(caretRect, paint);
21772182
} else {
21782183
final RRect caretRRect = RRect.fromRectAndRadius(caretRect, cursorRadius!);
21792184
canvas.drawRRect(caretRRect, paint);
21802185
}
2186+
}
21812187

2182-
if (caretRect != _lastCaretRect) {
2183-
_lastCaretRect = caretRect;
2188+
void _updateCaretRect() {
2189+
if (_currentCaretRect != _lastCaretRect) {
2190+
_lastCaretRect = _currentCaretRect;
21842191
if (onCaretChanged != null)
2185-
onCaretChanged!(caretRect);
2192+
onCaretChanged!(_currentCaretRect);
21862193
}
21872194
}
21882195

@@ -2333,11 +2340,11 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
23332340
final Offset effectiveOffset = offset + _paintOffset;
23342341

23352342
bool showSelection = false;
2336-
bool showCaret = false;
2343+
bool canShowCaret = false;
23372344

23382345
if (selection != null && !_floatingCursorOn) {
2339-
if (selection!.isCollapsed && _showCursor.value && cursorColor != null)
2340-
showCaret = true;
2346+
if (selection!.isCollapsed && cursorColor != null)
2347+
canShowCaret = true;
23412348
else if (!selection!.isCollapsed && _selectionColor != null)
23422349
showSelection = true;
23432350
_updateSelectionExtentsVisibility(effectiveOffset);
@@ -2356,17 +2363,20 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
23562363
if (paintCursorAboveText)
23572364
_textPainter.paint(context.canvas, effectiveOffset);
23582365

2359-
if (showCaret) {
2366+
if (canShowCaret) {
23602367
assert(selection != null);
2361-
_paintCaret(context.canvas, effectiveOffset, selection!.extent);
2368+
_paintCaretIfNeeded(context.canvas, effectiveOffset, selection!.extent);
2369+
_updateCaretRect();
23622370
}
23632371

23642372
if (!paintCursorAboveText)
23652373
_textPainter.paint(context.canvas, effectiveOffset);
23662374

23672375
if (_floatingCursorOn) {
2368-
if (_resetFloatingCursorAnimationValue == null)
2369-
_paintCaret(context.canvas, effectiveOffset, _floatingCursorTextPosition);
2376+
if (_resetFloatingCursorAnimationValue == null) {
2377+
_paintCaretIfNeeded(context.canvas, effectiveOffset, _floatingCursorTextPosition);
2378+
_updateCaretRect();
2379+
}
23702380
_paintFloatingCaret(context.canvas, _floatingCursorOffset);
23712381
}
23722382
}

packages/flutter/test/material/text_field_test.dart

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8574,6 +8574,51 @@ void main() {
85748574
expect(scrollController.offset, 48.0);
85758575
});
85768576

8577+
// Regression test for https://github.com/flutter/flutter/issues/74566
8578+
testWidgets('TextField and last input character are visible on the screen when the cursor is not shown', (WidgetTester tester) async {
8579+
final ScrollController scrollController = ScrollController();
8580+
final ScrollController textFieldScrollController = ScrollController();
8581+
8582+
await tester.pumpWidget(MaterialApp(
8583+
theme: ThemeData(),
8584+
home: Scaffold(
8585+
body: Center(
8586+
child: ListView(
8587+
controller: scrollController,
8588+
children: <Widget>[
8589+
Container(height: 579), // Push field almost off screen.
8590+
TextField(
8591+
scrollController: textFieldScrollController,
8592+
showCursor: false,
8593+
),
8594+
Container(height: 1000),
8595+
],
8596+
),
8597+
),
8598+
),
8599+
));
8600+
8601+
// Tap the TextField to bring it into view.
8602+
expect(scrollController.offset, 0.0);
8603+
await tester.tapAt(tester.getTopLeft(find.byType(TextField)));
8604+
await tester.pumpAndSettle();
8605+
8606+
// The ListView has scrolled to keep the TextField visible.
8607+
expect(scrollController.offset, 48.0);
8608+
expect(textFieldScrollController.offset, 0.0);
8609+
8610+
// After entering some long text, the last input character remains on the screen.
8611+
final String testValue = 'I love Flutter!' * 10;
8612+
tester.testTextInput.updateEditingValue(TextEditingValue(
8613+
text: testValue,
8614+
selection: TextSelection.collapsed(offset: testValue.length),
8615+
));
8616+
await tester.pump();
8617+
await tester.pumpAndSettle(); // Text scroll animation.
8618+
8619+
expect(textFieldScrollController.offset, 1602.0);
8620+
});
8621+
85778622
group('height', () {
85788623
testWidgets('By default, TextField is at least kMinInteractiveDimension high', (WidgetTester tester) async {
85798624
await tester.pumpWidget(MaterialApp(

0 commit comments

Comments
 (0)