Handles hidden by keyboard (#32838)
Extra space when scrolling to selected input so that the selection caret is visible.
diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart
index 1c23e12..d1cf8af 100644
--- a/packages/flutter/lib/src/widgets/editable_text.dart
+++ b/packages/flutter/lib/src/widgets/editable_text.dart
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
+import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
@@ -1120,10 +1121,11 @@
double scrollOffset = _scrollController.offset;
final double viewportExtent = _scrollController.position.viewportDimension;
- if (caretStart < 0.0) // cursor before start of bounds
+ if (caretStart < 0.0) { // cursor before start of bounds
scrollOffset += caretStart;
- else if (caretEnd >= viewportExtent) // cursor after end of bounds
+ } else if (caretEnd >= viewportExtent) { // cursor after end of bounds
scrollOffset += caretEnd - viewportExtent;
+ }
return scrollOffset;
}
@@ -1271,12 +1273,32 @@
curve: _caretAnimationCurve,
);
final Rect newCaretRect = _getCaretRectAtScrollOffset(_currentCaretRect, scrollOffsetForCaret);
- // Enlarge newCaretRect by scrollPadding to ensure that caret is not positioned directly at the edge after scrolling.
+ // Enlarge newCaretRect by scrollPadding to ensure that caret is not
+ // positioned directly at the edge after scrolling.
+ double bottomSpacing = widget.scrollPadding.bottom;
+ if (_selectionOverlay?.selectionControls != null) {
+ final double handleHeight = _selectionOverlay.selectionControls
+ .getHandleSize(renderEditable.preferredLineHeight).height;
+ final double interactiveHandleHeight = math.max(
+ handleHeight,
+ kMinInteractiveSize,
+ );
+ final Offset anchor = _selectionOverlay.selectionControls
+ .getHandleAnchor(
+ TextSelectionHandleType.collapsed,
+ renderEditable.preferredLineHeight,
+ );
+ final double handleCenter = handleHeight / 2 - anchor.dy;
+ bottomSpacing = math.max(
+ handleCenter + interactiveHandleHeight / 2,
+ bottomSpacing,
+ );
+ }
final Rect inflatedRect = Rect.fromLTRB(
newCaretRect.left - widget.scrollPadding.left,
newCaretRect.top - widget.scrollPadding.top,
newCaretRect.right + widget.scrollPadding.right,
- newCaretRect.bottom + widget.scrollPadding.bottom,
+ newCaretRect.bottom + bottomSpacing,
);
_editableKey.currentContext.findRenderObject().showOnScreen(
rect: inflatedRect,
diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart
index 81bf06e..2989b48 100644
--- a/packages/flutter/test/cupertino/text_field_test.dart
+++ b/packages/flutter/test/cupertino/text_field_test.dart
@@ -2626,6 +2626,34 @@
debugDefaultTargetPlatformOverride = null;
});
+ testWidgets('when CupertinoTextField would be blocked by keyboard, it is shown with enough space for the selection handle', (WidgetTester tester) async {
+ final ScrollController scrollController = ScrollController();
+ final TextEditingController controller = TextEditingController();
+
+ await tester.pumpWidget(CupertinoApp(
+ theme: const CupertinoThemeData(),
+ home: Center(
+ child: ListView(
+ controller: scrollController,
+ children: <Widget>[
+ Container(height: 585), // Push field almost off screen.
+ CupertinoTextField(controller: controller),
+ Container(height: 1000),
+ ],
+ ),
+ ),
+ ));
+
+ // Tap the TextField to put the cursor into it and bring it into view.
+ expect(scrollController.offset, 0.0);
+ await tester.tap(find.byType(CupertinoTextField));
+ await tester.pumpAndSettle();
+
+ // The ListView has scrolled to keep the TextField and cursor handle
+ // visible.
+ expect(scrollController.offset, 26.0);
+ });
+
testWidgets('disabled state golden', (WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart
index 8931b4e..22fede4 100644
--- a/packages/flutter/test/material/text_field_test.dart
+++ b/packages/flutter/test/material/text_field_test.dart
@@ -6581,4 +6581,34 @@
await tester.tapAt(handlePos, pointer: 7);
expect(editableText.selectionOverlay.toolbarIsVisible, isFalse);
});
+
+ testWidgets('when TextField would be blocked by keyboard, it is shown with enough space for the selection handle', (WidgetTester tester) async {
+ final ScrollController scrollController = ScrollController();
+ final TextEditingController controller = TextEditingController();
+
+ await tester.pumpWidget(MaterialApp(
+ theme: ThemeData(),
+ home: Scaffold(
+ body: Center(
+ child: ListView(
+ controller: scrollController,
+ children: <Widget>[
+ Container(height: 579), // Push field almost off screen.
+ TextField(controller: controller),
+ Container(height: 1000),
+ ],
+ ),
+ ),
+ ),
+ ));
+
+ // Tap the TextField to put the cursor into it and bring it into view.
+ expect(scrollController.offset, 0.0);
+ await tester.tap(find.byType(TextField));
+ await tester.pumpAndSettle();
+
+ // The ListView has scrolled to keep the TextField and cursor handle
+ // visible.
+ expect(scrollController.offset, 44.0);
+ });
}