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 [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
|
|||||||
/// 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
|
|||||||
/// * [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
|
|||||||
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
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
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() {
|
|||||||
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 {
|
|||||||
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user