Allow for customizable ModalRoute barrierTween (#48345)
This commit is contained in:
parent
1613a62e85
commit
db08afd07f
@ -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 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
|
||||
/// effect.
|
||||
///
|
||||
@ -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
|
||||
/// 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
|
||||
/// effect.
|
||||
///
|
||||
@ -1073,6 +1073,32 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
|
||||
/// * [ModalBarrier], the widget that implements this feature.
|
||||
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.
|
||||
///
|
||||
/// 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
|
||||
final GlobalKey _subtreeKey = GlobalKey();
|
||||
final PageStorageBucket _storageBucket = PageStorageBucket();
|
||||
|
||||
static final Animatable<double> _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> 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<T> extends TransitionRoute<T> with LocalHistoryRoute<T
|
||||
);
|
||||
}
|
||||
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
|
||||
child: barrier,
|
||||
);
|
||||
|
@ -1034,6 +1034,133 @@ void main() {
|
||||
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) {
|
||||
@ -1089,3 +1216,43 @@ class DialogObserver extends NavigatorObserver {
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user