mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Add ZoomPageTransitionsBuilder.allowSnapshotting (#122019)
Add ZoomPageTransitionsBuilder.allowSnapshotting
This commit is contained in:
parent
026adb8cc7
commit
961df985fa
@ -0,0 +1,72 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
// Flutter code sample for [PageTransitionsTheme].
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
void main() => runApp(const PageTransitionsThemeApp());
|
||||||
|
|
||||||
|
class PageTransitionsThemeApp extends StatelessWidget {
|
||||||
|
const PageTransitionsThemeApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
pageTransitionsTheme: const PageTransitionsTheme(
|
||||||
|
builders: <TargetPlatform, PageTransitionsBuilder>{
|
||||||
|
TargetPlatform.android: ZoomPageTransitionsBuilder(
|
||||||
|
allowSnapshotting: false,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
home: const HomePage(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HomePage extends StatelessWidget {
|
||||||
|
const HomePage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.blueGrey,
|
||||||
|
body: Center(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
MaterialPageRoute<SecondPage>(
|
||||||
|
builder: (BuildContext context) => const SecondPage(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Text('To SecondPage'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SecondPage extends StatelessWidget {
|
||||||
|
const SecondPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: Colors.purple[200],
|
||||||
|
body: Center(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text('Back to HomePage'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_api_samples/material/page_transitions_theme/page_transitions_theme.1.dart' as example;
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('MaterialApp defines a custom PageTransitionsTheme', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const example.PageTransitionsThemeApp(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Finder homePage = find.byType(example.HomePage);
|
||||||
|
expect(homePage, findsOneWidget);
|
||||||
|
|
||||||
|
final PageTransitionsTheme theme = Theme.of(tester.element(homePage)).pageTransitionsTheme;
|
||||||
|
expect(theme.builders, isNotNull);
|
||||||
|
|
||||||
|
// Check defined page transitions builder for each platform.
|
||||||
|
for (final TargetPlatform platform in TargetPlatform.values) {
|
||||||
|
switch (platform) {
|
||||||
|
case TargetPlatform.android:
|
||||||
|
expect(theme.builders[platform], isA<ZoomPageTransitionsBuilder>());
|
||||||
|
final ZoomPageTransitionsBuilder builder = theme.builders[platform]! as ZoomPageTransitionsBuilder;
|
||||||
|
expect(builder.allowSnapshotting, isFalse);
|
||||||
|
break;
|
||||||
|
case TargetPlatform.iOS:
|
||||||
|
case TargetPlatform.macOS:
|
||||||
|
case TargetPlatform.linux:
|
||||||
|
case TargetPlatform.fuchsia:
|
||||||
|
case TargetPlatform.windows:
|
||||||
|
expect(theme.builders[platform], isNull);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Can navigate to the second page.
|
||||||
|
expect(find.text('To SecondPage'), findsOneWidget);
|
||||||
|
await tester.tap(find.text('To SecondPage'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// Can navigate back to the home page.
|
||||||
|
expect(find.text('Back to HomePage'), findsOneWidget);
|
||||||
|
await tester.tap(find.text('Back to HomePage'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.text('To SecondPage'), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
@ -610,9 +610,36 @@ 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.allowSnapshotting = true,
|
||||||
this.allowEnterRouteSnapshotting = true,
|
this.allowEnterRouteSnapshotting = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Whether zoom page transitions will prefer to animate a snapshot of the entering
|
||||||
|
/// and exiting routes.
|
||||||
|
///
|
||||||
|
/// If not specified, defaults to true.
|
||||||
|
///
|
||||||
|
/// When this value is true, zoom page transitions will snapshot the entering and
|
||||||
|
/// exiting routes. These snapshots are then animated in place of the underlying
|
||||||
|
/// widgets to improve performance of the transition.
|
||||||
|
///
|
||||||
|
/// Generally this means that animations that occur on the entering/exiting route
|
||||||
|
/// while the route animation plays may appear frozen - unless they are a hero
|
||||||
|
/// animation or something that is drawn in a separate overlay.
|
||||||
|
///
|
||||||
|
/// {@tool dartpad}
|
||||||
|
/// This example shows a [MaterialApp] that disables snapshotting for the zoom
|
||||||
|
/// transitions on Android.
|
||||||
|
///
|
||||||
|
/// ** See code in examples/api/lib/material/page_transitions_theme/page_transitions_theme.1.dart **
|
||||||
|
/// {@end-tool}
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [PageRoute.allowSnapshotting], which enables or disables snapshotting
|
||||||
|
/// on a per route basis.
|
||||||
|
final bool allowSnapshotting;
|
||||||
|
|
||||||
/// Whether to enable snapshotting on the entering route during the
|
/// Whether to enable snapshotting on the entering route during the
|
||||||
/// transition animation.
|
/// transition animation.
|
||||||
///
|
///
|
||||||
@ -633,7 +660,7 @@ class ZoomPageTransitionsBuilder extends PageTransitionsBuilder {
|
|||||||
return _ZoomPageTransition(
|
return _ZoomPageTransition(
|
||||||
animation: animation,
|
animation: animation,
|
||||||
secondaryAnimation: secondaryAnimation,
|
secondaryAnimation: secondaryAnimation,
|
||||||
allowSnapshotting: route?.allowSnapshotting ?? true,
|
allowSnapshotting: allowSnapshotting && (route?.allowSnapshotting ?? true),
|
||||||
allowEnterRouteSnapshotting: allowEnterRouteSnapshotting,
|
allowEnterRouteSnapshotting: allowEnterRouteSnapshotting,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
@ -274,7 +274,7 @@ void main() {
|
|||||||
|
|
||||||
await expectLater(find.byKey(key), matchesGoldenFile('zoom_page_transition.small.png'));
|
await expectLater(find.byKey(key), matchesGoldenFile('zoom_page_transition.small.png'));
|
||||||
|
|
||||||
// Change the view insets
|
// Change the view insets.
|
||||||
tester.binding.window.viewInsetsTestValue = const TestViewPadding(left: 0, top: 0, right: 0, bottom: 500);
|
tester.binding.window.viewInsetsTestValue = const TestViewPadding(left: 0, top: 0, right: 0, bottom: 500);
|
||||||
|
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
@ -287,7 +287,6 @@ 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(
|
testWidgets(
|
||||||
'test page transition (_ZoomPageTransition) with rasterization disables snapshotting for enter route',
|
'test page transition (_ZoomPageTransition) with rasterization disables snapshotting for enter route',
|
||||||
(WidgetTester tester) async {
|
(WidgetTester tester) async {
|
||||||
@ -456,7 +455,7 @@ void main() {
|
|||||||
expect(find.text('Page 1'), findsNothing);
|
expect(find.text('Page 1'), findsNothing);
|
||||||
expect(find.text('Page 2'), isOnstage);
|
expect(find.text('Page 2'), isOnstage);
|
||||||
|
|
||||||
// Page 2 didn't move
|
// Page 2 didn't move.
|
||||||
expect(tester.getTopLeft(find.text('Page 2')), Offset.zero);
|
expect(tester.getTopLeft(find.text('Page 2')), Offset.zero);
|
||||||
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
|
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
|
||||||
|
|
||||||
@ -618,7 +617,7 @@ void main() {
|
|||||||
expect(find.text('Page 1'), findsNothing);
|
expect(find.text('Page 1'), findsNothing);
|
||||||
expect(find.text('Page 2'), isOnstage);
|
expect(find.text('Page 2'), isOnstage);
|
||||||
|
|
||||||
// Page 2 didn't move
|
// Page 2 didn't move.
|
||||||
expect(tester.getTopLeft(find.text('Page 2')), Offset.zero);
|
expect(tester.getTopLeft(find.text('Page 2')), Offset.zero);
|
||||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
|
||||||
|
|
||||||
@ -798,7 +797,7 @@ void main() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Check the basic iOS back-swipe dismiss transition. Dragging the pushed
|
// Check the basic iOS back-swipe dismiss transition. Dragging the pushed
|
||||||
// route halfway across the screen will trigger the iOS dismiss animation
|
// route halfway across the screen will trigger the iOS dismiss animation.
|
||||||
|
|
||||||
await tester.tap(find.text('push'));
|
await tester.tap(find.text('push'));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
@ -809,7 +808,7 @@ void main() {
|
|||||||
await gesture.moveBy(const Offset(400, 0));
|
await gesture.moveBy(const Offset(400, 0));
|
||||||
await gesture.up();
|
await gesture.up();
|
||||||
await tester.pump();
|
await tester.pump();
|
||||||
expect( // The 'route' route has been dragged to the right, halfway across the screen
|
expect( // The 'route' route has been dragged to the right, halfway across the screen.
|
||||||
tester.getTopLeft(find.ancestor(of: find.text('route'), matching: find.byType(Scaffold))),
|
tester.getTopLeft(find.ancestor(of: find.text('route'), matching: find.byType(Scaffold))),
|
||||||
const Offset(400, 0),
|
const Offset(400, 0),
|
||||||
);
|
);
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@ -175,7 +176,138 @@ void main() {
|
|||||||
expect(findFadeUpwardsPageTransition(), findsOneWidget);
|
expect(findFadeUpwardsPageTransition(), findsOneWidget);
|
||||||
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
|
}, variant: TargetPlatformVariant.only(TargetPlatform.android));
|
||||||
|
|
||||||
testWidgets('_ZoomPageTransition only cause child widget built once', (WidgetTester tester) async {
|
Widget boilerplate({
|
||||||
|
required bool themeAllowSnapshotting,
|
||||||
|
bool secondRouteAllowSnapshotting = true,
|
||||||
|
}) {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
pageTransitionsTheme: PageTransitionsTheme(
|
||||||
|
builders: <TargetPlatform, PageTransitionsBuilder>{
|
||||||
|
TargetPlatform.android: ZoomPageTransitionsBuilder(
|
||||||
|
allowSnapshotting: themeAllowSnapshotting,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onGenerateRoute: (RouteSettings settings) {
|
||||||
|
if (settings.name == '/') {
|
||||||
|
return MaterialPageRoute<Widget>(
|
||||||
|
builder: (_) => const Material(child: Text('Page 1')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return MaterialPageRoute<Widget>(
|
||||||
|
builder: (_) => const Material(child: Text('Page 2')),
|
||||||
|
allowSnapshotting: secondRouteAllowSnapshotting,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isTransitioningWithSnapshotting(WidgetTester tester, Finder of) {
|
||||||
|
final Iterable<Layer> layers = tester.layerListOf(
|
||||||
|
find.ancestor(of: of, matching: find.byType(SnapshotWidget)).first,
|
||||||
|
);
|
||||||
|
final bool hasOneOpacityLayer = layers.whereType<OpacityLayer>().length == 1;
|
||||||
|
final bool hasOneTransformLayer = layers.whereType<TransformLayer>().length == 1;
|
||||||
|
// When snapshotting is on, the OpacityLayer and TransformLayer will not be
|
||||||
|
// applied directly.
|
||||||
|
return !(hasOneOpacityLayer && hasOneTransformLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets('ZoomPageTransitionsBuilder default route snapshotting behavior', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
boilerplate(themeAllowSnapshotting: true),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Finder page1 = find.text('Page 1');
|
||||||
|
final Finder page2 = find.text('Page 2');
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
// Exiting route should be snapshotted.
|
||||||
|
expect(isTransitioningWithSnapshotting(tester, page1), isTrue);
|
||||||
|
|
||||||
|
// Entering route should be snapshotted.
|
||||||
|
expect(isTransitioningWithSnapshotting(tester, page2), isTrue);
|
||||||
|
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
// Exiting route should be snapshotted.
|
||||||
|
expect(isTransitioningWithSnapshotting(tester, page2), isTrue);
|
||||||
|
|
||||||
|
// Entering route should be snapshotted.
|
||||||
|
expect(isTransitioningWithSnapshotting(tester, page1), isTrue);
|
||||||
|
}, variant: TargetPlatformVariant.only(TargetPlatform.android), skip: kIsWeb); // [intended] rasterization is not used on the web.
|
||||||
|
|
||||||
|
testWidgets('ZoomPageTransitionsBuilder.allowSnapshotting can disable route snapshotting', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
boilerplate(themeAllowSnapshotting: false),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Finder page1 = find.text('Page 1');
|
||||||
|
final Finder page2 = find.text('Page 2');
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
// Exiting route should not be snapshotted.
|
||||||
|
expect(isTransitioningWithSnapshotting(tester, page1), isFalse);
|
||||||
|
|
||||||
|
// Entering route should not be snapshotted.
|
||||||
|
expect(isTransitioningWithSnapshotting(tester, page2), isFalse);
|
||||||
|
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
// Exiting route should not be snapshotted.
|
||||||
|
expect(isTransitioningWithSnapshotting(tester, page2), isFalse);
|
||||||
|
|
||||||
|
// Entering route should not be snapshotted.
|
||||||
|
expect(isTransitioningWithSnapshotting(tester, page1), isFalse);
|
||||||
|
}, variant: TargetPlatformVariant.only(TargetPlatform.android), skip: kIsWeb); // [intended] rasterization is not used on the web.
|
||||||
|
|
||||||
|
testWidgets('Setting PageRoute.allowSnapshotting to false overrides ZoomPageTransitionsBuilder.allowSnapshotting = true', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
boilerplate(
|
||||||
|
themeAllowSnapshotting: true,
|
||||||
|
secondRouteAllowSnapshotting: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Finder page1 = find.text('Page 1');
|
||||||
|
final Finder page2 = find.text('Page 2');
|
||||||
|
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
// First route should be snapshotted.
|
||||||
|
expect(isTransitioningWithSnapshotting(tester, page1), isTrue);
|
||||||
|
|
||||||
|
// Second route should not be snapshotted.
|
||||||
|
expect(isTransitioningWithSnapshotting(tester, page2), isFalse);
|
||||||
|
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
}, variant: TargetPlatformVariant.only(TargetPlatform.android), skip: kIsWeb); // [intended] rasterization is not used on the web.
|
||||||
|
|
||||||
|
testWidgets('_ZoomPageTransition only causes child widget built once', (WidgetTester tester) async {
|
||||||
// Regression test for https://github.com/flutter/flutter/issues/58345
|
// Regression test for https://github.com/flutter/flutter/issues/58345
|
||||||
|
|
||||||
int builtCount = 0;
|
int builtCount = 0;
|
||||||
|
Loading…
Reference in New Issue
Block a user