mirror of
https://github.com/flutter/flutter
synced 2024-10-13 19:52:53 +00:00
Material Bottom Sheet Reveal/Dismiss animation uses a curved animation (#51122)
This commit is contained in:
parent
f3018c378a
commit
ec64f93fdd
|
@ -1657,6 +1657,10 @@ class Curves {
|
|||
/// animation to finish, and the negative effects of motion are minimized.
|
||||
///
|
||||
/// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_out_slow_in.mp4}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [standardEasing], the name for this curve in the Material specification.
|
||||
static const Cubic fastOutSlowIn = Cubic(0.4, 0.0, 0.2, 1.0);
|
||||
|
||||
/// A cubic animation curve that starts quickly, slows down, and then ends
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:ui' show lerpDouble;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
@ -10,16 +11,25 @@ import 'package:flutter/widgets.dart';
|
|||
|
||||
import 'bottom_sheet_theme.dart';
|
||||
import 'colors.dart';
|
||||
import 'curves.dart';
|
||||
import 'debug.dart';
|
||||
import 'material.dart';
|
||||
import 'material_localizations.dart';
|
||||
import 'scaffold.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
const Duration _bottomSheetDuration = Duration(milliseconds: 200);
|
||||
const Duration _bottomSheetEnterDuration = Duration(milliseconds: 250);
|
||||
const Duration _bottomSheetExitDuration = Duration(milliseconds: 200);
|
||||
const Curve _modalBottomSheetCurve = decelerateEasing;
|
||||
const double _minFlingVelocity = 700.0;
|
||||
const double _closeProgressThreshold = 0.5;
|
||||
|
||||
typedef BottomSheetDragStartHandler = void Function(DragStartDetails details);
|
||||
typedef BottomSheetDragEndHandler = void Function(
|
||||
DragEndDetails details, {
|
||||
bool isClosing,
|
||||
});
|
||||
|
||||
/// A material design bottom sheet.
|
||||
///
|
||||
/// There are two kinds of bottom sheets in material design:
|
||||
|
@ -57,6 +67,8 @@ class BottomSheet extends StatefulWidget {
|
|||
Key key,
|
||||
this.animationController,
|
||||
this.enableDrag = true,
|
||||
this.onDragStart,
|
||||
this.onDragEnd,
|
||||
this.backgroundColor,
|
||||
this.elevation,
|
||||
this.shape,
|
||||
|
@ -95,6 +107,21 @@ class BottomSheet extends StatefulWidget {
|
|||
/// Default is true.
|
||||
final bool enableDrag;
|
||||
|
||||
/// Called when the user begins dragging the bottom sheet vertically, if
|
||||
/// [enableDrag] is true.
|
||||
///
|
||||
/// Would typically be used to change the bottom sheet animation curve so
|
||||
/// that it tracks the user's finger accurately.
|
||||
final BottomSheetDragStartHandler onDragStart;
|
||||
|
||||
/// Called when the user stops dragging the bottom sheet, if [enableDrag]
|
||||
/// is true.
|
||||
///
|
||||
/// Would typically be used to reset the bottom sheet animation curve, so
|
||||
/// that it animates non-linearly. Called before [onClosing] if the bottom
|
||||
/// sheet is closing.
|
||||
final BottomSheetDragEndHandler onDragEnd;
|
||||
|
||||
/// The bottom sheet's background color.
|
||||
///
|
||||
/// Defines the bottom sheet's [Material.color].
|
||||
|
@ -140,7 +167,8 @@ class BottomSheet extends StatefulWidget {
|
|||
/// animation controller could be provided.
|
||||
static AnimationController createAnimationController(TickerProvider vsync) {
|
||||
return AnimationController(
|
||||
duration: _bottomSheetDuration,
|
||||
duration: _bottomSheetEnterDuration,
|
||||
reverseDuration: _bottomSheetExitDuration,
|
||||
debugLabel: 'BottomSheet',
|
||||
vsync: vsync,
|
||||
);
|
||||
|
@ -158,6 +186,12 @@ class _BottomSheetState extends State<BottomSheet> {
|
|||
|
||||
bool get _dismissUnderway => widget.animationController.status == AnimationStatus.reverse;
|
||||
|
||||
void _handleDragStart(DragStartDetails details) {
|
||||
if (widget.onDragStart != null) {
|
||||
widget.onDragStart(details);
|
||||
}
|
||||
}
|
||||
|
||||
void _handleDragUpdate(DragUpdateDetails details) {
|
||||
assert(widget.enableDrag);
|
||||
if (_dismissUnderway)
|
||||
|
@ -169,21 +203,33 @@ class _BottomSheetState extends State<BottomSheet> {
|
|||
assert(widget.enableDrag);
|
||||
if (_dismissUnderway)
|
||||
return;
|
||||
bool isClosing = false;
|
||||
if (details.velocity.pixelsPerSecond.dy > _minFlingVelocity) {
|
||||
final double flingVelocity = -details.velocity.pixelsPerSecond.dy / _childHeight;
|
||||
if (widget.animationController.value > 0.0) {
|
||||
widget.animationController.fling(velocity: flingVelocity);
|
||||
}
|
||||
if (flingVelocity < 0.0) {
|
||||
widget.onClosing();
|
||||
isClosing = true;
|
||||
}
|
||||
} else if (widget.animationController.value < _closeProgressThreshold) {
|
||||
if (widget.animationController.value > 0.0)
|
||||
widget.animationController.fling(velocity: -1.0);
|
||||
widget.onClosing();
|
||||
isClosing = true;
|
||||
} else {
|
||||
widget.animationController.forward();
|
||||
}
|
||||
|
||||
if (widget.onDragEnd != null) {
|
||||
widget.onDragEnd(
|
||||
details,
|
||||
isClosing: isClosing,
|
||||
);
|
||||
}
|
||||
|
||||
if (isClosing) {
|
||||
widget.onClosing();
|
||||
}
|
||||
}
|
||||
|
||||
bool extentChanged(DraggableScrollableNotification notification) {
|
||||
|
@ -213,6 +259,7 @@ class _BottomSheetState extends State<BottomSheet> {
|
|||
),
|
||||
);
|
||||
return !widget.enableDrag ? bottomSheet : GestureDetector(
|
||||
onVerticalDragStart: _handleDragStart,
|
||||
onVerticalDragUpdate: _handleDragUpdate,
|
||||
onVerticalDragEnd: _handleDragEnd,
|
||||
child: bottomSheet,
|
||||
|
@ -283,6 +330,8 @@ class _ModalBottomSheet<T> extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
|
||||
ParametricCurve<double> animationCurve = _modalBottomSheetCurve;
|
||||
|
||||
String _getRouteLabel(MaterialLocalizations localizations) {
|
||||
switch (Theme.of(context).platform) {
|
||||
case TargetPlatform.iOS:
|
||||
|
@ -295,6 +344,19 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
|
|||
return null;
|
||||
}
|
||||
|
||||
void handleDragStart(DragStartDetails details) {
|
||||
// Allow the bottom sheet to track the user's finger accurately.
|
||||
animationCurve = Curves.linear;
|
||||
}
|
||||
|
||||
void handleDragEnd(DragEndDetails details, {bool isClosing}) {
|
||||
// Allow the bottom sheet to animate smoothly from its current position.
|
||||
animationCurve = _BottomSheetSuspendedCurve(
|
||||
widget.route.animation.value,
|
||||
curve: _modalBottomSheetCurve,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMediaQuery(context));
|
||||
|
@ -308,7 +370,9 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
|
|||
builder: (BuildContext context, Widget child) {
|
||||
// Disable the initial animation when accessible navigation is on so
|
||||
// that the semantics are added to the tree at the correct time.
|
||||
final double animationValue = mediaQuery.accessibleNavigation ? 1.0 : widget.route.animation.value;
|
||||
final double animationValue = animationCurve.transform(
|
||||
mediaQuery.accessibleNavigation ? 1.0 : widget.route.animation.value
|
||||
);
|
||||
return Semantics(
|
||||
scopesRoute: true,
|
||||
namesRoute: true,
|
||||
|
@ -330,6 +394,8 @@ class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
|
|||
shape: widget.shape,
|
||||
clipBehavior: widget.clipBehavior,
|
||||
enableDrag: widget.enableDrag,
|
||||
onDragStart: handleDragStart,
|
||||
onDragEnd: handleDragEnd,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -370,7 +436,10 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
|
|||
final bool enableDrag;
|
||||
|
||||
@override
|
||||
Duration get transitionDuration => _bottomSheetDuration;
|
||||
Duration get transitionDuration => _bottomSheetEnterDuration;
|
||||
|
||||
@override
|
||||
Duration get reverseTransitionDuration => _bottomSheetExitDuration;
|
||||
|
||||
@override
|
||||
bool get barrierDismissible => isDismissible;
|
||||
|
@ -383,7 +452,6 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
|
|||
|
||||
AnimationController _animationController;
|
||||
|
||||
|
||||
@override
|
||||
AnimationController createAnimationController() {
|
||||
assert(_animationController == null);
|
||||
|
@ -415,6 +483,63 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO(guidezpl): Look into making this public. A copy of this class is in scaffold.dart, for now.
|
||||
/// A curve that progresses linearly until a specified [startingPoint], at which
|
||||
/// point [curve] will begin. Unlike [Interval], [curve] will not start at zero,
|
||||
/// but will use [startingPoint] as the Y position.
|
||||
///
|
||||
/// For example, if [startingPoint] is set to `0.5`, and [curve] is set to
|
||||
/// [Curves.easeOut], then the bottom-left quarter of the curve will be a
|
||||
/// straight line, and the top-right quarter will contain the entire contents of
|
||||
/// [Curves.easeOut].
|
||||
///
|
||||
/// This is useful in situations where a widget must track the user's finger
|
||||
/// (which requires a linear animation), and afterwards can be flung using a
|
||||
/// curve specified with the [curve] argument, after the finger is released. In
|
||||
/// such a case, the value of [startingPoint] would be the progress of the
|
||||
/// animation at the time when the finger was released.
|
||||
///
|
||||
/// The [startingPoint] and [curve] arguments must not be null.
|
||||
class _BottomSheetSuspendedCurve extends ParametricCurve<double> {
|
||||
/// Creates a suspended curve.
|
||||
const _BottomSheetSuspendedCurve(
|
||||
this.startingPoint, {
|
||||
this.curve = Curves.easeOutCubic,
|
||||
}) : assert(startingPoint != null),
|
||||
assert(curve != null);
|
||||
|
||||
/// The progress value at which [curve] should begin.
|
||||
///
|
||||
/// This defaults to [Curves.easeOutCubic].
|
||||
final double startingPoint;
|
||||
|
||||
/// The curve to use when [startingPoint] is reached.
|
||||
final Curve curve;
|
||||
|
||||
@override
|
||||
double transform(double t) {
|
||||
assert(t >= 0.0 && t <= 1.0);
|
||||
assert(startingPoint >= 0.0 && startingPoint <= 1.0);
|
||||
|
||||
if (t < startingPoint) {
|
||||
return t;
|
||||
}
|
||||
|
||||
if (t == 1.0) {
|
||||
return t;
|
||||
}
|
||||
|
||||
final double curveProgress = (t - startingPoint) / (1 - startingPoint);
|
||||
final double transformed = curve.transform(curveProgress);
|
||||
return lerpDouble(startingPoint, 1, transformed);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '${describeIdentity(this)}($startingPoint, $curve)';
|
||||
}
|
||||
}
|
||||
|
||||
/// Shows a modal material design bottom sheet.
|
||||
///
|
||||
/// A modal bottom sheet is an alternative to a menu or a dialog and prevents
|
||||
|
@ -446,7 +571,7 @@ class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
|
|||
/// dismissed when user taps on the scrim.
|
||||
///
|
||||
/// The [enableDrag] parameter specifies whether the bottom sheet can be
|
||||
/// dragged up and down and dismissed by swiping downards.
|
||||
/// dragged up and down and dismissed by swiping downwards.
|
||||
///
|
||||
/// The optional [backgroundColor], [elevation], [shape], and [clipBehavior]
|
||||
/// parameters can be passed in to customize the appearance and behavior of
|
||||
|
|
36
packages/flutter/lib/src/material/curves.dart
Normal file
36
packages/flutter/lib/src/material/curves.dart
Normal file
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/animation.dart';
|
||||
|
||||
// The easing curves of the Material Library
|
||||
|
||||
/// The standard easing curve in the Material specification.
|
||||
///
|
||||
/// Elements that begin and end at rest use standard easing.
|
||||
/// They speed up quickly and slow down gradually, in order
|
||||
/// to emphasize the end of the transition.
|
||||
///
|
||||
/// See also:
|
||||
/// * <https://material.io/design/motion/speed.html#easing>
|
||||
const Curve standardEasing = Curves.fastOutSlowIn;
|
||||
|
||||
/// The accelerate easing curve in the Material specification.
|
||||
///
|
||||
/// Elements exiting a screen use acceleration easing,
|
||||
/// where they start at rest and end at peak velocity.
|
||||
///
|
||||
/// See also:
|
||||
/// * <https://material.io/design/motion/speed.html#easing>
|
||||
const Curve accelerateEasing = Cubic(0.4, 0.0, 1.0, 1.0);
|
||||
|
||||
/// The decelerate easing curve in the Material specification.
|
||||
///
|
||||
/// Incoming elements are animated using deceleration easing,
|
||||
/// which starts a transition at peak velocity (the fastest
|
||||
/// point of an element’s movement) and ends at rest.
|
||||
///
|
||||
/// See also:
|
||||
/// * <https://material.io/design/motion/speed.html#easing>
|
||||
const Curve decelerateEasing = Cubic(0.0, 0.0, 0.2, 1.0);
|
|
@ -9,6 +9,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' show lerpDouble;
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
@ -19,6 +20,7 @@ import 'app_bar.dart';
|
|||
import 'bottom_sheet.dart';
|
||||
import 'button_bar.dart';
|
||||
import 'colors.dart';
|
||||
import 'curves.dart';
|
||||
import 'divider.dart';
|
||||
import 'drawer.dart';
|
||||
import 'flexible_space_bar.dart';
|
||||
|
@ -40,6 +42,7 @@ import 'theme_data.dart';
|
|||
const FloatingActionButtonLocation _kDefaultFloatingActionButtonLocation = FloatingActionButtonLocation.endFloat;
|
||||
const FloatingActionButtonAnimator _kDefaultFloatingActionButtonAnimator = FloatingActionButtonAnimator.scaling;
|
||||
|
||||
const Curve _standardBottomSheetCurve = standardEasing;
|
||||
// When the top of the BottomSheet crosses this threshold, it will start to
|
||||
// shrink the FAB and show a scrim.
|
||||
const double _kBottomSheetDominatesPercentage = 0.3;
|
||||
|
@ -2562,6 +2565,63 @@ class ScaffoldFeatureController<T extends Widget, U> {
|
|||
final StateSetter setState;
|
||||
}
|
||||
|
||||
// TODO(guidezpl): Look into making this public. A copy of this class is in bottom_sheet.dart, for now.
|
||||
/// A curve that progresses linearly until a specified [startingPoint], at which
|
||||
/// point [curve] will begin. Unlike [Interval], [curve] will not start at zero,
|
||||
/// but will use [startingPoint] as the Y position.
|
||||
///
|
||||
/// For example, if [startingPoint] is set to `0.5`, and [curve] is set to
|
||||
/// [Curves.easeOut], then the bottom-left quarter of the curve will be a
|
||||
/// straight line, and the top-right quarter will contain the entire contents of
|
||||
/// [Curves.easeOut].
|
||||
///
|
||||
/// This is useful in situations where a widget must track the user's finger
|
||||
/// (which requires a linear animation), and afterwards can be flung using a
|
||||
/// curve specified with the [curve] argument, after the finger is released. In
|
||||
/// such a case, the value of [startingPoint] would be the progress of the
|
||||
/// animation at the time when the finger was released.
|
||||
///
|
||||
/// The [startingPoint] and [curve] arguments must not be null.
|
||||
class _BottomSheetSuspendedCurve extends ParametricCurve<double> {
|
||||
/// Creates a suspended curve.
|
||||
const _BottomSheetSuspendedCurve(
|
||||
this.startingPoint, {
|
||||
this.curve = Curves.easeOutCubic,
|
||||
}) : assert(startingPoint != null),
|
||||
assert(curve != null);
|
||||
|
||||
/// The progress value at which [curve] should begin.
|
||||
///
|
||||
/// This defaults to [Curves.easeOutCubic].
|
||||
final double startingPoint;
|
||||
|
||||
/// The curve to use when [startingPoint] is reached.
|
||||
final Curve curve;
|
||||
|
||||
@override
|
||||
double transform(double t) {
|
||||
assert(t >= 0.0 && t <= 1.0);
|
||||
assert(startingPoint >= 0.0 && startingPoint <= 1.0);
|
||||
|
||||
if (t < startingPoint) {
|
||||
return t;
|
||||
}
|
||||
|
||||
if (t == 1.0) {
|
||||
return t;
|
||||
}
|
||||
|
||||
final double curveProgress = (t - startingPoint) / (1 - startingPoint);
|
||||
final double transformed = curve.transform(curveProgress);
|
||||
return lerpDouble(startingPoint, 1, transformed);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '${describeIdentity(this)}($startingPoint, $curve)';
|
||||
}
|
||||
}
|
||||
|
||||
class _StandardBottomSheet extends StatefulWidget {
|
||||
const _StandardBottomSheet({
|
||||
Key key,
|
||||
|
@ -2593,6 +2653,8 @@ class _StandardBottomSheet extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _StandardBottomSheetState extends State<_StandardBottomSheet> {
|
||||
ParametricCurve<double> animationCurve = _standardBottomSheetCurve;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
@ -2617,6 +2679,19 @@ class _StandardBottomSheetState extends State<_StandardBottomSheet> {
|
|||
return null;
|
||||
}
|
||||
|
||||
void _handleDragStart(DragStartDetails details) {
|
||||
// Allow the bottom sheet to track the user's finger accurately.
|
||||
animationCurve = Curves.linear;
|
||||
}
|
||||
|
||||
void _handleDragEnd(DragEndDetails details, { bool isClosing }) {
|
||||
// Allow the bottom sheet to animate smoothly from its current position.
|
||||
animationCurve = _BottomSheetSuspendedCurve(
|
||||
widget.animationController.value,
|
||||
curve: _standardBottomSheetCurve,
|
||||
);
|
||||
}
|
||||
|
||||
void _handleStatusChange(AnimationStatus status) {
|
||||
if (status == AnimationStatus.dismissed && widget.onDismissed != null) {
|
||||
widget.onDismissed();
|
||||
|
@ -2662,7 +2737,7 @@ class _StandardBottomSheetState extends State<_StandardBottomSheet> {
|
|||
builder: (BuildContext context, Widget child) {
|
||||
return Align(
|
||||
alignment: AlignmentDirectional.topStart,
|
||||
heightFactor: widget.animationController.value,
|
||||
heightFactor: animationCurve.transform(widget.animationController.value),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
|
@ -2670,6 +2745,8 @@ class _StandardBottomSheetState extends State<_StandardBottomSheet> {
|
|||
BottomSheet(
|
||||
animationController: widget.animationController,
|
||||
enableDrag: widget.enableDrag,
|
||||
onDragStart: _handleDragStart,
|
||||
onDragEnd: _handleDragEnd,
|
||||
onClosing: widget.onClosing,
|
||||
builder: widget.builder,
|
||||
backgroundColor: widget.backgroundColor,
|
||||
|
|
|
@ -10,6 +10,21 @@ import 'package:flutter/gestures.dart';
|
|||
import '../widgets/semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
// Pumps and ensures that the BottomSheet animates non-linearly.
|
||||
Future<void> _checkNonLinearAnimation(WidgetTester tester) async {
|
||||
final Offset firstPosition = tester.getCenter(find.text('BottomSheet'));
|
||||
await tester.pump(const Duration(milliseconds: 30));
|
||||
final Offset secondPosition = tester.getCenter(find.text('BottomSheet'));
|
||||
await tester.pump(const Duration(milliseconds: 30));
|
||||
final Offset thirdPosition = tester.getCenter(find.text('BottomSheet'));
|
||||
|
||||
final double dyDelta1 = secondPosition.dy - firstPosition.dy;
|
||||
final double dyDelta2 = thirdPosition.dy - secondPosition.dy;
|
||||
|
||||
// If the animation were linear, these two values would be the same.
|
||||
expect(dyDelta1, isNot(closeTo(dyDelta2, 0.1)));
|
||||
}
|
||||
|
||||
testWidgets('Tapping on a modal BottomSheet should not dismiss it', (WidgetTester tester) async {
|
||||
BuildContext savedContext;
|
||||
|
||||
|
@ -115,6 +130,38 @@ void main() {
|
|||
expect(find.text('BottomSheet'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Verify that the BottomSheet animates non-linearly', (WidgetTester tester) async {
|
||||
BuildContext savedContext;
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: Builder(
|
||||
builder: (BuildContext context) {
|
||||
savedContext = context;
|
||||
return Container();
|
||||
},
|
||||
),
|
||||
));
|
||||
|
||||
await tester.pump();
|
||||
expect(find.text('BottomSheet'), findsNothing);
|
||||
|
||||
showModalBottomSheet<void>(
|
||||
context: savedContext,
|
||||
builder: (BuildContext context) => const Text('BottomSheet'),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
await _checkNonLinearAnimation(tester);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Tap above the bottom sheet to dismiss it.
|
||||
await tester.tapAt(const Offset(20.0, 20.0));
|
||||
await tester.pump();
|
||||
await _checkNonLinearAnimation(tester);
|
||||
await tester.pumpAndSettle(); // Bottom sheet dismiss animation.
|
||||
expect(find.text('BottomSheet'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Tapping outside a modal BottomSheet should not dismiss it when isDismissible=false', (WidgetTester tester) async {
|
||||
BuildContext savedContext;
|
||||
|
||||
|
|
|
@ -6,6 +6,21 @@ import 'package:flutter_test/flutter_test.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() {
|
||||
// Pumps and ensures that the BottomSheet animates non-linearly.
|
||||
Future<void> _checkNonLinearAnimation(WidgetTester tester) async {
|
||||
final Offset firstPosition = tester.getCenter(find.text('One'));
|
||||
await tester.pump(const Duration(milliseconds: 30));
|
||||
final Offset secondPosition = tester.getCenter(find.text('One'));
|
||||
await tester.pump(const Duration(milliseconds: 30));
|
||||
final Offset thirdPosition = tester.getCenter(find.text('One'));
|
||||
|
||||
final double dyDelta1 = secondPosition.dy - firstPosition.dy;
|
||||
final double dyDelta2 = thirdPosition.dy - secondPosition.dy;
|
||||
|
||||
// If the animation were linear, these two values would be the same.
|
||||
expect(dyDelta1, isNot(closeTo(dyDelta2, 0.1)));
|
||||
}
|
||||
|
||||
testWidgets('Verify that a BottomSheet can be rebuilt with ScaffoldFeatureController.setState()', (WidgetTester tester) async {
|
||||
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
PersistentBottomSheetController<void> bottomSheet;
|
||||
|
@ -97,6 +112,41 @@ void main() {
|
|||
expect(find.text('Two'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Verify that a BottomSheet animates non-linearly', (WidgetTester tester) async {
|
||||
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
await tester.pumpWidget(MaterialApp(
|
||||
home: Scaffold(
|
||||
key: scaffoldKey,
|
||||
body: const Center(child: Text('body')),
|
||||
),
|
||||
));
|
||||
|
||||
scaffoldKey.currentState.showBottomSheet<void>((BuildContext context) {
|
||||
return ListView(
|
||||
shrinkWrap: true,
|
||||
primary: false,
|
||||
children: <Widget>[
|
||||
Container(height: 100.0, child: const Text('One')),
|
||||
Container(height: 100.0, child: const Text('Two')),
|
||||
Container(height: 100.0, child: const Text('Three')),
|
||||
],
|
||||
);
|
||||
});
|
||||
await tester.pump();
|
||||
await _checkNonLinearAnimation(tester);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Two'), findsOneWidget);
|
||||
|
||||
await tester.drag(find.text('Two'), const Offset(0.0, 200.0));
|
||||
await _checkNonLinearAnimation(tester);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Two'), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('Verify that a scrollControlled BottomSheet can be dismissed', (WidgetTester tester) async {
|
||||
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
|
||||
|
||||
|
|
Loading…
Reference in a new issue