Defer the animation of text fields to the caret position (#10782)
Fixes https://github.com/flutter/flutter/issues/10681
diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart
index a4c8e7d..eb08c06 100644
--- a/packages/flutter/lib/src/widgets/editable_text.dart
+++ b/packages/flutter/lib/src/widgets/editable_text.dart
@@ -460,11 +460,13 @@
// selection, then scroll the caret into view.
if (_textChangedSinceLastCaretUpdate) {
_textChangedSinceLastCaretUpdate = false;
- _scrollController.animateTo(
- _getScrollOffsetForCaret(caretRect),
- curve: Curves.fastOutSlowIn,
- duration: const Duration(milliseconds: 50),
- );
+ scheduleMicrotask(() {
+ _scrollController.animateTo(
+ _getScrollOffsetForCaret(caretRect),
+ curve: Curves.fastOutSlowIn,
+ duration: const Duration(milliseconds: 50),
+ );
+ });
}
}
diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart
index 7591c5c..f9ea072 100644
--- a/packages/flutter/test/material/text_field_test.dart
+++ b/packages/flutter/test/material/text_field_test.dart
@@ -38,6 +38,11 @@
);
}
+Future<Null> skipPastScrollingAnimation(WidgetTester tester) async {
+ await tester.pump();
+ await tester.pump(const Duration(milliseconds: 200));
+}
+
void main() {
final MockClipboard mockClipboard = new MockClipboard();
SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall);
@@ -112,8 +117,7 @@
expect(textFieldValue, equals(testValue));
await tester.pumpWidget(builder());
- // skip past scrolling animation
- await tester.pump(const Duration(milliseconds: 200));
+ await skipPastScrollingAnimation(tester);
});
}
@@ -219,8 +223,7 @@
await tester.enterText(find.byType(TextField), testValue);
await tester.pumpWidget(builder());
- // skip past scrolling animation
- await tester.pump(const Duration(milliseconds: 200));
+ await skipPastScrollingAnimation(tester);
// Tap to reposition the caret.
final int tapIndex = testValue.indexOf('e');
@@ -263,8 +266,7 @@
expect(controller.value.text, testValue);
await tester.pumpWidget(builder());
- // skip past scrolling animation
- await tester.pump(const Duration(milliseconds: 200));
+ await skipPastScrollingAnimation(tester);
expect(controller.selection.isCollapsed, true);
@@ -299,8 +301,7 @@
await tester.enterText(find.byType(TextField), testValue);
await tester.pumpWidget(builder());
- // skip past scrolling animation
- await tester.pump(const Duration(milliseconds: 200));
+ await skipPastScrollingAnimation(tester);
// Long press the 'e' to select 'def'.
final Offset ePos = textOffsetToPosition(tester, testValue.indexOf('e'));
@@ -362,8 +363,7 @@
final String testValue = 'abc def ghi';
await tester.enterText(find.byType(TextField), testValue);
await tester.pumpWidget(builder());
- // skip past scrolling animation
- await tester.pump(const Duration(milliseconds: 200));
+ await skipPastScrollingAnimation(tester);
// Tap the selection handle to bring up the "paste / select all" menu.
await tester.tapAt(textOffsetToPosition(tester, testValue.indexOf('e')));
@@ -382,8 +382,7 @@
// COPY should reset the selection.
await tester.tap(find.text('COPY'));
await tester.pumpWidget(builder());
- // skip past scrolling animation
- await tester.pump(const Duration(milliseconds: 200));
+ await skipPastScrollingAnimation(tester);
expect(controller.selection.isCollapsed, true);
// Tap again to bring back the menu.
@@ -418,8 +417,7 @@
final String testValue = 'abc def ghi';
await tester.enterText(find.byType(TextField), testValue);
await tester.pumpWidget(builder());
- // skip past scrolling animation
- await tester.pump(const Duration(milliseconds: 200));
+ await skipPastScrollingAnimation(tester);
// Tap the selection handle to bring up the "paste / select all" menu.
await tester.tapAt(textOffsetToPosition(tester, testValue.indexOf('e')));
@@ -532,8 +530,7 @@
await tester.enterText(find.byType(TextField), testValue);
await tester.pumpWidget(builder());
- // skip past scrolling animation
- await tester.pump(const Duration(milliseconds: 200));
+ await skipPastScrollingAnimation(tester);
// Check that the text spans multiple lines.
final Offset firstPos = textOffsetToPosition(tester, testValue.indexOf('First'));
@@ -1340,8 +1337,7 @@
await tester.enterText(find.byType(TextField), 'a1b\n2c3');
expect(textController.text, '123');
await tester.pumpWidget(builder());
- // skip past scrolling animation
- await tester.pump(const Duration(milliseconds: 200));
+ await skipPastScrollingAnimation(tester);
await tester.tapAt(textOffsetToPosition(tester, '123'.indexOf('2')));
await tester.pumpWidget(builder());
@@ -1382,8 +1378,7 @@
final String longText = 'a' * 20;
await tester.enterText(find.byType(TextField), longText);
await tester.pumpWidget(builder());
- // skip past scrolling animation
- await tester.pump(const Duration(milliseconds: 200));
+ await skipPastScrollingAnimation(tester);
ScrollableState scrollableState = tester.firstState(find.byType(Scrollable));
expect(scrollableState.position.pixels, equals(0.0));
@@ -1392,8 +1387,7 @@
// scrolls to make the caret visible.
controller.selection = new TextSelection.collapsed(offset: longText.length);
await tester.pumpWidget(builder());
- // skip past scrolling animation
- await tester.pump(const Duration(milliseconds: 200));
+ await skipPastScrollingAnimation(tester);
scrollableState = tester.firstState(find.byType(Scrollable));
expect(scrollableState.position.pixels, isNot(equals(0.0)));