Skip to content

Commit

Permalink
Add option for opting out of enter route snapshotting. (#118086)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
Time1ess authored Jan 10, 2023
1 parent 594333b commit a6f17e6
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 3 deletions.
28 changes: 25 additions & 3 deletions packages/flutter/lib/src/material/page_transitions_theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ class _ZoomPageTransition extends StatelessWidget {
required this.animation,
required this.secondaryAnimation,
required this.allowSnapshotting,
required this.allowEnterRouteSnapshotting,
this.child,
}) : assert(animation != null),
assert(secondaryAnimation != null);
Expand Down Expand Up @@ -207,6 +208,15 @@ class _ZoomPageTransition extends StatelessWidget {
/// [secondaryAnimation].
final Widget? child;

/// Whether to enable snapshotting on the entering route during the
/// transition animation.
///
/// If not specified, defaults to true.
/// If false, the route snapshotting will not be applied to the route being
/// animating into, e.g. when transitioning from route A to route B, B will
/// not be snapshotted.
final bool allowEnterRouteSnapshotting;

@override
Widget build(BuildContext context) {
return DualTransitionBuilder(
Expand All @@ -218,7 +228,7 @@ class _ZoomPageTransition extends StatelessWidget {
) {
return _ZoomEnterTransition(
animation: animation,
allowSnapshotting: allowSnapshotting,
allowSnapshotting: allowSnapshotting && allowEnterRouteSnapshotting,
child: child,
);
},
Expand All @@ -243,7 +253,7 @@ class _ZoomPageTransition extends StatelessWidget {
) {
return _ZoomEnterTransition(
animation: animation,
allowSnapshotting: allowSnapshotting,
allowSnapshotting: allowSnapshotting && allowEnterRouteSnapshotting ,
reverse: true,
child: child,
);
Expand Down Expand Up @@ -596,7 +606,18 @@ class OpenUpwardsPageTransitionsBuilder extends PageTransitionsBuilder {
class ZoomPageTransitionsBuilder extends PageTransitionsBuilder {
/// Constructs a page transition animation that matches the transition used on
/// Android Q.
const ZoomPageTransitionsBuilder();
const ZoomPageTransitionsBuilder({
this.allowEnterRouteSnapshotting = true,
});

/// Whether to enable snapshotting on the entering route during the
/// transition animation.
///
/// If not specified, defaults to true.
/// If false, the route snapshotting will not be applied to the route being
/// animating into, e.g. when transitioning from route A to route B, B will
/// not be snapshotted.
final bool allowEnterRouteSnapshotting;

@override
Widget buildTransitions<T>(
Expand All @@ -610,6 +631,7 @@ class ZoomPageTransitionsBuilder extends PageTransitionsBuilder {
animation: animation,
secondaryAnimation: secondaryAnimation,
allowSnapshotting: route?.allowSnapshotting ?? true,
allowEnterRouteSnapshotting: allowEnterRouteSnapshotting,
child: child,
);
}
Expand Down
83 changes: 83 additions & 0 deletions packages/flutter/test/material/page_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,89 @@ void main() {
}
}, variant: TargetPlatformVariant.only(TargetPlatform.android), skip: kIsWeb); // [intended] rasterization is not used on the web.


testWidgets(
'test page transition (_ZoomPageTransition) with rasterization disables snapshotting for enter route',
(WidgetTester tester) async {
Iterable<Layer> findLayers(Finder of) {
return tester.layerListOf(
find.ancestor(of: of, matching: find.byType(SnapshotWidget)).first,
);
}

bool isTransitioningWithoutSnapshotting(Finder of) {
// When snapshotting is off, the OpacityLayer and TransformLayer will be
// applied directly.
final Iterable<Layer> layers = findLayers(of);
return layers.whereType<OpacityLayer>().length == 1 &&
layers.whereType<TransformLayer>().length == 1;
}

bool isSnapshotted(Finder of) {
final Iterable<Layer> layers = findLayers(of);
// The scrim and the snapshot image are the only two layers.
return layers.length == 2 &&
layers.whereType<OffsetLayer>().length == 1 &&
layers.whereType<PictureLayer>().length == 1;
}

await tester.pumpWidget(
MaterialApp(
routes: <String, WidgetBuilder>{
'/1': (_) => const Material(child: Text('Page 1')),
'/2': (_) => const Material(child: Text('Page 2')),
},
initialRoute: '/1',
builder: (BuildContext context, Widget? child) {
final ThemeData themeData = Theme.of(context);
return Theme(
data: themeData.copyWith(
pageTransitionsTheme: PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
...themeData.pageTransitionsTheme.builders,
TargetPlatform.android: const ZoomPageTransitionsBuilder(
allowEnterRouteSnapshotting: false,
),
},
),
),
child: Builder(builder: (_) => child!),
);
},
),
);

final Finder page1Finder = find.text('Page 1');
final Finder page2Finder = find.text('Page 2');

// Page 1 on top.
expect(isSnapshotted(page1Finder), isFalse);

// Transitioning from page 1 to page 2.
tester.state<NavigatorState>(find.byType(Navigator)).pushNamed('/2');
await tester.pump();
await tester.pump(const Duration(milliseconds: 50));

expect(isSnapshotted(page1Finder), isTrue);
expect(isTransitioningWithoutSnapshotting(page2Finder), isTrue);

// Page 2 on top.
await tester.pumpAndSettle();
expect(isSnapshotted(page2Finder), isFalse);

// Transitioning back from page 2 to page 1.
tester.state<NavigatorState>(find.byType(Navigator)).pop();
await tester.pump();
await tester.pump(const Duration(milliseconds: 50));

expect(isTransitioningWithoutSnapshotting(page1Finder), isTrue);
expect(isSnapshotted(page2Finder), isTrue);

// Page 1 on top.
await tester.pumpAndSettle();
expect(isSnapshotted(page1Finder), isFalse);
}, variant: TargetPlatformVariant.only(TargetPlatform.android), skip: kIsWeb); // [intended] rasterization is not used on the web.

testWidgets('test fullscreen dialog transition', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
Expand Down

0 comments on commit a6f17e6

Please sign in to comment.