mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Fix: fix the delay of showOnScreen animation when keyboard comes up. (#99546)
This commit is contained in:
parent
f1d8e30c4b
commit
dbbaf68ee4
@ -1965,7 +1965,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
// to make sure the user can see the changes they just made. Programmatical
|
// to make sure the user can see the changes they just made. Programmatical
|
||||||
// changes to `textEditingValue` do not trigger the behavior even if the
|
// changes to `textEditingValue` do not trigger the behavior even if the
|
||||||
// text field is focused.
|
// text field is focused.
|
||||||
_scheduleShowCaretOnScreen();
|
_scheduleShowCaretOnScreen(withAnimation: true);
|
||||||
if (_hasInputConnection) {
|
if (_hasInputConnection) {
|
||||||
// To keep the cursor from blinking while typing, we want to restart the
|
// To keep the cursor from blinking while typing, we want to restart the
|
||||||
// cursor timer every time a new character is typed.
|
// cursor timer every time a new character is typed.
|
||||||
@ -2498,7 +2498,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
|
|
||||||
bool _showCaretOnScreenScheduled = false;
|
bool _showCaretOnScreenScheduled = false;
|
||||||
|
|
||||||
void _scheduleShowCaretOnScreen() {
|
void _scheduleShowCaretOnScreen({required bool withAnimation}) {
|
||||||
if (_showCaretOnScreenScheduled) {
|
if (_showCaretOnScreenScheduled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2538,17 +2538,23 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
|
|
||||||
final RevealedOffset targetOffset = _getOffsetToRevealCaret(_currentCaretRect!);
|
final RevealedOffset targetOffset = _getOffsetToRevealCaret(_currentCaretRect!);
|
||||||
|
|
||||||
_scrollController.animateTo(
|
if (withAnimation) {
|
||||||
targetOffset.offset,
|
_scrollController.animateTo(
|
||||||
duration: _caretAnimationDuration,
|
targetOffset.offset,
|
||||||
curve: _caretAnimationCurve,
|
duration: _caretAnimationDuration,
|
||||||
);
|
curve: _caretAnimationCurve,
|
||||||
|
);
|
||||||
renderEditable.showOnScreen(
|
renderEditable.showOnScreen(
|
||||||
rect: caretPadding.inflateRect(targetOffset.rect),
|
rect: caretPadding.inflateRect(targetOffset.rect),
|
||||||
duration: _caretAnimationDuration,
|
duration: _caretAnimationDuration,
|
||||||
curve: _caretAnimationCurve,
|
curve: _caretAnimationCurve,
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
_scrollController.jumpTo(targetOffset.offset);
|
||||||
|
renderEditable.showOnScreen(
|
||||||
|
rect: caretPadding.inflateRect(targetOffset.rect),
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2561,7 +2567,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
_selectionOverlay?.updateForScroll();
|
_selectionOverlay?.updateForScroll();
|
||||||
});
|
});
|
||||||
if (_lastBottomViewInset < WidgetsBinding.instance.window.viewInsets.bottom) {
|
if (_lastBottomViewInset < WidgetsBinding.instance.window.viewInsets.bottom) {
|
||||||
_scheduleShowCaretOnScreen();
|
// Because the metrics change signal from engine will come here every frame
|
||||||
|
// (on both iOS and Android). So we don't need to show caret with animation.
|
||||||
|
_scheduleShowCaretOnScreen(withAnimation: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_lastBottomViewInset = WidgetsBinding.instance.window.viewInsets.bottom;
|
_lastBottomViewInset = WidgetsBinding.instance.window.viewInsets.bottom;
|
||||||
@ -2745,7 +2753,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
_lastBottomViewInset = WidgetsBinding.instance.window.viewInsets.bottom;
|
_lastBottomViewInset = WidgetsBinding.instance.window.viewInsets.bottom;
|
||||||
if (!widget.readOnly) {
|
if (!widget.readOnly) {
|
||||||
_scheduleShowCaretOnScreen();
|
_scheduleShowCaretOnScreen(withAnimation: true);
|
||||||
}
|
}
|
||||||
if (!_value.selection.isValid) {
|
if (!_value.selection.isValid) {
|
||||||
// Place cursor at the end if the selection is invalid when we receive focus.
|
// 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
|
|||||||
? _value.selection != value.selection
|
? _value.selection != value.selection
|
||||||
: _value != value;
|
: _value != value;
|
||||||
if (shouldShowCaret) {
|
if (shouldShowCaret) {
|
||||||
_scheduleShowCaretOnScreen();
|
_scheduleShowCaretOnScreen(withAnimation: true);
|
||||||
}
|
}
|
||||||
_formatAndSetValue(value, cause, userInteraction: true);
|
_formatAndSetValue(value, cause, userInteraction: true);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
@ -39,6 +41,26 @@ class _MatchesMethodCall extends Matcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used to set window.viewInsets since the real ui.WindowPadding has only a
|
||||||
|
// private constructor.
|
||||||
|
class _TestWindowPadding implements ui.WindowPadding {
|
||||||
|
const _TestWindowPadding({
|
||||||
|
required this.bottom,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
final double bottom;
|
||||||
|
|
||||||
|
@override
|
||||||
|
double get top => 0.0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
double get left => 0.0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
double get right => 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
late TextEditingController controller;
|
late TextEditingController controller;
|
||||||
final FocusNode focusNode = FocusNode(debugLabel: 'EditableText Node');
|
final FocusNode focusNode = FocusNode(debugLabel: 'EditableText Node');
|
||||||
final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: 'EditableText Scope Node');
|
final FocusScopeNode focusScopeNode = FocusScopeNode(debugLabel: 'EditableText Scope Node');
|
||||||
@ -107,6 +129,51 @@ void main() {
|
|||||||
expect(tester.testTextInput.setClientArgs!['inputAction'], equals(serializedActionName));
|
expect(tester.testTextInput.setClientArgs!['inputAction'], equals(serializedActionName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Related issue: https://github.com/flutter/flutter/issues/98115
|
||||||
|
testWidgets('ScheduleShowCaretOnScreen with no animation when the window changes metrics', (WidgetTester tester) async {
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
final Widget widget = MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
controller: scrollController,
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Column(
|
||||||
|
children: List<Widget>.generate(
|
||||||
|
5,
|
||||||
|
(_) {
|
||||||
|
return Container(
|
||||||
|
height: 1200.0,
|
||||||
|
color: Colors.black12,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 20,
|
||||||
|
child: EditableText(
|
||||||
|
controller: TextEditingController(),
|
||||||
|
backgroundCursorColor: Colors.grey,
|
||||||
|
focusNode: focusNode,
|
||||||
|
style: const TextStyle(),
|
||||||
|
cursorColor: Colors.red,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpWidget(widget);
|
||||||
|
await tester.showKeyboard(find.byType(EditableText));
|
||||||
|
TestWidgetsFlutterBinding.instance.window.viewInsetsTestValue = const _TestWindowPadding(bottom: 500);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// The offset of the scrollController should change immediately after window changes its metrics.
|
||||||
|
final double offsetAfter = scrollController.offset;
|
||||||
|
expect(offsetAfter, isNot(0.0));
|
||||||
|
});
|
||||||
|
|
||||||
// Regression test for https://github.com/flutter/flutter/issues/34538.
|
// Regression test for https://github.com/flutter/flutter/issues/34538.
|
||||||
testWidgets('RTL arabic correct caret placement after trailing whitespace', (WidgetTester tester) async {
|
testWidgets('RTL arabic correct caret placement after trailing whitespace', (WidgetTester tester) async {
|
||||||
final TextEditingController controller = TextEditingController();
|
final TextEditingController controller = TextEditingController();
|
||||||
|
Loading…
Reference in New Issue
Block a user