From db08afd07ff2c72ff9550d084849f01d95e096fb Mon Sep 17 00:00:00 2001 From: Shi-Hao Hong Date: Thu, 9 Jan 2020 19:28:01 -0800 Subject: [PATCH] Allow for customizable ModalRoute barrierTween (#48345) --- packages/flutter/lib/src/widgets/routes.dart | 48 +++-- .../flutter/test/widgets/routes_test.dart | 167 ++++++++++++++++++ 2 files changed, 203 insertions(+), 12 deletions(-) diff --git a/packages/flutter/lib/src/widgets/routes.dart b/packages/flutter/lib/src/widgets/routes.dart index 2ad8276c57..46527aea19 100644 --- a/packages/flutter/lib/src/widgets/routes.dart +++ b/packages/flutter/lib/src/widgets/routes.dart @@ -998,7 +998,7 @@ abstract class ModalRoute extends TransitionRoute with LocalHistoryRoute extends TransitionRoute with LocalHistoryRoute extends TransitionRoute with LocalHistoryRoute Curves.ease; + /// Whether the route should remain in memory when it is inactive. /// /// If this is true, then the route is maintained, so that any futures it is @@ -1282,30 +1308,28 @@ abstract class ModalRoute extends TransitionRoute with LocalHistoryRoute _easeCurveTween = CurveTween(curve: Curves.ease); - // one of the builders OverlayEntry _modalBarrier; Widget _buildModalBarrier(BuildContext context) { Widget barrier; - if (barrierColor != null && !offstage) { // changedInternalState is called if these update + if (barrierColor != null && !offstage) { // changedInternalState is called if barrierColor or offstage updates assert(barrierColor != _kTransparent); final Animation color = animation.drive( ColorTween( begin: _kTransparent, - end: barrierColor, // changedInternalState is called if this updates - ).chain(_easeCurveTween), + end: barrierColor, // changedInternalState is called if barrierColor updates + ).chain(CurveTween(curve: barrierCurve)), // changedInternalState is called if barrierCurve updates ); barrier = AnimatedModalBarrier( color: color, - dismissible: barrierDismissible, // changedInternalState is called if this updates - semanticsLabel: barrierLabel, // changedInternalState is called if this updates + dismissible: barrierDismissible, // changedInternalState is called if barrierDismissible updates + semanticsLabel: barrierLabel, // changedInternalState is called if barrierLabel updates barrierSemanticsDismissible: semanticsDismissible, ); } else { barrier = ModalBarrier( - dismissible: barrierDismissible, // changedInternalState is called if this updates - semanticsLabel: barrierLabel, // changedInternalState is called if this updates + dismissible: barrierDismissible, // changedInternalState is called if barrierDismissible updates + semanticsLabel: barrierLabel, // changedInternalState is called if barrierLabel updates barrierSemanticsDismissible: semanticsDismissible, ); } @@ -1316,7 +1340,7 @@ abstract class ModalRoute extends TransitionRoute with LocalHistoryRoute( + _TestDialogRouteWithCustomBarrierCurve( + child: const Text('Hello World'), + ) + ); + }, + ), + ); + } + ), + ), + )); + + final CurveTween _defaultBarrierTween = CurveTween(curve: Curves.ease); + int _getExpectedBarrierTweenAlphaValue(double t) { + return Color.getAlphaFromOpacity(_defaultBarrierTween.transform(t)); + } + + await tester.tap(find.text('X')); + await tester.pump(); + final Finder animatedModalBarrier = find.byType(AnimatedModalBarrier); + expect(animatedModalBarrier, findsOneWidget); + + Animation modalBarrierAnimation; + modalBarrierAnimation = tester.widget(animatedModalBarrier).color; + expect(modalBarrierAnimation.value, Colors.transparent); + + await tester.pump(const Duration(milliseconds: 25)); + modalBarrierAnimation = tester.widget(animatedModalBarrier).color; + expect( + modalBarrierAnimation.value.alpha, + closeTo(_getExpectedBarrierTweenAlphaValue(0.25), 1.0), + ); + + await tester.pump(const Duration(milliseconds: 25)); + modalBarrierAnimation = tester.widget(animatedModalBarrier).color; + expect( + modalBarrierAnimation.value.alpha, + closeTo(_getExpectedBarrierTweenAlphaValue(0.50), 1.0), + ); + + await tester.pump(const Duration(milliseconds: 25)); + modalBarrierAnimation = tester.widget(animatedModalBarrier).color; + expect( + modalBarrierAnimation.value.alpha, + closeTo(_getExpectedBarrierTweenAlphaValue(0.75), 1.0), + ); + + await tester.pumpAndSettle(); + modalBarrierAnimation = tester.widget(animatedModalBarrier).color; + expect(modalBarrierAnimation.value, Colors.black); + }); + + testWidgets('custom barrierCurve', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + home: Material( + child: Builder( + builder: (BuildContext context) { + return Center( + child: RaisedButton( + child: const Text('X'), + onPressed: () { + Navigator.of(context).push( + _TestDialogRouteWithCustomBarrierCurve( + child: const Text('Hello World'), + barrierCurve: Curves.linear, + ), + ); + }, + ), + ); + }, + ), + ), + )); + + final CurveTween _customBarrierTween = CurveTween(curve: Curves.linear); + int _getExpectedBarrierTweenAlphaValue(double t) { + return Color.getAlphaFromOpacity(_customBarrierTween.transform(t)); + } + + await tester.tap(find.text('X')); + await tester.pump(); + final Finder animatedModalBarrier = find.byType(AnimatedModalBarrier); + expect(animatedModalBarrier, findsOneWidget); + + Animation modalBarrierAnimation; + modalBarrierAnimation = tester.widget(animatedModalBarrier).color; + expect(modalBarrierAnimation.value, Colors.transparent); + + await tester.pump(const Duration(milliseconds: 25)); + modalBarrierAnimation = tester.widget(animatedModalBarrier).color; + expect( + modalBarrierAnimation.value.alpha, + closeTo(_getExpectedBarrierTweenAlphaValue(0.25), 1.0), + ); + + await tester.pump(const Duration(milliseconds: 25)); + modalBarrierAnimation = tester.widget(animatedModalBarrier).color; + expect( + modalBarrierAnimation.value.alpha, + closeTo(_getExpectedBarrierTweenAlphaValue(0.50), 1.0), + ); + + await tester.pump(const Duration(milliseconds: 25)); + modalBarrierAnimation = tester.widget(animatedModalBarrier).color; + expect( + modalBarrierAnimation.value.alpha, + closeTo(_getExpectedBarrierTweenAlphaValue(0.75), 1.0), + ); + + await tester.pumpAndSettle(); + modalBarrierAnimation = tester.widget(animatedModalBarrier).color; + expect(modalBarrierAnimation.value, Colors.black); + }); + }); } double _getOpacity(GlobalKey key, WidgetTester tester) { @@ -1089,3 +1216,43 @@ class DialogObserver extends NavigatorObserver { super.didPush(route, previousRoute); } } + +class _TestDialogRouteWithCustomBarrierCurve extends PopupRoute { + _TestDialogRouteWithCustomBarrierCurve({ + @required Widget child, + Curve barrierCurve, + }) : _barrierCurve = barrierCurve, + _child = child; + + final Widget _child; + + @override + bool get barrierDismissible => true; + + @override + String get barrierLabel => null; + + @override + Color get barrierColor => Colors.black; // easier value to test against + + @override + Curve get barrierCurve { + if (_barrierCurve == null) { + return super.barrierCurve; + } + return _barrierCurve; + } + final Curve _barrierCurve; + + @override + Duration get transitionDuration => const Duration(milliseconds: 100); // easier value to test against + + @override + Widget buildPage(BuildContext context, Animation animation, Animation secondaryAnimation) { + return Semantics( + child: _child, + scopesRoute: true, + explicitChildNodes: true, + ); + } +}