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.
This commit is contained in:
Youchen Du 2023-01-11 05:31:17 +08:00 committed by GitHub
parent 594333b369
commit a6f17e697c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 108 additions and 3 deletions

View file

@ -157,6 +157,7 @@ class _ZoomPageTransition extends StatelessWidget {
required this.animation, required this.animation,
required this.secondaryAnimation, required this.secondaryAnimation,
required this.allowSnapshotting, required this.allowSnapshotting,
required this.allowEnterRouteSnapshotting,
this.child, this.child,
}) : assert(animation != null), }) : assert(animation != null),
assert(secondaryAnimation != null); assert(secondaryAnimation != null);
@ -207,6 +208,15 @@ class _ZoomPageTransition extends StatelessWidget {
/// [secondaryAnimation]. /// [secondaryAnimation].
final Widget? child; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DualTransitionBuilder( return DualTransitionBuilder(
@ -218,7 +228,7 @@ class _ZoomPageTransition extends StatelessWidget {
) { ) {
return _ZoomEnterTransition( return _ZoomEnterTransition(
animation: animation, animation: animation,
allowSnapshotting: allowSnapshotting, allowSnapshotting: allowSnapshotting && allowEnterRouteSnapshotting,
child: child, child: child,
); );
}, },
@ -243,7 +253,7 @@ class _ZoomPageTransition extends StatelessWidget {
) { ) {
return _ZoomEnterTransition( return _ZoomEnterTransition(
animation: animation, animation: animation,
allowSnapshotting: allowSnapshotting, allowSnapshotting: allowSnapshotting && allowEnterRouteSnapshotting ,
reverse: true, reverse: true,
child: child, child: child,
); );
@ -596,7 +606,18 @@ class OpenUpwardsPageTransitionsBuilder extends PageTransitionsBuilder {
class ZoomPageTransitionsBuilder extends PageTransitionsBuilder { class ZoomPageTransitionsBuilder extends PageTransitionsBuilder {
/// Constructs a page transition animation that matches the transition used on /// Constructs a page transition animation that matches the transition used on
/// Android Q. /// 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 @override
Widget buildTransitions<T>( Widget buildTransitions<T>(
@ -610,6 +631,7 @@ class ZoomPageTransitionsBuilder extends PageTransitionsBuilder {
animation: animation, animation: animation,
secondaryAnimation: secondaryAnimation, secondaryAnimation: secondaryAnimation,
allowSnapshotting: route?.allowSnapshotting ?? true, allowSnapshotting: route?.allowSnapshotting ?? true,
allowEnterRouteSnapshotting: allowEnterRouteSnapshotting,
child: child, child: child,
); );
} }

View file

@ -287,6 +287,89 @@ void main() {
} }
}, variant: TargetPlatformVariant.only(TargetPlatform.android), skip: kIsWeb); // [intended] rasterization is not used on the web. }, 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 { testWidgets('test fullscreen dialog transition', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
const MaterialApp( const MaterialApp(