Skip to content

Commit dbbaf68

Browse files
authored
Fix: fix the delay of showOnScreen animation when keyboard comes up. (flutter#99546)
1 parent f1d8e30 commit dbbaf68

File tree

2 files changed

+91
-16
lines changed

2 files changed

+91
-16
lines changed

packages/flutter/lib/src/widgets/editable_text.dart

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1965,7 +1965,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
19651965
// to make sure the user can see the changes they just made. Programmatical
19661966
// changes to `textEditingValue` do not trigger the behavior even if the
19671967
// text field is focused.
1968-
_scheduleShowCaretOnScreen();
1968+
_scheduleShowCaretOnScreen(withAnimation: true);
19691969
if (_hasInputConnection) {
19701970
// To keep the cursor from blinking while typing, we want to restart the
19711971
// cursor timer every time a new character is typed.
@@ -2498,7 +2498,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
24982498

24992499
bool _showCaretOnScreenScheduled = false;
25002500

2501-
void _scheduleShowCaretOnScreen() {
2501+
void _scheduleShowCaretOnScreen({required bool withAnimation}) {
25022502
if (_showCaretOnScreenScheduled) {
25032503
return;
25042504
}
@@ -2538,17 +2538,23 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
25382538

25392539
final RevealedOffset targetOffset = _getOffsetToRevealCaret(_currentCaretRect!);
25402540

2541-
_scrollController.animateTo(
2542-
targetOffset.offset,
2543-
duration: _caretAnimationDuration,
2544-
curve: _caretAnimationCurve,
2545-
);
2546-
2547-
renderEditable.showOnScreen(
2548-
rect: caretPadding.inflateRect(targetOffset.rect),
2549-
duration: _caretAnimationDuration,
2550-
curve: _caretAnimationCurve,
2551-
);
2541+
if (withAnimation) {
2542+
_scrollController.animateTo(
2543+
targetOffset.offset,
2544+
duration: _caretAnimationDuration,
2545+
curve: _caretAnimationCurve,
2546+
);
2547+
renderEditable.showOnScreen(
2548+
rect: caretPadding.inflateRect(targetOffset.rect),
2549+
duration: _caretAnimationDuration,
2550+
curve: _caretAnimationCurve,
2551+
);
2552+
} else {
2553+
_scrollController.jumpTo(targetOffset.offset);
2554+
renderEditable.showOnScreen(
2555+
rect: caretPadding.inflateRect(targetOffset.rect),
2556+
);
2557+
}
25522558
});
25532559
}
25542560

@@ -2561,7 +2567,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
25612567
_selectionOverlay?.updateForScroll();
25622568
});
25632569
if (_lastBottomViewInset < WidgetsBinding.instance.window.viewInsets.bottom) {
2564-
_scheduleShowCaretOnScreen();
2570+
// Because the metrics change signal from engine will come here every frame
2571+
// (on both iOS and Android). So we don't need to show caret with animation.
2572+
_scheduleShowCaretOnScreen(withAnimation: false);
25652573
}
25662574
}
25672575
_lastBottomViewInset = WidgetsBinding.instance.window.viewInsets.bottom;
@@ -2745,7 +2753,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
27452753
WidgetsBinding.instance.addObserver(this);
27462754
_lastBottomViewInset = WidgetsBinding.instance.window.viewInsets.bottom;
27472755
if (!widget.readOnly) {
2748-
_scheduleShowCaretOnScreen();
2756+
_scheduleShowCaretOnScreen(withAnimation: true);
27492757
}
27502758
if (!_value.selection.isValid) {
27512759
// Place cursor at the end if the selection is invalid when we receive focus.
@@ -2900,7 +2908,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
29002908
? _value.selection != value.selection
29012909
: _value != value;
29022910
if (shouldShowCaret) {
2903-
_scheduleShowCaretOnScreen();
2911+
_scheduleShowCaretOnScreen(withAnimation: true);
29042912
}
29052913
_formatAndSetValue(value, cause, userInteraction: true);
29062914
}

packages/flutter/test/widgets/editable_text_test.dart

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'dart:ui' as ui;
6+
57
import 'package:flutter/cupertino.dart';
68
import 'package:flutter/foundation.dart';
79
import 'package:flutter/gestures.dart';
@@ -39,6 +41,26 @@ class _MatchesMethodCall extends Matcher {
3941
}
4042
}
4143

44+
// Used to set window.viewInsets since the real ui.WindowPadding has only a
45+
// private constructor.
46+
class _TestWindowPadding implements ui.WindowPadding {
47+
const _TestWindowPadding({
48+
required this.bottom,
49+
});
50+
51+
@override
52+
final double bottom;
53+
54+
@override
55+
double get top => 0.0;
56+
57+
@override
58+
double get left => 0.0;
59+
60+
@override
61+
double get right => 0.0;
62+
}
63+
4264
late TextEditingController controller;
4365
final FocusNode focusNode = FocusNode(debugLabel: 'EditableText Node');
4466
final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: 'EditableText Scope Node');
@@ -107,6 +129,51 @@ void main() {
107129
expect(tester.testTextInput.setClientArgs!['inputAction'], equals(serializedActionName));
108130
}
109131

132+
// Related issue: https://github.com/flutter/flutter/issues/98115
133+
testWidgets('ScheduleShowCaretOnScreen with no animation when the window changes metrics', (WidgetTester tester) async {
134+
final ScrollController scrollController = ScrollController();
135+
final Widget widget = MaterialApp(
136+
home: Scaffold(
137+
body: SingleChildScrollView(
138+
controller: scrollController,
139+
child: Column(
140+
children: <Widget>[
141+
Column(
142+
children: List<Widget>.generate(
143+
5,
144+
(_) {
145+
return Container(
146+
height: 1200.0,
147+
color: Colors.black12,
148+
);
149+
},
150+
),
151+
),
152+
SizedBox(
153+
height: 20,
154+
child: EditableText(
155+
controller: TextEditingController(),
156+
backgroundCursorColor: Colors.grey,
157+
focusNode: focusNode,
158+
style: const TextStyle(),
159+
cursorColor: Colors.red,
160+
),
161+
),
162+
],
163+
),
164+
),
165+
),
166+
);
167+
await tester.pumpWidget(widget);
168+
await tester.showKeyboard(find.byType(EditableText));
169+
TestWidgetsFlutterBinding.instance.window.viewInsetsTestValue = const _TestWindowPadding(bottom: 500);
170+
await tester.pump();
171+
172+
// The offset of the scrollController should change immediately after window changes its metrics.
173+
final double offsetAfter = scrollController.offset;
174+
expect(offsetAfter, isNot(0.0));
175+
});
176+
110177
// Regression test for https://github.com/flutter/flutter/issues/34538.
111178
testWidgets('RTL arabic correct caret placement after trailing whitespace', (WidgetTester tester) async {
112179
final TextEditingController controller = TextEditingController();

0 commit comments

Comments
 (0)