mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Add showCupertinoDialog and showGeneralDialog (#20152)
This commit is contained in:
parent
e770685a4b
commit
345d939ee8
@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
@ -9,6 +11,9 @@ import 'package:flutter/widgets.dart';
|
|||||||
const double _kBackGestureWidth = 20.0;
|
const double _kBackGestureWidth = 20.0;
|
||||||
const double _kMinFlingVelocity = 1.0; // Screen widths per second.
|
const double _kMinFlingVelocity = 1.0; // Screen widths per second.
|
||||||
|
|
||||||
|
// Barrier color for a Cupertino modal barrier.
|
||||||
|
const Color _kModalBarrierColor = Color(0x6604040F);
|
||||||
|
|
||||||
// Offset from offscreen to the right to fully on screen.
|
// Offset from offscreen to the right to fully on screen.
|
||||||
final Tween<Offset> _kRightMiddleTween = new Tween<Offset>(
|
final Tween<Offset> _kRightMiddleTween = new Tween<Offset>(
|
||||||
begin: const Offset(1.0, 0.0),
|
begin: const Offset(1.0, 0.0),
|
||||||
@ -709,3 +714,77 @@ class _CupertinoEdgeShadowPainter extends BoxPainter {
|
|||||||
canvas.drawRect(rect, paint);
|
canvas.drawRect(rect, paint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildCupertinoDialogTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
|
||||||
|
final CurvedAnimation fadeAnimation = new CurvedAnimation(
|
||||||
|
parent: animation,
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
);
|
||||||
|
if (animation.status == AnimationStatus.reverse) {
|
||||||
|
return new FadeTransition(
|
||||||
|
opacity: fadeAnimation,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return new FadeTransition(
|
||||||
|
opacity: fadeAnimation,
|
||||||
|
child: ScaleTransition(
|
||||||
|
child: child,
|
||||||
|
scale: new Tween<double>(
|
||||||
|
begin: 1.2,
|
||||||
|
end: 1.0,
|
||||||
|
).animate(
|
||||||
|
new CurvedAnimation(
|
||||||
|
parent: animation,
|
||||||
|
curve: Curves.fastOutSlowIn,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays an iOS-style dialog above the current contents of the app, with
|
||||||
|
/// iOS-style entrance and exit animations, modal barrier color, and modal
|
||||||
|
/// barrier behavior (the dialog is not dismissible with a tap on the barrier).
|
||||||
|
///
|
||||||
|
/// This function takes a `builder` which typically builds a [CupertinoDialog]
|
||||||
|
/// or [CupertinoAlertDialog] widget. Content below the dialog is dimmed with a
|
||||||
|
/// [ModalBarrier]. The widget returned by the `builder` does not share a
|
||||||
|
/// context with the location that `showCupertinoDialog` is originally called
|
||||||
|
/// from. Use a [StatefulBuilder] or a custom [StatefulWidget] if the dialog
|
||||||
|
/// needs to update dynamically.
|
||||||
|
///
|
||||||
|
/// The `context` argument is used to look up the [Navigator] for the dialog.
|
||||||
|
/// It is only used when the method is called. Its corresponding widget can
|
||||||
|
/// be safely removed from the tree before the dialog is closed.
|
||||||
|
///
|
||||||
|
/// Returns a [Future] that resolves to the value (if any) that was passed to
|
||||||
|
/// [Navigator.pop] when the dialog was closed.
|
||||||
|
///
|
||||||
|
/// The dialog route created by this method is pushed to the root navigator.
|
||||||
|
/// If the application has multiple [Navigator] objects, it may be necessary to
|
||||||
|
/// call `Navigator.of(context, rootNavigator: true).pop(result)` to close the
|
||||||
|
/// dialog rather than just `Navigator.pop(context, result)`.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
/// * [CupertinoDialog], an iOS-style dialog.
|
||||||
|
/// * [CupertinoAlertDialog], an iOS-style alert dialog.
|
||||||
|
/// * [showDialog], which displays a Material-style dialog.
|
||||||
|
/// * [showGeneralDialog], which allows for customization of the dialog popup.
|
||||||
|
/// * <https://developer.apple.com/ios/human-interface-guidelines/views/alerts/>
|
||||||
|
Future<T> showCupertinoDialog<T>({
|
||||||
|
@required BuildContext context,
|
||||||
|
@required WidgetBuilder builder,
|
||||||
|
}) {
|
||||||
|
assert(builder != null);
|
||||||
|
return showGeneralDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
barrierColor: _kModalBarrierColor,
|
||||||
|
transitionDuration: const Duration(milliseconds: 300),
|
||||||
|
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
||||||
|
return builder(context);
|
||||||
|
},
|
||||||
|
transitionBuilder: _buildCupertinoDialogTransitions,
|
||||||
|
);
|
||||||
|
}
|
@ -543,70 +543,25 @@ class SimpleDialog extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DialogRoute<T> extends PopupRoute<T> {
|
Widget _buildMaterialDialogTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
|
||||||
_DialogRoute({
|
|
||||||
@required this.theme,
|
|
||||||
bool barrierDismissible = true,
|
|
||||||
this.barrierLabel,
|
|
||||||
@required this.child,
|
|
||||||
RouteSettings settings,
|
|
||||||
}) : assert(barrierDismissible != null),
|
|
||||||
_barrierDismissible = barrierDismissible,
|
|
||||||
super(settings: settings);
|
|
||||||
|
|
||||||
final Widget child;
|
|
||||||
final ThemeData theme;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Duration get transitionDuration => const Duration(milliseconds: 150);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get barrierDismissible => _barrierDismissible;
|
|
||||||
final bool _barrierDismissible;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Color get barrierColor => Colors.black54;
|
|
||||||
|
|
||||||
@override
|
|
||||||
final String barrierLabel;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
|
||||||
return new SafeArea(
|
|
||||||
child: new Builder(
|
|
||||||
builder: (BuildContext context) {
|
|
||||||
final Widget annotatedChild = new Semantics(
|
|
||||||
child: child,
|
|
||||||
scopesRoute: true,
|
|
||||||
explicitChildNodes: true,
|
|
||||||
);
|
|
||||||
return theme != null
|
|
||||||
? new Theme(data: theme, child: annotatedChild)
|
|
||||||
: annotatedChild;
|
|
||||||
}
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
|
|
||||||
return new FadeTransition(
|
return new FadeTransition(
|
||||||
opacity: new CurvedAnimation(
|
opacity: new CurvedAnimation(
|
||||||
parent: animation,
|
parent: animation,
|
||||||
curve: Curves.easeOut
|
curve: Curves.easeOut,
|
||||||
),
|
),
|
||||||
child: child
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Displays a dialog above the current contents of the app.
|
/// Displays a Material dialog above the current contents of the app, with
|
||||||
|
/// Material entrance and exit animations, modal barrier color, and modal
|
||||||
|
/// barrier behavior (dialog is dismissible with a tap on the barrier).
|
||||||
///
|
///
|
||||||
/// This function takes a `builder` which typically builds a [Dialog] widget.
|
/// This function takes a `builder` which typically builds a [Dialog] widget.
|
||||||
/// Content below the dialog is dimmed with a [ModalBarrier]. This widget does
|
/// Content below the dialog is dimmed with a [ModalBarrier]. The widget
|
||||||
/// not share a context with the location that `showDialog` is originally
|
/// returned by the `builder` does not share a context with the location that
|
||||||
/// called from. Use a [StatefulBuilder] or a custom [StatefulWidget] if the
|
/// `showDialog` is originally called from. Use a [StatefulBuilder] or a
|
||||||
/// dialog needs to update dynamically.
|
/// custom [StatefulWidget] if the dialog needs to update dynamically.
|
||||||
///
|
///
|
||||||
/// The `context` argument is used to look up the [Navigator] and [Theme] for
|
/// The `context` argument is used to look up the [Navigator] and [Theme] for
|
||||||
/// the dialog. It is only used when the method is called. Its corresponding
|
/// the dialog. It is only used when the method is called. Its corresponding
|
||||||
@ -620,13 +575,15 @@ class _DialogRoute<T> extends PopupRoute<T> {
|
|||||||
/// The dialog route created by this method is pushed to the root navigator.
|
/// The dialog route created by this method is pushed to the root navigator.
|
||||||
/// If the application has multiple [Navigator] objects, it may be necessary to
|
/// If the application has multiple [Navigator] objects, it may be necessary to
|
||||||
/// call `Navigator.of(context, rootNavigator: true).pop(result)` to close the
|
/// call `Navigator.of(context, rootNavigator: true).pop(result)` to close the
|
||||||
/// dialog rather just 'Navigator.pop(context, result)`.
|
/// dialog rather than just `Navigator.pop(context, result)`.
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
/// * [AlertDialog], for dialogs that have a row of buttons below a body.
|
/// * [AlertDialog], for dialogs that have a row of buttons below a body.
|
||||||
/// * [SimpleDialog], which handles the scrolling of the contents and does
|
/// * [SimpleDialog], which handles the scrolling of the contents and does
|
||||||
/// not show buttons below its body.
|
/// not show buttons below its body.
|
||||||
/// * [Dialog], on which [SimpleDialog] and [AlertDialog] are based.
|
/// * [Dialog], on which [SimpleDialog] and [AlertDialog] are based.
|
||||||
|
/// * [showCupertinoDialog], which displays an iOS-style dialog.
|
||||||
|
/// * [showGeneralDialog], which allows for customization of the dialog popup.
|
||||||
/// * <https://material.google.com/components/dialogs.html>
|
/// * <https://material.google.com/components/dialogs.html>
|
||||||
Future<T> showDialog<T>({
|
Future<T> showDialog<T>({
|
||||||
@required BuildContext context,
|
@required BuildContext context,
|
||||||
@ -639,10 +596,25 @@ Future<T> showDialog<T>({
|
|||||||
WidgetBuilder builder,
|
WidgetBuilder builder,
|
||||||
}) {
|
}) {
|
||||||
assert(child == null || builder == null);
|
assert(child == null || builder == null);
|
||||||
return Navigator.of(context, rootNavigator: true).push(new _DialogRoute<T>(
|
return showGeneralDialog(
|
||||||
child: child ?? new Builder(builder: builder),
|
context: context,
|
||||||
theme: Theme.of(context, shadowThemeOnly: true),
|
pageBuilder: (BuildContext buildContext, Animation<double> animation, Animation<double> secondaryAnimation) {
|
||||||
|
final ThemeData theme = Theme.of(context, shadowThemeOnly: true);
|
||||||
|
final Widget pageChild = child ?? new Builder(builder: builder);
|
||||||
|
return new SafeArea(
|
||||||
|
child: new Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return theme != null
|
||||||
|
? new Theme(data: theme, child: pageChild)
|
||||||
|
: pageChild;
|
||||||
|
}
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
barrierDismissible: barrierDismissible,
|
barrierDismissible: barrierDismissible,
|
||||||
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
|
||||||
));
|
barrierColor: Colors.black54,
|
||||||
|
transitionDuration: const Duration(milliseconds: 150),
|
||||||
|
transitionBuilder: _buildMaterialDialogTransitions,
|
||||||
|
);
|
||||||
}
|
}
|
@ -44,18 +44,6 @@ abstract class PageRoute<T> extends ModalRoute<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signature for the [PageRouteBuilder] function that builds the route's
|
|
||||||
/// primary contents.
|
|
||||||
///
|
|
||||||
/// See [ModalRoute.buildPage] for complete definition of the parameters.
|
|
||||||
typedef Widget RoutePageBuilder(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation);
|
|
||||||
|
|
||||||
/// Signature for the [PageRouteBuilder] function that builds the route's
|
|
||||||
/// transitions.
|
|
||||||
///
|
|
||||||
/// See [ModalRoute.buildTransitions] for complete definition of the parameters.
|
|
||||||
typedef Widget RouteTransitionsBuilder(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child);
|
|
||||||
|
|
||||||
Widget _defaultTransitionsBuilder(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
|
Widget _defaultTransitionsBuilder(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
|
||||||
return child;
|
return child;
|
||||||
}
|
}
|
||||||
|
@ -1379,3 +1379,143 @@ abstract class RouteAware {
|
|||||||
/// longer visible.
|
/// longer visible.
|
||||||
void didPushNext() { }
|
void didPushNext() { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _DialogRoute<T> extends PopupRoute<T> {
|
||||||
|
_DialogRoute({
|
||||||
|
@required RoutePageBuilder pageBuilder,
|
||||||
|
bool barrierDismissible = true,
|
||||||
|
String barrierLabel,
|
||||||
|
Color barrierColor = const Color(0x80000000),
|
||||||
|
Duration transitionDuration = const Duration(milliseconds: 200),
|
||||||
|
RouteTransitionsBuilder transitionBuilder,
|
||||||
|
RouteSettings settings,
|
||||||
|
}) : assert(barrierDismissible != null),
|
||||||
|
_pageBuilder = pageBuilder,
|
||||||
|
_barrierDismissible = barrierDismissible,
|
||||||
|
_barrierLabel = barrierLabel,
|
||||||
|
_barrierColor = barrierColor,
|
||||||
|
_transitionDuration = transitionDuration,
|
||||||
|
_transitionBuilder = transitionBuilder,
|
||||||
|
super(settings: settings);
|
||||||
|
|
||||||
|
final RoutePageBuilder _pageBuilder;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get barrierDismissible => _barrierDismissible;
|
||||||
|
final bool _barrierDismissible;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get barrierLabel => _barrierLabel;
|
||||||
|
final String _barrierLabel;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Color get barrierColor => _barrierColor;
|
||||||
|
final Color _barrierColor;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Duration get transitionDuration => _transitionDuration;
|
||||||
|
final Duration _transitionDuration;
|
||||||
|
|
||||||
|
final RouteTransitionsBuilder _transitionBuilder;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
|
||||||
|
return new Semantics(
|
||||||
|
child: _pageBuilder(context, animation, secondaryAnimation),
|
||||||
|
scopesRoute: true,
|
||||||
|
explicitChildNodes: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget buildTransitions(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
|
||||||
|
if (_transitionBuilder == null) {
|
||||||
|
return new FadeTransition(
|
||||||
|
opacity: new CurvedAnimation(
|
||||||
|
parent: animation,
|
||||||
|
curve: Curves.linear,
|
||||||
|
),
|
||||||
|
child: child);
|
||||||
|
} // Some default transition
|
||||||
|
return _transitionBuilder(context, animation, secondaryAnimation, child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays a dialog above the current contents of the app.
|
||||||
|
///
|
||||||
|
/// This function allows for customization of aspects of the dialog popup.
|
||||||
|
///
|
||||||
|
/// This function takes a `pageBuilder` which is used to build the primary
|
||||||
|
/// content of the route (typically a dialog widget). Content below the dialog
|
||||||
|
/// is dimmed with a [ModalBarrier]. The widget returned by the `pageBuilder`
|
||||||
|
/// does not share a context with the location that `showGeneralDialog` is
|
||||||
|
/// originally called from. Use a [StatefulBuilder] or a custom
|
||||||
|
/// [StatefulWidget] if the dialog needs to update dynamically. The
|
||||||
|
/// `pageBuilder` argument can not be null.
|
||||||
|
///
|
||||||
|
/// The `context` argument is used to look up the [Navigator] for the dialog.
|
||||||
|
/// It is only used when the method is called. Its corresponding widget can
|
||||||
|
/// be safely removed from the tree before the dialog is closed.
|
||||||
|
///
|
||||||
|
/// The `barrierDismissible` argument is used to determine whether this route
|
||||||
|
/// can be dismissed by tapping the modal barrier. This argument defaults
|
||||||
|
/// to true. If `barrierDismissible` is true, a non-null `barrierLabel` must be
|
||||||
|
/// provided.
|
||||||
|
///
|
||||||
|
/// The `barrierLabel` argument is the semantic label used for a dismissible
|
||||||
|
/// barrier. This argument defaults to "Dismiss".
|
||||||
|
///
|
||||||
|
/// The `barrierColor` argument is the color used for the modal barrier. This
|
||||||
|
/// argument defaults to `Color(0x80000000)`.
|
||||||
|
///
|
||||||
|
/// The `transitionDuration` argument is used to determine how long it takes
|
||||||
|
/// for the route to arrive on or leave off the screen. This argument defaults
|
||||||
|
/// to 200 milliseconds.
|
||||||
|
///
|
||||||
|
/// The `transitionBuilder` argument is used to define how the route arrives on
|
||||||
|
/// and leaves off the screen. By default, the transition is a linear fade of
|
||||||
|
/// the page's contents.
|
||||||
|
///
|
||||||
|
/// Returns a [Future] that resolves to the value (if any) that was passed to
|
||||||
|
/// [Navigator.pop] when the dialog was closed.
|
||||||
|
///
|
||||||
|
/// The dialog route created by this method is pushed to the root navigator.
|
||||||
|
/// If the application has multiple [Navigator] objects, it may be necessary to
|
||||||
|
/// call `Navigator.of(context, rootNavigator: true).pop(result)` to close the
|
||||||
|
/// dialog rather than just `Navigator.pop(context, result)`.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
/// * [showDialog], which displays a Material-style dialog.
|
||||||
|
/// * [showCupertinoDialog], which displays an iOS-style dialog.
|
||||||
|
Future<T> showGeneralDialog<T>({
|
||||||
|
@required BuildContext context,
|
||||||
|
@required RoutePageBuilder pageBuilder,
|
||||||
|
bool barrierDismissible,
|
||||||
|
String barrierLabel,
|
||||||
|
Color barrierColor,
|
||||||
|
Duration transitionDuration,
|
||||||
|
RouteTransitionsBuilder transitionBuilder,
|
||||||
|
}) {
|
||||||
|
assert(pageBuilder != null);
|
||||||
|
assert(!barrierDismissible || barrierLabel != null);
|
||||||
|
return Navigator.of(context, rootNavigator: true).push(new _DialogRoute<T>(
|
||||||
|
pageBuilder: pageBuilder,
|
||||||
|
barrierDismissible: barrierDismissible,
|
||||||
|
barrierLabel: barrierLabel,
|
||||||
|
barrierColor: barrierColor,
|
||||||
|
transitionDuration: transitionDuration,
|
||||||
|
transitionBuilder: transitionBuilder,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Signature for the function that builds a route's primary contents.
|
||||||
|
/// Used in [PageRouteBuilder] and [showGeneralDialog].
|
||||||
|
///
|
||||||
|
/// See [ModalRoute.buildPage] for complete definition of the parameters.
|
||||||
|
typedef Widget RoutePageBuilder(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation);
|
||||||
|
|
||||||
|
/// Signature for the function that builds a route's transitions.
|
||||||
|
/// Used in [PageRouteBuilder] and [showGeneralDialog].
|
||||||
|
///
|
||||||
|
/// See [ModalRoute.buildTransitions] for complete definition of the parameters.
|
||||||
|
typedef Widget RouteTransitionsBuilder(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child);
|
||||||
|
@ -332,6 +332,176 @@ void main() {
|
|||||||
expect(scrollController.offset, 0.0);
|
expect(scrollController.offset, 0.0);
|
||||||
expect(find.widgetWithText(CupertinoDialogAction, 'One'), findsNothing);
|
expect(find.widgetWithText(CupertinoDialogAction, 'One'), findsNothing);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('ScaleTransition animation for showCupertinoDialog()', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new CupertinoApp(
|
||||||
|
home: new Center(
|
||||||
|
child: new Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return new CupertinoButton(
|
||||||
|
onPressed: () {
|
||||||
|
showCupertinoDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return new CupertinoAlertDialog(
|
||||||
|
title: const Text('The title'),
|
||||||
|
content: const Text('The content'),
|
||||||
|
actions: <Widget>[
|
||||||
|
const CupertinoDialogAction(
|
||||||
|
child: Text('Cancel'),
|
||||||
|
),
|
||||||
|
new CupertinoDialogAction(
|
||||||
|
isDestructiveAction: true,
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: const Text('Delete'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Text('Go'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.text('Go'));
|
||||||
|
|
||||||
|
// Enter animation.
|
||||||
|
await tester.pump();
|
||||||
|
Transform transform = tester.widget(find.byType(Transform));
|
||||||
|
expect(transform.transform[0], closeTo(1.2, 0.01));
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 50));
|
||||||
|
transform = tester.widget(find.byType(Transform));
|
||||||
|
expect(transform.transform[0], closeTo(1.182, 0.001));
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 50));
|
||||||
|
transform = tester.widget(find.byType(Transform));
|
||||||
|
expect(transform.transform[0], closeTo(1.108, 0.001));
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 50));
|
||||||
|
transform = tester.widget(find.byType(Transform));
|
||||||
|
expect(transform.transform[0], closeTo(1.044, 0.001));
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 50));
|
||||||
|
transform = tester.widget(find.byType(Transform));
|
||||||
|
expect(transform.transform[0], closeTo(1.015, 0.001));
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 50));
|
||||||
|
transform = tester.widget(find.byType(Transform));
|
||||||
|
expect(transform.transform[0], closeTo(1.003, 0.001));
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 50));
|
||||||
|
transform = tester.widget(find.byType(Transform));
|
||||||
|
expect(transform.transform[0], closeTo(1.000, 0.001));
|
||||||
|
|
||||||
|
await tester.tap(find.text('Delete'));
|
||||||
|
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pump(const Duration(milliseconds: 50));
|
||||||
|
|
||||||
|
// No scaling on exit animation.
|
||||||
|
expect(find.byType(Transform), findsNothing);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('FadeTransition animation for showCupertinoDialog()', (WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
new CupertinoApp(
|
||||||
|
home: new Center(
|
||||||
|
child: new Builder(
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return new CupertinoButton(
|
||||||
|
onPressed: () {
|
||||||
|
showCupertinoDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return new CupertinoAlertDialog(
|
||||||
|
title: const Text('The title'),
|
||||||
|
content: const Text('The content'),
|
||||||
|
actions: <Widget>[
|
||||||
|
const CupertinoDialogAction(
|
||||||
|
child: Text('Cancel'),
|
||||||
|
),
|
||||||
|
new CupertinoDialogAction(
|
||||||
|
isDestructiveAction: true,
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
child: const Text('Delete'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: const Text('Go'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.tap(find.text('Go'));
|
||||||
|
|
||||||
|
// Enter animation.
|
||||||
|
await tester.pump();
|
||||||
|
FadeTransition transition = tester.firstWidget(find.byType(FadeTransition));
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 25));
|
||||||
|
transition = tester.firstWidget(find.byType(FadeTransition));
|
||||||
|
expect(transition.opacity.value, closeTo(0.10, 0.001));
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 25));
|
||||||
|
transition = tester.firstWidget(find.byType(FadeTransition));
|
||||||
|
expect(transition.opacity.value, closeTo(0.156, 0.001));
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 25));
|
||||||
|
transition = tester.firstWidget(find.byType(FadeTransition));
|
||||||
|
expect(transition.opacity.value, closeTo(0.324, 0.001));
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 25));
|
||||||
|
transition = tester.firstWidget(find.byType(FadeTransition));
|
||||||
|
expect(transition.opacity.value, closeTo(0.606, 0.001));
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 25));
|
||||||
|
transition = tester.firstWidget(find.byType(FadeTransition));
|
||||||
|
expect(transition.opacity.value, closeTo(1.0, 0.001));
|
||||||
|
|
||||||
|
await tester.tap(find.text('Delete'));
|
||||||
|
|
||||||
|
// Exit animation, look at reverse FadeTransition.
|
||||||
|
await tester.pump(const Duration(milliseconds: 25));
|
||||||
|
transition = tester.widgetList(find.byType(FadeTransition)).elementAt(1);
|
||||||
|
expect(transition.opacity.value, closeTo(0.358, 0.001));
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 25));
|
||||||
|
transition = tester.widgetList(find.byType(FadeTransition)).elementAt(1);
|
||||||
|
expect(transition.opacity.value, closeTo(0.231, 0.001));
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 25));
|
||||||
|
transition = tester.widgetList(find.byType(FadeTransition)).elementAt(1);
|
||||||
|
expect(transition.opacity.value, closeTo(0.128, 0.001));
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 25));
|
||||||
|
transition = tester.widgetList(find.byType(FadeTransition)).elementAt(1);
|
||||||
|
expect(transition.opacity.value, closeTo(0.056, 0.001));
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 25));
|
||||||
|
transition = tester.widgetList(find.byType(FadeTransition)).elementAt(1);
|
||||||
|
expect(transition.opacity.value, closeTo(0.013, 0.001));
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 25));
|
||||||
|
transition = tester.widgetList(find.byType(FadeTransition)).elementAt(1);
|
||||||
|
expect(transition.opacity.value, closeTo(0.0, 0.001));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget boilerplate(Widget child) {
|
Widget boilerplate(Widget child) {
|
||||||
|
Loading…
Reference in New Issue
Block a user