Skip to content

Commit a6f17e6

Browse files
authored
Add option for opting out of enter route snapshotting. (#118086)
* Add option for opting out of enter route snapshotting. * Fix typo. * Merge find layers logic. * Add justification comment on why web is skipped in test. * Update documentation as suggested. * Update documentation as suggested.
1 parent 594333b commit a6f17e6

File tree

2 files changed

+108
-3
lines changed

2 files changed

+108
-3
lines changed

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

+25-3
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ class _ZoomPageTransition extends StatelessWidget {
157157
required this.animation,
158158
required this.secondaryAnimation,
159159
required this.allowSnapshotting,
160+
required this.allowEnterRouteSnapshotting,
160161
this.child,
161162
}) : assert(animation != null),
162163
assert(secondaryAnimation != null);
@@ -207,6 +208,15 @@ class _ZoomPageTransition extends StatelessWidget {
207208
/// [secondaryAnimation].
208209
final Widget? child;
209210

211+
/// Whether to enable snapshotting on the entering route during the
212+
/// transition animation.
213+
///
214+
/// If not specified, defaults to true.
215+
/// If false, the route snapshotting will not be applied to the route being
216+
/// animating into, e.g. when transitioning from route A to route B, B will
217+
/// not be snapshotted.
218+
final bool allowEnterRouteSnapshotting;
219+
210220
@override
211221
Widget build(BuildContext context) {
212222
return DualTransitionBuilder(
@@ -218,7 +228,7 @@ class _ZoomPageTransition extends StatelessWidget {
218228
) {
219229
return _ZoomEnterTransition(
220230
animation: animation,
221-
allowSnapshotting: allowSnapshotting,
231+
allowSnapshotting: allowSnapshotting && allowEnterRouteSnapshotting,
222232
child: child,
223233
);
224234
},
@@ -243,7 +253,7 @@ class _ZoomPageTransition extends StatelessWidget {
243253
) {
244254
return _ZoomEnterTransition(
245255
animation: animation,
246-
allowSnapshotting: allowSnapshotting,
256+
allowSnapshotting: allowSnapshotting && allowEnterRouteSnapshotting ,
247257
reverse: true,
248258
child: child,
249259
);
@@ -596,7 +606,18 @@ class OpenUpwardsPageTransitionsBuilder extends PageTransitionsBuilder {
596606
class ZoomPageTransitionsBuilder extends PageTransitionsBuilder {
597607
/// Constructs a page transition animation that matches the transition used on
598608
/// Android Q.
599-
const ZoomPageTransitionsBuilder();
609+
const ZoomPageTransitionsBuilder({
610+
this.allowEnterRouteSnapshotting = true,
611+
});
612+
613+
/// Whether to enable snapshotting on the entering route during the
614+
/// transition animation.
615+
///
616+
/// If not specified, defaults to true.
617+
/// If false, the route snapshotting will not be applied to the route being
618+
/// animating into, e.g. when transitioning from route A to route B, B will
619+
/// not be snapshotted.
620+
final bool allowEnterRouteSnapshotting;
600621

601622
@override
602623
Widget buildTransitions<T>(
@@ -610,6 +631,7 @@ class ZoomPageTransitionsBuilder extends PageTransitionsBuilder {
610631
animation: animation,
611632
secondaryAnimation: secondaryAnimation,
612633
allowSnapshotting: route?.allowSnapshotting ?? true,
634+
allowEnterRouteSnapshotting: allowEnterRouteSnapshotting,
613635
child: child,
614636
);
615637
}

packages/flutter/test/material/page_test.dart

+83
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,89 @@ void main() {
287287
}
288288
}, variant: TargetPlatformVariant.only(TargetPlatform.android), skip: kIsWeb); // [intended] rasterization is not used on the web.
289289

290+
291+
testWidgets(
292+
'test page transition (_ZoomPageTransition) with rasterization disables snapshotting for enter route',
293+
(WidgetTester tester) async {
294+
Iterable<Layer> findLayers(Finder of) {
295+
return tester.layerListOf(
296+
find.ancestor(of: of, matching: find.byType(SnapshotWidget)).first,
297+
);
298+
}
299+
300+
bool isTransitioningWithoutSnapshotting(Finder of) {
301+
// When snapshotting is off, the OpacityLayer and TransformLayer will be
302+
// applied directly.
303+
final Iterable<Layer> layers = findLayers(of);
304+
return layers.whereType<OpacityLayer>().length == 1 &&
305+
layers.whereType<TransformLayer>().length == 1;
306+
}
307+
308+
bool isSnapshotted(Finder of) {
309+
final Iterable<Layer> layers = findLayers(of);
310+
// The scrim and the snapshot image are the only two layers.
311+
return layers.length == 2 &&
312+
layers.whereType<OffsetLayer>().length == 1 &&
313+
layers.whereType<PictureLayer>().length == 1;
314+
}
315+
316+
await tester.pumpWidget(
317+
MaterialApp(
318+
routes: <String, WidgetBuilder>{
319+
'/1': (_) => const Material(child: Text('Page 1')),
320+
'/2': (_) => const Material(child: Text('Page 2')),
321+
},
322+
initialRoute: '/1',
323+
builder: (BuildContext context, Widget? child) {
324+
final ThemeData themeData = Theme.of(context);
325+
return Theme(
326+
data: themeData.copyWith(
327+
pageTransitionsTheme: PageTransitionsTheme(
328+
builders: <TargetPlatform, PageTransitionsBuilder>{
329+
...themeData.pageTransitionsTheme.builders,
330+
TargetPlatform.android: const ZoomPageTransitionsBuilder(
331+
allowEnterRouteSnapshotting: false,
332+
),
333+
},
334+
),
335+
),
336+
child: Builder(builder: (_) => child!),
337+
);
338+
},
339+
),
340+
);
341+
342+
final Finder page1Finder = find.text('Page 1');
343+
final Finder page2Finder = find.text('Page 2');
344+
345+
// Page 1 on top.
346+
expect(isSnapshotted(page1Finder), isFalse);
347+
348+
// Transitioning from page 1 to page 2.
349+
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/2');
350+
await tester.pump();
351+
await tester.pump(const Duration(milliseconds: 50));
352+
353+
expect(isSnapshotted(page1Finder), isTrue);
354+
expect(isTransitioningWithoutSnapshotting(page2Finder), isTrue);
355+
356+
// Page 2 on top.
357+
await tester.pumpAndSettle();
358+
expect(isSnapshotted(page2Finder), isFalse);
359+
360+
// Transitioning back from page 2 to page 1.
361+
tester.state<NavigatorState>(find.byType(Navigator)).pop();
362+
await tester.pump();
363+
await tester.pump(const Duration(milliseconds: 50));
364+
365+
expect(isTransitioningWithoutSnapshotting(page1Finder), isTrue);
366+
expect(isSnapshotted(page2Finder), isTrue);
367+
368+
// Page 1 on top.
369+
await tester.pumpAndSettle();
370+
expect(isSnapshotted(page1Finder), isFalse);
371+
}, variant: TargetPlatformVariant.only(TargetPlatform.android), skip: kIsWeb); // [intended] rasterization is not used on the web.
372+
290373
testWidgets('test fullscreen dialog transition', (WidgetTester tester) async {
291374
await tester.pumpWidget(
292375
const MaterialApp(

0 commit comments

Comments
 (0)