Commit a14b4f62 authored by Norbert Kozsir's avatar Norbert Kozsir Committed by Michael Goderbauer

Making the space between keyboard and text-field when the keyboard pops up a variable. (#18562)

parent f0639bb0
...@@ -118,6 +118,7 @@ class TextField extends StatefulWidget { ...@@ -118,6 +118,7 @@ class TextField extends StatefulWidget {
this.inputFormatters, this.inputFormatters,
this.enabled, this.enabled,
this.keyboardAppearance, this.keyboardAppearance,
this.scrollPadding = const EdgeInsets.all(20.0),
}) : assert(keyboardType != null), }) : assert(keyboardType != null),
assert(textInputAction != null), assert(textInputAction != null),
assert(textAlign != null), assert(textAlign != null),
...@@ -125,6 +126,7 @@ class TextField extends StatefulWidget { ...@@ -125,6 +126,7 @@ class TextField extends StatefulWidget {
assert(obscureText != null), assert(obscureText != null),
assert(autocorrect != null), assert(autocorrect != null),
assert(maxLengthEnforced != null), assert(maxLengthEnforced != null),
assert(scrollPadding != null),
assert(maxLines == null || maxLines > 0), assert(maxLines == null || maxLines > 0),
assert(maxLength == null || maxLength > 0), assert(maxLength == null || maxLength > 0),
keyboardType = maxLines == 1 ? keyboardType : TextInputType.multiline, keyboardType = maxLines == 1 ? keyboardType : TextInputType.multiline,
...@@ -319,6 +321,16 @@ class TextField extends StatefulWidget { ...@@ -319,6 +321,16 @@ class TextField extends StatefulWidget {
/// If unset, defaults to the brightness of [ThemeData.primaryColorBrightness]. /// If unset, defaults to the brightness of [ThemeData.primaryColorBrightness].
final Brightness keyboardAppearance; final Brightness keyboardAppearance;
/// Configures padding to edges surrounding a [Scrollable] when the Textfield scrolls into view.
///
/// When this widget receives focus and is not completely visible (for example scrolled partially
/// off the screen or overlapped by the keyboard)
/// then it will attempt to make itself visible by scrolling a surrounding [Scrollable], if one is present.
/// This value controls how far from the edges of a [Scrollable] the TextField will be positioned after the scroll.
///
/// Defaults to EdgeInserts.all(20.0).
final EdgeInsets scrollPadding;
@override @override
_TextFieldState createState() => new _TextFieldState(); _TextFieldState createState() => new _TextFieldState();
...@@ -541,6 +553,7 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi ...@@ -541,6 +553,7 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
onSelectionChanged: _handleSelectionChanged, onSelectionChanged: _handleSelectionChanged,
inputFormatters: formatters, inputFormatters: formatters,
rendererIgnoresPointer: true, rendererIgnoresPointer: true,
scrollPadding: widget.scrollPadding,
keyboardAppearance: keyboardAppearance, keyboardAppearance: keyboardAppearance,
), ),
); );
......
...@@ -73,6 +73,7 @@ class TextFormField extends FormField<String> { ...@@ -73,6 +73,7 @@ class TextFormField extends FormField<String> {
List<TextInputFormatter> inputFormatters, List<TextInputFormatter> inputFormatters,
bool enabled, bool enabled,
Brightness keyboardAppearance, Brightness keyboardAppearance,
EdgeInsets scrollPadding = const EdgeInsets.all(20.0),
}) : assert(initialValue == null || controller == null), }) : assert(initialValue == null || controller == null),
assert(keyboardType != null), assert(keyboardType != null),
assert(textInputAction != null), assert(textInputAction != null),
...@@ -82,6 +83,7 @@ class TextFormField extends FormField<String> { ...@@ -82,6 +83,7 @@ class TextFormField extends FormField<String> {
assert(autocorrect != null), assert(autocorrect != null),
assert(autovalidate != null), assert(autovalidate != null),
assert(maxLengthEnforced != null), assert(maxLengthEnforced != null),
assert(scrollPadding != null),
assert(maxLines == null || maxLines > 0), assert(maxLines == null || maxLines > 0),
assert(maxLength == null || maxLength > 0), assert(maxLength == null || maxLength > 0),
super( super(
...@@ -114,6 +116,7 @@ class TextFormField extends FormField<String> { ...@@ -114,6 +116,7 @@ class TextFormField extends FormField<String> {
onSubmitted: onFieldSubmitted, onSubmitted: onFieldSubmitted,
inputFormatters: inputFormatters, inputFormatters: inputFormatters,
enabled: enabled, enabled: enabled,
scrollPadding: scrollPadding,
keyboardAppearance: keyboardAppearance, keyboardAppearance: keyboardAppearance,
); );
}, },
......
...@@ -211,6 +211,7 @@ class EditableText extends StatefulWidget { ...@@ -211,6 +211,7 @@ class EditableText extends StatefulWidget {
this.rendererIgnoresPointer = false, this.rendererIgnoresPointer = false,
this.cursorWidth = 1.0, this.cursorWidth = 1.0,
this.cursorRadius, this.cursorRadius,
this.scrollPadding = const EdgeInsets.all(20.0),
this.keyboardAppearance = Brightness.light, this.keyboardAppearance = Brightness.light,
}) : assert(controller != null), }) : assert(controller != null),
assert(focusNode != null), assert(focusNode != null),
...@@ -222,6 +223,7 @@ class EditableText extends StatefulWidget { ...@@ -222,6 +223,7 @@ class EditableText extends StatefulWidget {
assert(maxLines == null || maxLines > 0), assert(maxLines == null || maxLines > 0),
assert(autofocus != null), assert(autofocus != null),
assert(rendererIgnoresPointer != null), assert(rendererIgnoresPointer != null),
assert(scrollPadding != null),
keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline), keyboardType = keyboardType ?? (maxLines == 1 ? TextInputType.text : TextInputType.multiline),
inputFormatters = maxLines == 1 inputFormatters = maxLines == 1
? ( ? (
...@@ -387,6 +389,16 @@ class EditableText extends StatefulWidget { ...@@ -387,6 +389,16 @@ class EditableText extends StatefulWidget {
/// Defaults to [Brightness.light]. /// Defaults to [Brightness.light].
final Brightness keyboardAppearance; final Brightness keyboardAppearance;
/// Configures padding to edges surrounding a [Scrollable] when the Textfield scrolls into view.
///
/// When this widget receives focus and is not completely visible (for example scrolled partially
/// off the screen or overlapped by the keyboard)
/// then it will attempt to make itself visible by scrolling a surrounding [Scrollable], if one is present.
/// This value controls how far from the edges of a [Scrollable] the TextField will be positioned after the scroll.
///
/// Defaults to EdgeInserts.all(20.0).
final EdgeInsets scrollPadding;
@override @override
EditableTextState createState() => new EditableTextState(); EditableTextState createState() => new EditableTextState();
...@@ -579,7 +591,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -579,7 +591,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
inputType: widget.keyboardType, inputType: widget.keyboardType,
obscureText: widget.obscureText, obscureText: widget.obscureText,
autocorrect: widget.autocorrect, autocorrect: widget.autocorrect,
keyboardAppearance: widget.keyboardAppearance,
inputAction: widget.keyboardType == TextInputType.multiline inputAction: widget.keyboardType == TextInputType.multiline
? TextInputAction.newline ? TextInputAction.newline
: widget.textInputAction, : widget.textInputAction,
...@@ -702,9 +713,15 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -702,9 +713,15 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
curve: _caretAnimationCurve, curve: _caretAnimationCurve,
); );
final Rect newCaretRect = _getCaretRectAtScrollOffset(_currentCaretRect, scrollOffsetForCaret); final Rect newCaretRect = _getCaretRectAtScrollOffset(_currentCaretRect, scrollOffsetForCaret);
// Enlarge newCaretRect by scrollPadding to ensure that caret is not positioned directly at the edge after scrolling.
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
);
_editableKey.currentContext.findRenderObject().showOnScreen( _editableKey.currentContext.findRenderObject().showOnScreen(
// Inflate ensures that caret is not positioned directly at the edge. rect: inflatedRect,
rect: newCaretRect.inflate(20.0),
duration: _caretAnimationDuration, duration: _caretAnimationDuration,
curve: _caretAnimationCurve, curve: _caretAnimationCurve,
); );
......
...@@ -51,6 +51,51 @@ void main() { ...@@ -51,6 +51,51 @@ void main() {
expect(scrollController.offset, 0.0); expect(scrollController.offset, 0.0);
}); });
testWidgets('tapping on a partly visible editable brings it fully on screen with scrollInsets', (WidgetTester tester) async {
final ScrollController scrollController = new ScrollController();
final TextEditingController controller = new TextEditingController();
final FocusNode focusNode = new FocusNode();
await tester.pumpWidget(new MaterialApp(
home: new Center(
child: new Container(
height: 300.0,
child: new ListView(
controller: scrollController,
children: <Widget>[
new Container(
height: 200.0,
),
new EditableText(
scrollPadding: const EdgeInsets.all(50.0),
controller: controller,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
),
new Container(
height: 850.0,
),
],
),
),
),
));
// Scroll the EditableText half off screen.
final RenderBox render = tester.renderObject(find.byType(EditableText));
scrollController.jumpTo(200 + render.size.height / 2);
await tester.pumpAndSettle();
expect(scrollController.offset, 200 + render.size.height / 2);
await tester.showKeyboard(find.byType(EditableText));
await tester.pumpAndSettle();
// Container above the text is 200 in height, the scrollInsets are 50
// Tolerance of 5 units (The actual value was 152.0 in the current tests instead of 150.0)
expect(scrollController.offset, lessThan(200.0 - 50.0 + 5.0));
expect(scrollController.offset, greaterThan(200.0 - 50.0 - 5.0));
});
testWidgets('editable comes back on screen when entering text while it is off-screen', (WidgetTester tester) async { testWidgets('editable comes back on screen when entering text while it is off-screen', (WidgetTester tester) async {
final ScrollController scrollController = new ScrollController(initialScrollOffset: 100.0); final ScrollController scrollController = new ScrollController(initialScrollOffset: 100.0);
final TextEditingController controller = new TextEditingController(); final TextEditingController controller = new TextEditingController();
...@@ -145,4 +190,47 @@ void main() { ...@@ -145,4 +190,47 @@ void main() {
expect(render.size.height, greaterThan(500.0)); expect(render.size.height, greaterThan(500.0));
expect(scrollController.offset, greaterThan(0.0)); expect(scrollController.offset, greaterThan(0.0));
}); });
}
testWidgets('scrolls into view with scrollInserts after the keyboard pops up', (WidgetTester tester) async {
final ScrollController scrollController = new ScrollController();
final TextEditingController controller = new TextEditingController();
final FocusNode focusNode = new FocusNode();
const Key container = const Key('container');
await tester.pumpWidget(new MaterialApp(
home: new Align(
alignment: Alignment.bottomCenter,
child: new Container(
height: 300.0,
child: new ListView(
controller: scrollController,
children: <Widget>[
new Container(
key: container,
height: 200.0,
),
new EditableText(
scrollPadding: const EdgeInsets.only(bottom: 300.0),
controller: controller,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
),
new Container(
height: 400.0,
),
],
),
),
),
));
expect(scrollController.offset, 0.0);
await tester.showKeyboard(find.byType(EditableText));
await tester.pumpAndSettle();
expect(scrollController.offset, greaterThan(0.0));
expect(find.byKey(container), findsNothing);
});
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment