Skip to content

Commit d01874d

Browse files
[framework] re-rasterize page transition when layout size changes (#115371)
* [framework] autoresize on snapshot widget * ++ * ++ * ++ * use layout to resize
1 parent b579109 commit d01874d

File tree

3 files changed

+67
-40
lines changed

3 files changed

+67
-40
lines changed

packages/flutter/lib/src/material/page_transitions_theme.dart

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,6 @@ class _ZoomEnterTransitionState extends State<_ZoomEnterTransition> with _ZoomTr
291291
bool get useSnapshot => !kIsWeb && widget.allowSnapshotting;
292292

293293
late _ZoomEnterTransitionPainter delegate;
294-
MediaQueryData? mediaQueryData;
295294

296295
static final Animatable<double> _fadeInTransition = Tween<double>(
297296
begin: 0.0,
@@ -356,18 +355,6 @@ class _ZoomEnterTransitionState extends State<_ZoomEnterTransition> with _ZoomTr
356355
super.didUpdateWidget(oldWidget);
357356
}
358357

359-
@override
360-
void didChangeDependencies() {
361-
// If the screen size changes during the transition, perhaps due to
362-
// a keyboard dismissal, then ensure that contents are re-rasterized once.
363-
final MediaQueryData? data = MediaQuery.maybeOf(context);
364-
if (mediaQueryDataChanged(mediaQueryData, data)) {
365-
controller.clear();
366-
}
367-
mediaQueryData = data;
368-
super.didChangeDependencies();
369-
}
370-
371358
@override
372359
void dispose() {
373360
widget.animation.removeListener(onAnimationValueChange);
@@ -382,6 +369,7 @@ class _ZoomEnterTransitionState extends State<_ZoomEnterTransition> with _ZoomTr
382369
painter: delegate,
383370
controller: controller,
384371
mode: SnapshotMode.permissive,
372+
autoresize: true,
385373
child: widget.child,
386374
);
387375
}
@@ -407,7 +395,6 @@ class _ZoomExitTransition extends StatefulWidget {
407395

408396
class _ZoomExitTransitionState extends State<_ZoomExitTransition> with _ZoomTransitionBase {
409397
late _ZoomExitTransitionPainter delegate;
410-
MediaQueryData? mediaQueryData;
411398

412399
// See SnapshotWidget doc comment, this is disabled on web because the HTML backend doesn't
413400
// support this functionality and the canvaskit backend uses a single thread for UI and raster
@@ -472,18 +459,6 @@ class _ZoomExitTransitionState extends State<_ZoomExitTransition> with _ZoomTran
472459
super.didUpdateWidget(oldWidget);
473460
}
474461

475-
@override
476-
void didChangeDependencies() {
477-
// If the screen size changes during the transition, perhaps due to
478-
// a keyboard dismissal, then ensure that contents are re-rasterized once.
479-
final MediaQueryData? data = MediaQuery.maybeOf(context);
480-
if (mediaQueryDataChanged(mediaQueryData, data)) {
481-
controller.clear();
482-
}
483-
mediaQueryData = data;
484-
super.didChangeDependencies();
485-
}
486-
487462
@override
488463
void dispose() {
489464
widget.animation.removeListener(onAnimationValueChange);
@@ -498,6 +473,7 @@ class _ZoomExitTransitionState extends State<_ZoomExitTransition> with _ZoomTran
498473
painter: delegate,
499474
controller: controller,
500475
mode: SnapshotMode.permissive,
476+
autoresize: true,
501477
child: widget.child,
502478
);
503479
}
@@ -830,13 +806,6 @@ mixin _ZoomTransitionBase {
830806
break;
831807
}
832808
}
833-
834-
// Whether any of the properties that would impact the page transition
835-
// changed.
836-
bool mediaQueryDataChanged(MediaQueryData? oldData, MediaQueryData? newData) {
837-
return oldData?.size != newData?.size ||
838-
oldData?.viewInsets != newData?.viewInsets;
839-
}
840809
}
841810

842811
class _ZoomEnterTransitionPainter extends SnapshotPainter {

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

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ class SnapshotWidget extends SingleChildRenderObjectWidget {
110110
super.key,
111111
this.mode = SnapshotMode.normal,
112112
this.painter = const _DefaultSnapshotPainter(),
113+
this.autoresize = false,
113114
required this.controller,
114115
required super.child
115116
});
@@ -125,6 +126,12 @@ class SnapshotWidget extends SingleChildRenderObjectWidget {
125126
/// See [SnapshotMode] for more information.
126127
final SnapshotMode mode;
127128

129+
/// Whether or not changes in render object size should automatically re-create
130+
/// the snapshot.
131+
///
132+
/// Defaults to false.
133+
final bool autoresize;
134+
128135
/// The painter used to paint the child snapshot or child widgets.
129136
final SnapshotPainter painter;
130137

@@ -136,6 +143,7 @@ class SnapshotWidget extends SingleChildRenderObjectWidget {
136143
mode: mode,
137144
devicePixelRatio: MediaQuery.of(context).devicePixelRatio,
138145
painter: painter,
146+
autoresize: autoresize,
139147
);
140148
}
141149

@@ -146,7 +154,8 @@ class SnapshotWidget extends SingleChildRenderObjectWidget {
146154
..controller = controller
147155
..mode = mode
148156
..devicePixelRatio = MediaQuery.of(context).devicePixelRatio
149-
..painter = painter;
157+
..painter = painter
158+
..autoresize = autoresize;
150159
}
151160
}
152161

