Fix janks and memory leaks in CupertinoPageTransition
and CupertinoFullscreenDialogTransition
(#146999)
This commit is contained in:
parent
9dac4eda90
commit
f3978c7a46
@ -435,7 +435,7 @@ class _CupertinoPageTransitionState extends State<CupertinoPageTransition> {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.primaryRouteAnimation != widget.primaryRouteAnimation
|
||||
|| oldWidget.secondaryRouteAnimation != widget.secondaryRouteAnimation
|
||||
|| oldWidget.child != widget.child || oldWidget.linearTransition != widget.linearTransition) {
|
||||
|| oldWidget.linearTransition != widget.linearTransition) {
|
||||
_disposeCurve();
|
||||
_setupAnimation();
|
||||
}
|
||||
@ -443,14 +443,17 @@ class _CupertinoPageTransitionState extends State<CupertinoPageTransition> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_disposeCurve();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _disposeCurve() {
|
||||
_primaryPositionCurve?.dispose();
|
||||
_secondaryPositionCurve?.dispose();
|
||||
_primaryShadowCurve?.dispose();
|
||||
_primaryPositionCurve = null;
|
||||
_secondaryPositionCurve = null;
|
||||
_primaryShadowCurve = null;
|
||||
}
|
||||
|
||||
void _setupAnimation() {
|
||||
@ -557,7 +560,6 @@ class _CupertinoFullscreenDialogTransitionState extends State<CupertinoFullscree
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.primaryRouteAnimation != widget.primaryRouteAnimation ||
|
||||
oldWidget.secondaryRouteAnimation != widget.secondaryRouteAnimation ||
|
||||
oldWidget.child != widget.child ||
|
||||
oldWidget.linearTransition != widget.linearTransition) {
|
||||
_disposeCurve();
|
||||
_setupAnimation();
|
||||
|
@ -644,7 +644,10 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Fullscreen route animates correct transform values over time', (WidgetTester tester) async {
|
||||
testWidgets('Fullscreen route animates correct transform values over time',
|
||||
// TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in]
|
||||
experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const <String>['CurvedAnimation']),
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
home: Builder(
|
||||
@ -712,11 +715,27 @@ void main() {
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(7.4, epsilon: 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(3, epsilon: 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(0, epsilon: 0.1));
|
||||
|
||||
// Give time to the animation to finish and update its status to
|
||||
// AnimationState.completed, so the reverse curved can be used in the next
|
||||
// step.
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 1));
|
||||
|
||||
// Exit animation
|
||||
await tester.tap(find.text('Close'));
|
||||
await tester.pump();
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(156.3, epsilon: 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(308.1, epsilon: 0.1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dy, moreOrLessEquals(411.03, epsilon: 0.1));
|
||||
|
||||
@ -818,10 +837,27 @@ void main() {
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-263.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-265.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-266.0, epsilon: 1.0));
|
||||
|
||||
// Give time to the animation to finish and update its status to
|
||||
// AnimationState.completed, so the reverse curved can be used in the next
|
||||
// step.
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 1));
|
||||
|
||||
// Exit animation
|
||||
await tester.tap(find.text('Close'));
|
||||
await tester.pump();
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-197.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-129.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-83.0, epsilon: 1.0));
|
||||
|
||||
@ -829,14 +865,145 @@ void main() {
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-0.0, epsilon: 1.0));
|
||||
}
|
||||
|
||||
testWidgets('CupertinoPageRoute has parallax when non fullscreenDialog route is pushed on top', (WidgetTester tester) async {
|
||||
testWidgets('CupertinoPageRoute has parallax when non fullscreenDialog route is pushed on top',
|
||||
// TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in]
|
||||
experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const <String>['CurvedAnimation']),
|
||||
(WidgetTester tester) async {
|
||||
await testParallax(tester, fromFullscreenDialog: false);
|
||||
});
|
||||
|
||||
testWidgets('FullscreenDialog CupertinoPageRoute has parallax when non fullscreenDialog route is pushed on top', (WidgetTester tester) async {
|
||||
testWidgets('FullscreenDialog CupertinoPageRoute has parallax when non fullscreenDialog route is pushed on top',
|
||||
// TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in]
|
||||
experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const <String>['CurvedAnimation']),
|
||||
(WidgetTester tester) async {
|
||||
await testParallax(tester, fromFullscreenDialog: true);
|
||||
});
|
||||
|
||||
group('Interrupted push', () {
|
||||
Future<void> testParallax(WidgetTester tester, {required bool fromFullscreenDialog}) async {
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
onGenerateRoute: (RouteSettings settings) => CupertinoPageRoute<void>(
|
||||
fullscreenDialog: fromFullscreenDialog,
|
||||
settings: settings,
|
||||
builder: (BuildContext context) {
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
const Placeholder(),
|
||||
CupertinoButton(
|
||||
child: const Text('Button'),
|
||||
onPressed: () {
|
||||
Navigator.push<void>(context, CupertinoPageRoute<void>(
|
||||
builder: (BuildContext context) {
|
||||
return CupertinoButton(
|
||||
child: const Text('Close'),
|
||||
onPressed: () {
|
||||
Navigator.pop<void>(context);
|
||||
},
|
||||
);
|
||||
},
|
||||
));
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Enter animation.
|
||||
await tester.tap(find.text('Button'));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(0.0, epsilon: 0.1));
|
||||
await tester.pump();
|
||||
|
||||
// The push animation duration is 500ms. We let it run for 400ms before
|
||||
// interrupting and popping it.
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-55.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-111.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-161.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-200.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-226.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-242.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-251.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-257.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-261.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-263.0, epsilon: 1.0));
|
||||
|
||||
// Exit animation
|
||||
await tester.tap(find.text('Close'));
|
||||
await tester.pump();
|
||||
|
||||
// When the push animation is interrupted, the forward curved is used for
|
||||
// the reversed animation to avoid discontinuities.
|
||||
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-261.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-257.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-251.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-242.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-226.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-200.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-161.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-111.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(-55.0, epsilon: 1.0));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 40));
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, moreOrLessEquals(0.0, epsilon: 1.0));
|
||||
}
|
||||
|
||||
testWidgets('CupertinoPageRoute has parallax when non fullscreenDialog route is pushed on top and gets popped before the end of the animation',
|
||||
// TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in]
|
||||
experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const <String>['CurvedAnimation']),
|
||||
(WidgetTester tester) async {
|
||||
await testParallax(tester, fromFullscreenDialog: false);
|
||||
});
|
||||
|
||||
testWidgets('FullscreenDialog CupertinoPageRoute has parallax when non fullscreenDialog route is pushed on top and gets popped before the end of the animation',
|
||||
// TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in]
|
||||
experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const <String>['CurvedAnimation']),
|
||||
(WidgetTester tester) async {
|
||||
await testParallax(tester, fromFullscreenDialog: true);
|
||||
});
|
||||
});
|
||||
|
||||
Future<void> testNoParallax(WidgetTester tester, {required bool fromFullscreenDialog}) async{
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
@ -918,15 +1085,24 @@ void main() {
|
||||
expect(tester.getTopLeft(find.byType(Placeholder)).dx, 0.0);
|
||||
}
|
||||
|
||||
testWidgets('CupertinoPageRoute has no parallax when fullscreenDialog route is pushed on top', (WidgetTester tester) async {
|
||||
testWidgets('CupertinoPageRoute has no parallax when fullscreenDialog route is pushed on top',
|
||||
// TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in]
|
||||
experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const <String>['CurvedAnimation']),
|
||||
(WidgetTester tester) async {
|
||||
await testNoParallax(tester, fromFullscreenDialog: false);
|
||||
});
|
||||
|
||||
testWidgets('FullscreenDialog CupertinoPageRoute has no parallax when fullscreenDialog route is pushed on top', (WidgetTester tester) async {
|
||||
testWidgets('FullscreenDialog CupertinoPageRoute has no parallax when fullscreenDialog route is pushed on top',
|
||||
// TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in]
|
||||
experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const <String>['CurvedAnimation']),
|
||||
(WidgetTester tester) async {
|
||||
await testNoParallax(tester, fromFullscreenDialog: true);
|
||||
});
|
||||
|
||||
testWidgets('Animated push/pop is not linear', (WidgetTester tester) async {
|
||||
testWidgets('Animated push/pop is not linear',
|
||||
// TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in]
|
||||
experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const <String>['CurvedAnimation']),
|
||||
(WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const CupertinoApp(
|
||||
home: Text('1'),
|
||||
@ -944,6 +1120,11 @@ void main() {
|
||||
tester.state<NavigatorState>(find.byType(Navigator)).push(route2);
|
||||
// The whole transition is 500ms based on CupertinoPageRoute.transitionDuration.
|
||||
// Break it up into small chunks.
|
||||
//
|
||||
// The screen width is 800.
|
||||
// The top left corner of the text 1 will go from 0 to -800 / 3 = - 266.67.
|
||||
// The top left corner of the text 2 will go from 800 to 0.
|
||||
|
||||
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
@ -961,8 +1142,14 @@ void main() {
|
||||
|
||||
// Finish the rest of the animation
|
||||
await tester.pump(const Duration(milliseconds: 350));
|
||||
// Give time to the animation to finish and update its status to
|
||||
// AnimationState.completed, so the reverse curved can be used in the next
|
||||
// step.
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 1));
|
||||
|
||||
tester.state<NavigatorState>(find.byType(Navigator)).pop();
|
||||
// The top left corner of the text 1 will go from -800 / 3 = - 266.67 to 0.
|
||||
// The top left corner of the text 2 will go from 0 to 800.
|
||||
await tester.pump();
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
expect(tester.getTopLeft(find.text('1')).dx, moreOrLessEquals(-197, epsilon: 1));
|
||||
|
Loading…
x
Reference in New Issue
Block a user