diff --git a/dev/benchmarks/macrobenchmarks/lib/common.dart b/dev/benchmarks/macrobenchmarks/lib/common.dart index d75ce7067a6..3742c1608d1 100644 --- a/dev/benchmarks/macrobenchmarks/lib/common.dart +++ b/dev/benchmarks/macrobenchmarks/lib/common.dart @@ -9,3 +9,4 @@ const String kPostBackdropFilterRouteName = '/post_backdrop_filter'; const String kSimpleAnimationRouteName = '/simple_animation'; const String kPictureCacheRouteName = '/picture_cache'; const String kLargeImagesRouteName = '/large_images'; +const String kTextRouteName = '/text'; diff --git a/dev/benchmarks/macrobenchmarks/lib/main.dart b/dev/benchmarks/macrobenchmarks/lib/main.dart index d7877bd870b..67b87f97753 100644 --- a/dev/benchmarks/macrobenchmarks/lib/main.dart +++ b/dev/benchmarks/macrobenchmarks/lib/main.dart @@ -12,6 +12,7 @@ import 'src/cubic_bezier.dart'; import 'src/cull_opacity.dart'; import 'src/post_backdrop_filter.dart'; import 'src/simple_animation.dart'; +import 'src/text.dart'; const String kMacrobenchmarks ='Macrobenchmarks'; @@ -34,6 +35,7 @@ class MacrobenchmarksApp extends StatelessWidget { kSimpleAnimationRouteName: (BuildContext conttext) => SimpleAnimationPage(), kPictureCacheRouteName: (BuildContext context) => PictureCachePage(), kLargeImagesRouteName: (BuildContext context) => LargeImagesPage(), + kTextRouteName: (BuildContext context) => TextPage(), }, ); } @@ -97,6 +99,13 @@ class HomePage extends StatelessWidget { Navigator.pushNamed(context, kLargeImagesRouteName); }, ), + RaisedButton( + key: const Key(kTextRouteName), + child: const Text('Text'), + onPressed: () { + Navigator.pushNamed(context, kTextRouteName); + }, + ), ], ), ); diff --git a/dev/benchmarks/macrobenchmarks/lib/src/text.dart b/dev/benchmarks/macrobenchmarks/lib/src/text.dart new file mode 100644 index 00000000000..58f3488b022 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/lib/src/text.dart @@ -0,0 +1,22 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; + +class TextPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + width: 200, + height: 100, + child: const TextField( + key: Key('basic-textfield'), + ), + ), + ], + ); + } +} diff --git a/dev/benchmarks/macrobenchmarks/test_driver/textfield_perf.dart b/dev/benchmarks/macrobenchmarks/test_driver/textfield_perf.dart new file mode 100644 index 00000000000..8169d132b69 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/test_driver/textfield_perf.dart @@ -0,0 +1,11 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_driver/driver_extension.dart'; +import 'package:macrobenchmarks/main.dart' as app; + +void main() { + enableFlutterDriverExtension(); + app.main(); +} diff --git a/dev/benchmarks/macrobenchmarks/test_driver/textfield_perf_test.dart b/dev/benchmarks/macrobenchmarks/test_driver/textfield_perf_test.dart new file mode 100644 index 00000000000..9af44ca5718 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/test_driver/textfield_perf_test.dart @@ -0,0 +1,21 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_driver/flutter_driver.dart'; +import 'package:macrobenchmarks/common.dart'; + +import 'util.dart'; + +void main() { + macroPerfTest( + 'textfield_perf', + kTextRouteName, + driverOps: (FlutterDriver driver) async { + final SerializableFinder textfield = find.byValueKey('basic-textfield'); + driver.tap(textfield); + // Caret should be cached, so repeated blinking should not require recompute. + await Future.delayed(const Duration(milliseconds: 5000)); + }, + ); +} diff --git a/dev/devicelab/bin/tasks/textfield_perf.dart b/dev/devicelab/bin/tasks/textfield_perf.dart new file mode 100644 index 00000000000..049869b7641 --- /dev/null +++ b/dev/devicelab/bin/tasks/textfield_perf.dart @@ -0,0 +1,14 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter_devicelab/tasks/perf_tests.dart'; +import 'package:flutter_devicelab/framework/adb.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; + +Future main() async { + deviceOperatingSystem = DeviceOperatingSystem.android; + await task(createTextfieldPerfTest()); +} diff --git a/dev/devicelab/lib/tasks/perf_tests.dart b/dev/devicelab/lib/tasks/perf_tests.dart index fc3c57a8d8f..a1ad8fd14c5 100644 --- a/dev/devicelab/lib/tasks/perf_tests.dart +++ b/dev/devicelab/lib/tasks/perf_tests.dart @@ -156,6 +156,14 @@ TaskFunction createBasicMaterialCompileTest() { }; } +TaskFunction createTextfieldPerfTest() { + return PerfTest( + '${flutterDirectory.path}/dev/benchmarks/macrobenchmarks', + 'test_driver/textfield_perf.dart', + 'textfield_perf', + ).run; +} + /// Measure application startup performance. class StartupTest { diff --git a/packages/flutter/lib/src/painting/text_painter.dart b/packages/flutter/lib/src/painting/text_painter.dart index 2fd8a8d2fa4..6caf3bbee76 100644 --- a/packages/flutter/lib/src/painting/text_painter.dart +++ b/packages/flutter/lib/src/painting/text_painter.dart @@ -170,6 +170,8 @@ class TextPainter { void markNeedsLayout() { _paragraph = null; _needsLayout = true; + _previousCaretPosition = null; + _previousCaretPrototype = null; } /// The (potentially styled) text to paint. diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index dfef98c15cc..79765ba981f 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -365,7 +365,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin { final Offset startOffset = _textPainter.getOffsetForCaret( TextPosition(offset: _selection.start, affinity: _selection.affinity), - Rect.zero, + _caretPrototype, ); // TODO(justinmc): https://github.com/flutter/flutter/issues/31495 // Check if the selection is visible with an approximation because a @@ -381,7 +381,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin { final Offset endOffset = _textPainter.getOffsetForCaret( TextPosition(offset: _selection.end, affinity: _selection.affinity), - Rect.zero, + _caretPrototype, ); _selectionEndInViewport.value = visibleRegion .inflate(visibleRegionSlop)