@@ -159,10 +168,12 @@ class _RenderSnapshotWidget extends RenderProxyBox {
159168
required SnapshotController controller,
160169
required SnapshotMode mode,
161170
required SnapshotPainter painter,
171+
required bool autoresize,
162172
}) : _devicePixelRatio = devicePixelRatio,
163173
_controller = controller,
164174
_mode = mode,
165-
_painter = painter;
175+
_painter = painter,
176+
_autoresize = autoresize;
166177

167178
/// The device pixel ratio used to create the child image.
168179
double get devicePixelRatio => _devicePixelRatio;
@@ -230,6 +241,17 @@ class _RenderSnapshotWidget extends RenderProxyBox {
230241
markNeedsPaint();
231242
}
232243

244+
/// Whether or not changes in render object size should automatically re-rasterize.
245+
bool get autoresize => _autoresize;
246+
bool _autoresize;
247+
set autoresize(bool value) {
248+
if (value == autoresize) {
249+
return;
250+
}
251+
_autoresize = value;
252+
markNeedsPaint();
253+
}
254+
233255
ui.Image? _childRaster;
234256
Size? _childRasterSize;
235257
// Set to true if the snapshot mode was not forced and a platform view
@@ -292,9 +314,12 @@ class _RenderSnapshotWidget extends RenderProxyBox {
292314
}
293315
final ui.Image image = offsetLayer.toImageSync(Offset.zero & size, pixelRatio: devicePixelRatio);
294316
offsetLayer.dispose();
317+
_lastCachedSize = size;
295318
return image;
296319
}
297320

321+
Size? _lastCachedSize;
322+
298323
@override
299324
void paint(PaintingContext context, Offset offset) {
300325
if (size.isEmpty) {
@@ -310,6 +335,12 @@ class _RenderSnapshotWidget extends RenderProxyBox {
310335
painter.paint(context, offset, size, super.paint);
311336
return;
312337
}
338+
339+
if (autoresize && size != _lastCachedSize && _lastCachedSize != null) {
340+
_childRaster?.dispose();
341+
_childRaster = null;
342+
}
343+
313344
if (_childRaster == null) {
314345
_childRaster = _paintAndDetachToImage();
315346
_childRasterSize = size * devicePixelRatio;

packages/flutter/test/material/page_test.dart

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
// found in the LICENSE file.
44

55
@Tags(<String>['reduced-test-set'])
6+
import 'dart:ui' as ui;
7+
68
import 'package:flutter/cupertino.dart' show CupertinoPageRoute;
79
import 'package:flutter/foundation.dart';
810
import 'package:flutter/material.dart';
@@ -236,13 +238,17 @@ void main() {
236238
expect(find.text('Page 2'), findsNothing);
237239
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
238240

239-
testWidgets('test page transition (_ZoomPageTransition) with rasterization re-rasterizes when window size changes', (WidgetTester tester) async {
240-
// Shrink the window size.
241+
testWidgets('test page transition (_ZoomPageTransition) with rasterization re-rasterizes when window insets', (WidgetTester tester) async {
241242
late Size oldSize;
243+
late ui.WindowPadding oldInsets;
242244
try {
243245
oldSize = tester.binding.window.physicalSize;
246+
oldInsets = tester.binding.window.viewInsets;
244247
tester.binding.window.physicalSizeTestValue = const Size(1000, 1000);
248+
tester.binding.window.viewInsetsTestValue = ui.WindowPadding.zero;
245249

250+
// Intentionally use nested scaffolds to simulate the view insets being
251+
// consumed.
246252
final Key key = GlobalKey();
247253
await tester.pumpWidget(
248254
RepaintBoundary(
@@ -251,7 +257,9 @@ void main() {
251257
onGenerateRoute: (RouteSettings settings) {
252258
return MaterialPageRoute<void>(
253259
builder: (BuildContext context) {
254-
return const Material(child: SizedBox.shrink());
260+
return const Scaffold(body: Scaffold(
261+
body: Material(child: SizedBox.shrink())
262+
));
255263
},
256264
);
257265
},
@@ -265,15 +273,16 @@ void main() {
265273

266274
await expectLater(find.byKey(key), matchesGoldenFile('zoom_page_transition.small.png'));
267275

268-
// Increase the window size.
269-
tester.binding.window.physicalSizeTestValue = const Size(1000, 2000);
276+
// Change the view insets
277+
tester.binding.window.viewInsetsTestValue = const TestWindowPadding(left: 0, top: 0, right: 0, bottom: 500);
270278

271279
await tester.pump();
272280
await tester.pump(const Duration(milliseconds: 50));
273281

274282
await expectLater(find.byKey(key), matchesGoldenFile('zoom_page_transition.big.png'));
275283
} finally {
276284
tester.binding.window.physicalSizeTestValue = oldSize;
285+
tester.binding.window.viewInsetsTestValue = oldInsets;
277286
}
278287
}, variant: TargetPlatformVariant.only(TargetPlatform.android), skip: kIsWeb); // [intended] rasterization is not used on the web.
279288

@@ -1236,3 +1245,21 @@ class TestDependencies extends StatelessWidget {
12361245
);
12371246
}
12381247
}
1248+
1249+
class TestWindowPadding implements ui.WindowPadding {
1250+
const TestWindowPadding({
1251+
required this.left,
1252+
required this.top,
1253+
required this.right,
1254+
required this.bottom,
1255+
});
1256+
1257+
@override
1258+
final double left;
1259+
@override
1260+
final double top;
1261+
@override
1262+
final double right;
1263+
@override
1264+
final double bottom;
1265+
}

0 commit comments

Comments
 (0)