Handles hidden by keyboard (#32838)

Extra space when scrolling to selected input so that the selection caret is visible.
This commit is contained in:
Justin McCandless 2019-06-04 08:37:29 -07:00 committed by GitHub
parent 04015b987b
commit fce54ae685
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 84 additions and 4 deletions

View file

@ -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 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
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 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
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,

View file

@ -2626,6 +2626,34 @@ void main() {
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(

View file

@ -6581,4 +6581,34 @@ void main() {
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);
});
}