Commit db08afd0 authored by Shi-Hao Hong's avatar Shi-Hao Hong Committed by Flutter GitHub Bot

Allow for customizable ModalRoute barrierTween (#48345)

parent 1613a62e
...@@ -998,7 +998,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -998,7 +998,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// ///
/// If [barrierDismissible] is false, then tapping the barrier has no effect. /// If [barrierDismissible] is false, then tapping the barrier has no effect.
/// ///
/// If this getter would ever start returning a different color, /// If this getter would ever start returning a different value,
/// [changedInternalState] should be invoked so that the change can take /// [changedInternalState] should be invoked so that the change can take
/// effect. /// effect.
/// ///
...@@ -1062,7 +1062,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -1062,7 +1062,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// For example, when a dialog is on the screen, the page below the dialog is /// For example, when a dialog is on the screen, the page below the dialog is
/// usually darkened by the modal barrier. /// usually darkened by the modal barrier.
/// ///
/// If this getter would ever start returning a different color, /// If this getter would ever start returning a different label,
/// [changedInternalState] should be invoked so that the change can take /// [changedInternalState] should be invoked so that the change can take
/// effect. /// effect.
/// ///
...@@ -1073,6 +1073,32 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -1073,6 +1073,32 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// * [ModalBarrier], the widget that implements this feature. /// * [ModalBarrier], the widget that implements this feature.
String get barrierLabel; String get barrierLabel;
/// The curve that is used for animating the modal barrier in and out.
///
/// The modal barrier is the scrim that is rendered behind each route, which
/// generally prevents the user from interacting with the route below the
/// current route, and normally partially obscures such routes.
///
/// For example, when a dialog is on the screen, the page below the dialog is
/// usually darkened by the modal barrier.
///
/// While the route is animating into position, the color is animated from
/// transparent to the specified [barrierColor].
///
/// If this getter would ever start returning a different curve,
/// [changedInternalState] should be invoked so that the change can take
/// effect.
///
/// It defaults to [Curves.ease].
///
/// See also:
///
/// * [barrierColor], which determines the color that the modal transitions
/// to.
/// * [Curves] for a collection of common curves.
/// * [AnimatedModalBarrier], the widget that implements this feature.
Curve get barrierCurve => Curves.ease;
/// Whether the route should remain in memory when it is inactive. /// 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 /// If this is true, then the route is maintained, so that any futures it is
...@@ -1282,30 +1308,28 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -1282,30 +1308,28 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
final GlobalKey _subtreeKey = GlobalKey(); final GlobalKey _subtreeKey = GlobalKey();
final PageStorageBucket _storageBucket = PageStorageBucket(); final PageStorageBucket _storageBucket = PageStorageBucket();
static final Animatable<double> _easeCurveTween = CurveTween(curve: Curves.ease);
// one of the builders // one of the builders
OverlayEntry _modalBarrier; OverlayEntry _modalBarrier;
Widget _buildModalBarrier(BuildContext context) { Widget _buildModalBarrier(BuildContext context) {
Widget barrier; 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); assert(barrierColor != _kTransparent);
final Animation<Color> color = animation.drive( final Animation<Color> color = animation.drive(
ColorTween( ColorTween(
begin: _kTransparent, begin: _kTransparent,
end: barrierColor, // changedInternalState is called if this updates end: barrierColor, // changedInternalState is called if barrierColor updates
).chain(_easeCurveTween), ).chain(CurveTween(curve: barrierCurve)), // changedInternalState is called if barrierCurve updates
); );
barrier = AnimatedModalBarrier( barrier = AnimatedModalBarrier(
color: color, color: color,
dismissible: barrierDismissible, // changedInternalState is called if this updates dismissible: barrierDismissible, // changedInternalState is called if barrierDismissible updates
semanticsLabel: barrierLabel, // changedInternalState is called if this updates semanticsLabel: barrierLabel, // changedInternalState is called if barrierLabel updates
barrierSemanticsDismissible: semanticsDismissible, barrierSemanticsDismissible: semanticsDismissible,
); );
} else { } else {
barrier = ModalBarrier( barrier = ModalBarrier(
dismissible: barrierDismissible, // changedInternalState is called if this updates dismissible: barrierDismissible, // changedInternalState is called if barrierDismissible updates
semanticsLabel: barrierLabel, // changedInternalState is called if this updates semanticsLabel: barrierLabel, // changedInternalState is called if barrierLabel updates
barrierSemanticsDismissible: semanticsDismissible, barrierSemanticsDismissible: semanticsDismissible,
); );
} }
...@@ -1316,7 +1340,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -1316,7 +1340,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
); );
} }
return IgnorePointer( return IgnorePointer(
ignoring: animation.status == AnimationStatus.reverse || // changedInternalState is called when this updates ignoring: animation.status == AnimationStatus.reverse || // changedInternalState is called when animation.status updates
animation.status == AnimationStatus.dismissed, // dismissed is possible when doing a manual pop gesture animation.status == AnimationStatus.dismissed, // dismissed is possible when doing a manual pop gesture
child: barrier, child: barrier,
); );
......
...@@ -1034,6 +1034,133 @@ void main() { ...@@ -1034,6 +1034,133 @@ void main() {
expect(find.byKey(containerKey), findsNothing); expect(find.byKey(containerKey), findsNothing);
}); });
}); });
group('ModalRoute', () {
testWidgets('default 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<void>(
_TestDialogRouteWithCustomBarrierCurve<void>(
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<Color> modalBarrierAnimation;
modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
expect(modalBarrierAnimation.value, Colors.transparent);
await tester.pump(const Duration(milliseconds: 25));
modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
expect(
modalBarrierAnimation.value.alpha,
closeTo(_getExpectedBarrierTweenAlphaValue(0.25), 1.0),
);
await tester.pump(const Duration(milliseconds: 25));
modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
expect(
modalBarrierAnimation.value.alpha,
closeTo(_getExpectedBarrierTweenAlphaValue(0.50), 1.0),
);
await tester.pump(const Duration(milliseconds: 25));
modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
expect(
modalBarrierAnimation.value.alpha,
closeTo(_getExpectedBarrierTweenAlphaValue(0.75), 1.0),
);
await tester.pumpAndSettle();
modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(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<void>(
_TestDialogRouteWithCustomBarrierCurve<void>(
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<Color> modalBarrierAnimation;
modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
expect(modalBarrierAnimation.value, Colors.transparent);
await tester.pump(const Duration(milliseconds: 25));
modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
expect(
modalBarrierAnimation.value.alpha,
closeTo(_getExpectedBarrierTweenAlphaValue(0.25), 1.0),
);
await tester.pump(const Duration(milliseconds: 25));
modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
expect(
modalBarrierAnimation.value.alpha,
closeTo(_getExpectedBarrierTweenAlphaValue(0.50), 1.0),
);
await tester.pump(const Duration(milliseconds: 25));
modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
expect(
modalBarrierAnimation.value.alpha,
closeTo(_getExpectedBarrierTweenAlphaValue(0.75), 1.0),
);
await tester.pumpAndSettle();
modalBarrierAnimation = tester.widget<AnimatedModalBarrier>(animatedModalBarrier).color;
expect(modalBarrierAnimation.value, Colors.black);
});
});
} }
double _getOpacity(GlobalKey key, WidgetTester tester) { double _getOpacity(GlobalKey key, WidgetTester tester) {
...@@ -1089,3 +1216,43 @@ class DialogObserver extends NavigatorObserver { ...@@ -1089,3 +1216,43 @@ class DialogObserver extends NavigatorObserver {
super.didPush(route, previousRoute); super.didPush(route, previousRoute);
} }
} }
class _TestDialogRouteWithCustomBarrierCurve<T> extends PopupRoute<T> {
_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<double> animation, Animation<double> secondaryAnimation) {
return Semantics(
child: _child,
scopesRoute: true,
explicitChildNodes: true,
);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment