Skip to content

Commit 5828969

Browse files
authored
Override PlaceholderDimensions equality operator to avoid unnecessary TextPainter re-layouts (flutter#108623)
1 parent b007d33 commit 5828969

File tree

2 files changed

+90
-0
lines changed

2 files changed

+90
-0
lines changed

packages/flutter/lib/src/painting/text_painter.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,21 @@ class PlaceholderDimensions {
108108
/// * [ui.PlaceholderAlignment.middle]
109109
final TextBaseline? baseline;
110110

111+
@override
112+
bool operator ==(Object other) {
113+
if (identical(this, other)) {
114+
return true;
115+
}
116+
return other is PlaceholderDimensions
117+
&& other.size == size
118+
&& other.alignment == alignment
119+
&& other.baseline == baseline
120+
&& other.baselineOffset == baselineOffset;
121+
}
122+
123+
@override
124+
int get hashCode => Object.hash(size, alignment, baseline, baselineOffset);
125+
111126
@override
112127
String toString() {
113128
return 'PlaceholderDimensions($size, $baseline)';

packages/flutter/test/painting/text_painter_test.dart

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,6 +1069,81 @@ void main() {
10691069
expect(exception?.message, contains('The calls that first invalidated the text layout were:'));
10701070
exception = null;
10711071
});
1072+
1073+
test('TextPainter requires layout after providing different placeholder dimensions', () {
1074+
final TextPainter painter = TextPainter()
1075+
..textDirection = TextDirection.ltr;
1076+
1077+
painter.text = const TextSpan(children: <InlineSpan>[
1078+
TextSpan(text: 'before'),
1079+
WidgetSpan(child: Text('widget1')),
1080+
WidgetSpan(child: Text('widget2')),
1081+
WidgetSpan(child: Text('widget3')),
1082+
TextSpan(text: 'after'),
1083+
]);
1084+
1085+
painter.setPlaceholderDimensions(const <PlaceholderDimensions>[
1086+
PlaceholderDimensions(size: Size(30, 30), alignment: ui.PlaceholderAlignment.bottom),
1087+
PlaceholderDimensions(size: Size(40, 30), alignment: ui.PlaceholderAlignment.bottom),
1088+
PlaceholderDimensions(size: Size(50, 30), alignment: ui.PlaceholderAlignment.bottom),
1089+
]);
1090+
painter.layout();
1091+
1092+
painter.setPlaceholderDimensions(const <PlaceholderDimensions>[
1093+
PlaceholderDimensions(size: Size(30, 30), alignment: ui.PlaceholderAlignment.bottom),
1094+
PlaceholderDimensions(size: Size(40, 20), alignment: ui.PlaceholderAlignment.bottom),
1095+
PlaceholderDimensions(size: Size(50, 30), alignment: ui.PlaceholderAlignment.bottom),
1096+
]);
1097+
1098+
Object? e;
1099+
try {
1100+
painter.paint(MockCanvas(), Offset.zero);
1101+
} catch (exception) {
1102+
e = exception;
1103+
}
1104+
expect(
1105+
e.toString(),
1106+
contains('TextPainter.paint called when text geometry was not yet calculated'),
1107+
);
1108+
}, skip: isBrowser && !isCanvasKit); // https://github.com/flutter/flutter/issues/56308
1109+
1110+
test('TextPainter does not require layout after providing identical placeholder dimensions', () {
1111+
final TextPainter painter = TextPainter()
1112+
..textDirection = TextDirection.ltr;
1113+
1114+
painter.text = const TextSpan(children: <InlineSpan>[
1115+
TextSpan(text: 'before'),
1116+
WidgetSpan(child: Text('widget1')),
1117+
WidgetSpan(child: Text('widget2')),
1118+
WidgetSpan(child: Text('widget3')),
1119+
TextSpan(text: 'after'),
1120+
]);
1121+
1122+
painter.setPlaceholderDimensions(const <PlaceholderDimensions>[
1123+
PlaceholderDimensions(size: Size(30, 30), alignment: ui.PlaceholderAlignment.bottom),
1124+
PlaceholderDimensions(size: Size(40, 30), alignment: ui.PlaceholderAlignment.bottom),
1125+
PlaceholderDimensions(size: Size(50, 30), alignment: ui.PlaceholderAlignment.bottom),
1126+
]);
1127+
painter.layout();
1128+
1129+
painter.setPlaceholderDimensions(const <PlaceholderDimensions>[
1130+
PlaceholderDimensions(size: Size(30, 30), alignment: ui.PlaceholderAlignment.bottom),
1131+
PlaceholderDimensions(size: Size(40, 30), alignment: ui.PlaceholderAlignment.bottom),
1132+
PlaceholderDimensions(size: Size(50, 30), alignment: ui.PlaceholderAlignment.bottom),
1133+
]);
1134+
1135+
Object? e;
1136+
try {
1137+
painter.paint(MockCanvas(), Offset.zero);
1138+
} catch (exception) {
1139+
e = exception;
1140+
}
1141+
// In tests, paint() will throw an UnimplementedError due to missing drawParagraph method.
1142+
expect(
1143+
e.toString(),
1144+
isNot(contains('TextPainter.paint called when text geometry was not yet calculated')),
1145+
);
1146+
}, skip: isBrowser && !isCanvasKit); // https://github.com/flutter/flutter/issues/56308
10721147
}
10731148

10741149
class MockCanvas extends Fake implements Canvas {

0 commit comments

Comments
 (0)