DropDownMenu should animation away when interrupted (#4485)
If you tap outside the drop down menu while its animating in, we should animate it away smoothly. Previously, we jumped to the reverseCurve, which made the menu disappear immediately. Now we hold the animations as state, which means we keep their _curveDirection property and don't switch curves unless the animation actually finishes. Also, fix a subtle bug in CurvedAnimation whereby we'd never set the _curveDirection if we didn't see a status change in the parent animation. Now we initialize _curveDirection based on the current value of the parent's status. Fixes #4379
This commit is contained in:
parent
4e3e40a174
commit
2be1eb480f
@ -300,8 +300,8 @@ class ReverseAnimation extends Animation<double>
|
||||
/// [CurvedAnimation] is useful when you want to apply a non-linear [Curve] to
|
||||
/// an animation object wrapped in the [CurvedAnimation].
|
||||
///
|
||||
/// For example, the following code snippet shows how you can apply a curve to a linear animation
|
||||
/// produced by an [AnimationController]:
|
||||
/// For example, the following code snippet shows how you can apply a curve to a
|
||||
/// linear animation produced by an [AnimationController]:
|
||||
///
|
||||
/// ``` dart
|
||||
/// final AnimationController controller =
|
||||
@ -309,9 +309,10 @@ class ReverseAnimation extends Animation<double>
|
||||
/// final CurvedAnimation animation =
|
||||
/// new CurvedAnimation(parent: controller, curve: Curves.ease);
|
||||
///```
|
||||
/// Depending on the given curve, the output of the [CurvedAnimation] could have a wider range
|
||||
/// than its input. For example, elastic curves such as [Curves.elasticIn] will significantly
|
||||
/// overshoot or undershoot the default range of 0.0 to 1.0.
|
||||
/// Depending on the given curve, the output of the [CurvedAnimation] could have
|
||||
/// a wider range than its input. For example, elastic curves such as
|
||||
/// [Curves.elasticIn] will significantly overshoot or undershoot the default
|
||||
/// range of 0.0 to 1.0.
|
||||
///
|
||||
/// If you want to apply a [Curve] to a [Tween], consider using [CurveTween].
|
||||
class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<double> {
|
||||
@ -325,7 +326,8 @@ class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<do
|
||||
}) {
|
||||
assert(parent != null);
|
||||
assert(curve != null);
|
||||
parent.addStatusListener(_handleStatusChanged);
|
||||
_updateCurveDirection(parent.status);
|
||||
parent.addStatusListener(_updateCurveDirection);
|
||||
}
|
||||
|
||||
/// The animation to which this animation applies a curve.
|
||||
@ -337,6 +339,16 @@ class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<do
|
||||
|
||||
/// The curve to use in the reverse direction.
|
||||
///
|
||||
/// If the parent animation changes direction without first reaching the
|
||||
/// [AnimationStatus.completed] or [AnimationStatus.dismissed] status, the
|
||||
/// [CurvedAnimation] stays on the same curve (albeit in the opposite
|
||||
/// direction) to avoid visual discontinuities.
|
||||
///
|
||||
/// If you use a non-null [reverseCurve], you might want to hold this object
|
||||
/// in a [State] object rather than recreating it each time your widget builds
|
||||
/// in order to take advantage of the state in this object that avoids visual
|
||||
/// discontinuities.
|
||||
///
|
||||
/// If this field is null, uses [curve] in both directions.
|
||||
Curve reverseCurve;
|
||||
|
||||
@ -347,7 +359,7 @@ class CurvedAnimation extends Animation<double> with AnimationWithParentMixin<do
|
||||
/// a animation is used to animate.
|
||||
AnimationStatus _curveDirection;
|
||||
|
||||
void _handleStatusChanged(AnimationStatus status) {
|
||||
void _updateCurveDirection(AnimationStatus status) {
|
||||
switch (status) {
|
||||
case AnimationStatus.dismissed:
|
||||
case AnimationStatus.completed:
|
||||
|
@ -70,14 +70,41 @@ class _DropDownMenuPainter extends CustomPainter {
|
||||
}
|
||||
}
|
||||
|
||||
class _DropDownMenu<T> extends StatusTransitionWidget {
|
||||
class _DropDownMenu<T> extends StatefulWidget {
|
||||
_DropDownMenu({
|
||||
Key key,
|
||||
_DropDownRoute<T> route
|
||||
}) : route = route, super(key: key, animation: route.animation);
|
||||
}) : route = route, super(key: key);
|
||||
|
||||
final _DropDownRoute<T> route;
|
||||
|
||||
@override
|
||||
_DropDownMenuState<T> createState() => new _DropDownMenuState<T>();
|
||||
}
|
||||
|
||||
class _DropDownMenuState<T> extends State<_DropDownMenu<T>> {
|
||||
CurvedAnimation _fadeOpacity;
|
||||
CurvedAnimation _resize;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// We need to hold these animations as state because of their curve
|
||||
// direction. When the route's animation reverses, if we were to recreate
|
||||
// the CurvedAnimation objects in build, we'd lose
|
||||
// CurvedAnimation._curveDirection.
|
||||
_fadeOpacity = new CurvedAnimation(
|
||||
parent: config.route.animation,
|
||||
curve: const Interval(0.0, 0.25),
|
||||
reverseCurve: const Interval(0.75, 1.0)
|
||||
);
|
||||
_resize = new CurvedAnimation(
|
||||
parent: config.route.animation,
|
||||
curve: const Interval(0.25, 0.5),
|
||||
reverseCurve: const Step(0.0)
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// The menu is shown in three stages (unit timing in brackets):
|
||||
@ -89,17 +116,17 @@ class _DropDownMenu<T> extends StatusTransitionWidget {
|
||||
// When the menu is dismissed we just fade the entire thing out
|
||||
// in the first 0.25s.
|
||||
|
||||
final _DropDownRoute<T> route = config.route;
|
||||
final double unit = 0.5 / (route.items.length + 1.5);
|
||||
final List<Widget> children = <Widget>[];
|
||||
for (int itemIndex = 0; itemIndex < route.items.length; ++itemIndex) {
|
||||
CurvedAnimation opacity;
|
||||
Interval reverseCurve = const Interval(0.75, 1.0);
|
||||
if (itemIndex == route.selectedIndex) {
|
||||
opacity = new CurvedAnimation(parent: route.animation, curve: const Step(0.0), reverseCurve: reverseCurve);
|
||||
opacity = new CurvedAnimation(parent: route.animation, curve: const Step(0.0));
|
||||
} else {
|
||||
final double start = (0.5 + (itemIndex + 1) * unit).clamp(0.0, 1.0);
|
||||
final double end = (start + 1.5 * unit).clamp(0.0, 1.0);
|
||||
opacity = new CurvedAnimation(parent: route.animation, curve: new Interval(start, end), reverseCurve: reverseCurve);
|
||||
opacity = new CurvedAnimation(parent: route.animation, curve: new Interval(start, end));
|
||||
}
|
||||
children.add(new FadeTransition(
|
||||
opacity: opacity,
|
||||
@ -117,21 +144,13 @@ class _DropDownMenu<T> extends StatusTransitionWidget {
|
||||
}
|
||||
|
||||
return new FadeTransition(
|
||||
opacity: new CurvedAnimation(
|
||||
parent: route.animation,
|
||||
curve: const Interval(0.0, 0.25),
|
||||
reverseCurve: const Interval(0.75, 1.0)
|
||||
),
|
||||
opacity: _fadeOpacity,
|
||||
child: new CustomPaint(
|
||||
painter: new _DropDownMenuPainter(
|
||||
color: Theme.of(context).canvasColor,
|
||||
elevation: route.elevation,
|
||||
selectedIndex: route.selectedIndex,
|
||||
resize: new CurvedAnimation(
|
||||
parent: route.animation,
|
||||
curve: const Interval(0.25, 0.5),
|
||||
reverseCurve: const Step(0.0)
|
||||
)
|
||||
resize: _resize
|
||||
),
|
||||
child: new Material(
|
||||
type: MaterialType.transparency,
|
||||
@ -456,8 +475,7 @@ class _DropDownButtonState<T> extends State<DropDownButton<T>> {
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasMaterial(context));
|
||||
final TextStyle style = _textStyle;
|
||||
if (_currentRoute != null)
|
||||
_currentRoute.style = style;
|
||||
_currentRoute?.style = style;
|
||||
Widget result = new DefaultTextStyle(
|
||||
style: style,
|
||||
child: new Row(
|
||||
|
Loading…
x
Reference in New Issue
Block a user