fixes CupertinoFullscreenDialogTransition
leaks (#147168)
This commit is contained in:
parent
de411a5976
commit
cc9ac7d13c
@ -506,44 +506,96 @@ class _CupertinoPageTransitionState extends State<CupertinoPageTransition> {
|
|||||||
///
|
///
|
||||||
/// For example, used when creating a new calendar event by bringing in the next
|
/// For example, used when creating a new calendar event by bringing in the next
|
||||||
/// screen from the bottom.
|
/// screen from the bottom.
|
||||||
class CupertinoFullscreenDialogTransition extends StatelessWidget {
|
class CupertinoFullscreenDialogTransition extends StatefulWidget {
|
||||||
/// Creates an iOS-style transition used for summoning fullscreen dialogs.
|
/// Creates an iOS-style transition used for summoning fullscreen dialogs.
|
||||||
///
|
///
|
||||||
|
const CupertinoFullscreenDialogTransition({
|
||||||
|
super.key,
|
||||||
|
required this.primaryRouteAnimation,
|
||||||
|
required this.secondaryRouteAnimation,
|
||||||
|
required this.child,
|
||||||
|
required this.linearTransition,
|
||||||
|
});
|
||||||
|
|
||||||
/// * `primaryRouteAnimation` is a linear route animation from 0.0 to 1.0
|
/// * `primaryRouteAnimation` is a linear route animation from 0.0 to 1.0
|
||||||
/// when this screen is being pushed.
|
/// when this screen is being pushed.
|
||||||
|
final Animation<double> primaryRouteAnimation;
|
||||||
|
|
||||||
/// * `secondaryRouteAnimation` is a linear route animation from 0.0 to 1.0
|
/// * `secondaryRouteAnimation` is a linear route animation from 0.0 to 1.0
|
||||||
/// when another screen is being pushed on top of this one.
|
/// when another screen is being pushed on top of this one.
|
||||||
/// * `linearTransition` is whether to perform the secondary transition linearly.
|
final Animation<double> secondaryRouteAnimation;
|
||||||
|
|
||||||
|
/// * `linearTransition` is whether to perform the transitions linearly.
|
||||||
/// Used to precisely track back gesture drags.
|
/// Used to precisely track back gesture drags.
|
||||||
CupertinoFullscreenDialogTransition({
|
final bool linearTransition;
|
||||||
super.key,
|
|
||||||
required Animation<double> primaryRouteAnimation,
|
/// The widget below this widget in the tree.
|
||||||
required Animation<double> secondaryRouteAnimation,
|
final Widget child;
|
||||||
required this.child,
|
|
||||||
required bool linearTransition,
|
@override
|
||||||
}) : _positionAnimation = CurvedAnimation(
|
State<CupertinoFullscreenDialogTransition> createState() => _CupertinoFullscreenDialogTransitionState();
|
||||||
parent: primaryRouteAnimation,
|
}
|
||||||
|
|
||||||
|
class _CupertinoFullscreenDialogTransitionState extends State<CupertinoFullscreenDialogTransition> {
|
||||||
|
/// When this page is coming in to cover another page.
|
||||||
|
late Animation<Offset> _primaryPositionAnimation;
|
||||||
|
/// When this page is becoming covered by another page.
|
||||||
|
late Animation<Offset> _secondaryPositionAnimation;
|
||||||
|
/// Curve of primary page which is coming in to cover another page.
|
||||||
|
CurvedAnimation? _primaryPositionCurve;
|
||||||
|
/// Curve of secondary page which is becoming covered by another page.
|
||||||
|
CurvedAnimation? _secondaryPositionCurve;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_setupAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant CupertinoFullscreenDialogTransition oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (oldWidget.primaryRouteAnimation != widget.primaryRouteAnimation ||
|
||||||
|
oldWidget.secondaryRouteAnimation != widget.secondaryRouteAnimation ||
|
||||||
|
oldWidget.child != widget.child ||
|
||||||
|
oldWidget.linearTransition != widget.linearTransition) {
|
||||||
|
_disposeCurve();
|
||||||
|
_setupAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_disposeCurve();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _disposeCurve() {
|
||||||
|
_primaryPositionCurve?.dispose();
|
||||||
|
_secondaryPositionCurve?.dispose();
|
||||||
|
_primaryPositionCurve = null;
|
||||||
|
_secondaryPositionCurve = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _setupAnimation() {
|
||||||
|
_primaryPositionAnimation = (_primaryPositionCurve = CurvedAnimation(
|
||||||
|
parent: widget.primaryRouteAnimation,
|
||||||
curve: Curves.linearToEaseOut,
|
curve: Curves.linearToEaseOut,
|
||||||
// The curve must be flipped so that the reverse animation doesn't play
|
// The curve must be flipped so that the reverse animation doesn't play
|
||||||
// an ease-in curve, which iOS does not use.
|
// an ease-in curve, which iOS does not use.
|
||||||
reverseCurve: Curves.linearToEaseOut.flipped,
|
reverseCurve: Curves.linearToEaseOut.flipped,
|
||||||
).drive(_kBottomUpTween),
|
)).drive(_kBottomUpTween);
|
||||||
_secondaryPositionAnimation =
|
_secondaryPositionAnimation =
|
||||||
(linearTransition
|
(widget.linearTransition
|
||||||
? secondaryRouteAnimation
|
? widget.secondaryRouteAnimation
|
||||||
: CurvedAnimation(
|
: _secondaryPositionCurve = CurvedAnimation(
|
||||||
parent: secondaryRouteAnimation,
|
parent: widget.secondaryRouteAnimation,
|
||||||
curve: Curves.linearToEaseOut,
|
curve: Curves.linearToEaseOut,
|
||||||
reverseCurve: Curves.easeInToLinear,
|
reverseCurve: Curves.easeInToLinear,
|
||||||
)
|
)
|
||||||
).drive(_kMiddleLeftTween);
|
).drive(_kMiddleLeftTween);
|
||||||
|
}
|
||||||
|
|
||||||
final Animation<Offset> _positionAnimation;
|
|
||||||
// When this page is becoming covered by another page.
|
|
||||||
final Animation<Offset> _secondaryPositionAnimation;
|
|
||||||
|
|
||||||
/// The widget below this widget in the tree.
|
|
||||||
final Widget child;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -554,8 +606,8 @@ class CupertinoFullscreenDialogTransition extends StatelessWidget {
|
|||||||
textDirection: textDirection,
|
textDirection: textDirection,
|
||||||
transformHitTests: false,
|
transformHitTests: false,
|
||||||
child: SlideTransition(
|
child: SlideTransition(
|
||||||
position: _positionAnimation,
|
position: _primaryPositionAnimation,
|
||||||
child: child,
|
child: widget.child,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
|
||||||
|
|
||||||
import '../widgets/semantics_tester.dart';
|
import '../widgets/semantics_tester.dart';
|
||||||
|
|
||||||
@ -2431,6 +2432,53 @@ void main() {
|
|||||||
expect(tester.getBottomRight(find.byType(Placeholder)).dx, 390.0);
|
expect(tester.getBottomRight(find.byType(Placeholder)).dx, 390.0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
// TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in]
|
||||||
|
experimentalLeakTesting: LeakTesting.settings.withTracked(classes: <String>['CurvedAnimation']),
|
||||||
|
'Fullscreen route does not leak CurveAnimation', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return CupertinoButton(
|
||||||
|
child: const Text('Button'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push<void>(context, CupertinoPageRoute<void>(
|
||||||
|
fullscreenDialog: true,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return Column(
|
||||||
|
children: <Widget>[
|
||||||
|
const Placeholder(),
|
||||||
|
CupertinoButton(
|
||||||
|
child: const Text('Close'),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop<void>(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Enter animation.
|
||||||
|
await tester.tap(find.text('Button'));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 400));
|
||||||
|
|
||||||
|
// Exit animation
|
||||||
|
await tester.tap(find.text('Close'));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 400));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockNavigatorObserver extends NavigatorObserver {
|
class MockNavigatorObserver extends NavigatorObserver {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user