Commit f8a80b1d authored by Jason Simmons's avatar Jason Simmons Committed by GitHub

Notify a Scrollable that the associated animation has stopped (#5209)

This also required changing the AnimationController state transition
logic to signal completion of the animation during the tick that
finishes the simulation.

Fixes https://github.com/flutter/flutter/issues/3675
parent b87cc8b1
......@@ -53,8 +53,9 @@ class AnimationController extends Animation<double>
this.upperBound: 1.0
}) {
assert(upperBound >= lowerBound);
_value = (value ?? lowerBound).clamp(lowerBound, upperBound);
_direction = _AnimationDirection.forward;
_ticker = new Ticker(_tick);
_internalSetValue(value ?? lowerBound);
}
/// Creates an animation controller with no upper or lower bound for its value.
......@@ -71,10 +72,11 @@ class AnimationController extends Animation<double>
this.duration,
this.debugLabel
}) : lowerBound = double.NEGATIVE_INFINITY,
upperBound = double.INFINITY,
_value = value {
upperBound = double.INFINITY {
assert(value != null);
_direction = _AnimationDirection.forward;
_ticker = new Ticker(_tick);
_internalSetValue(value);
}
/// The value at which this animation is deemed to be dismissed.
......@@ -119,11 +121,23 @@ class AnimationController extends Animation<double>
set value(double newValue) {
assert(newValue != null);
stop();
_value = newValue.clamp(lowerBound, upperBound);
_internalSetValue(newValue);
notifyListeners();
_checkStatusChanged();
}
void _internalSetValue(double newValue) {
_value = newValue.clamp(lowerBound, upperBound);
if (_value == lowerBound) {
_status = AnimationStatus.dismissed;
} else if (_value == upperBound) {
_status = AnimationStatus.completed;
} else
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.forward :
AnimationStatus.reverse;
}
/// The amount of time that has passed between the time the animation started and the most recent tick of the animation.
///
/// If the controller is not animating, the last elapsed duration is null.
......@@ -136,29 +150,22 @@ class AnimationController extends Animation<double>
_AnimationDirection _direction;
@override
AnimationStatus get status {
if (!isAnimating && value == upperBound)
return AnimationStatus.completed;
if (!isAnimating && value == lowerBound)
return AnimationStatus.dismissed;
return _direction == _AnimationDirection.forward ?
AnimationStatus.forward :
AnimationStatus.reverse;
}
AnimationStatus get status => _status;
AnimationStatus _status;
/// Starts running this animation forwards (towards the end).
Future<Null> forward({ double from }) {
_direction = _AnimationDirection.forward;
if (from != null)
value = from;
_direction = _AnimationDirection.forward;
return animateTo(upperBound);
}
/// Starts running this animation in reverse (towards the beginning).
Future<Null> reverse({ double from }) {
_direction = _AnimationDirection.reverse;
if (from != null)
value = from;
_direction = _AnimationDirection.reverse;
return animateTo(lowerBound);
}
......@@ -174,6 +181,9 @@ class AnimationController extends Animation<double>
stop();
if (simulationDuration == Duration.ZERO) {
assert(value == target);
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.completed :
AnimationStatus.dismissed;
_checkStatusChanged();
return new Future<Null>.value();
}
......@@ -215,6 +225,9 @@ class AnimationController extends Animation<double>
_lastElapsedDuration = Duration.ZERO;
_value = simulation.x(0.0).clamp(lowerBound, upperBound);
Future<Null> result = _ticker.start();
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.forward :
AnimationStatus.reverse;
_checkStatusChanged();
return result;
}
......@@ -245,8 +258,12 @@ class AnimationController extends Animation<double>
_lastElapsedDuration = elapsed;
double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND;
_value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound);
if (_simulation.isDone(elapsedInSeconds))
if (_simulation.isDone(elapsedInSeconds)) {
_status = (_direction == _AnimationDirection.forward) ?
AnimationStatus.completed :
AnimationStatus.dismissed;
stop();
}
notifyListeners();
_checkStatusChanged();
}
......
......@@ -249,7 +249,8 @@ class ScrollableState<T extends Scrollable> extends State<T> {
void initState() {
super.initState();
_controller = new AnimationController.unbounded()
..addListener(_handleAnimationChanged);
..addListener(_handleAnimationChanged)
..addStatusListener(_handleAnimationStatusChanged);
_scrollOffset = PageStorage.of(context)?.readState(context) ?? config.initialScrollOffset ?? 0.0;
}
......@@ -365,6 +366,11 @@ class ScrollableState<T extends Scrollable> extends State<T> {
_setScrollOffset(_controller.value);
}
void _handleAnimationStatusChanged(AnimationStatus status) {
if (!_controller.isAnimating)
_simulation = null;
}
void _setScrollOffset(double newScrollOffset, { DragUpdateDetails details }) {
if (_scrollOffset == newScrollOffset)
return;
......
......@@ -132,6 +132,26 @@ void main() {
expect(controller.value, equals(0.0));
});
test('Forward only from value', () {
AnimationController controller = new AnimationController(
duration: const Duration(milliseconds: 100)
);
List<double> valueLog = <double>[];
List<AnimationStatus> statusLog = <AnimationStatus>[];
controller
..addStatusListener((AnimationStatus status) {
statusLog.add(status);
})
..addListener(() {
valueLog.add(controller.value);
});
controller.forward(from: 0.2);
expect(statusLog, equals(<AnimationStatus>[ AnimationStatus.forward ]));
expect(valueLog, equals(<double>[ 0.2 ]));
expect(controller.value, equals(0.2));
});
test('Can fling to upper and lower bounds', () {
AnimationController controller = new AnimationController(
duration: const Duration(milliseconds: 100)
......
// Copyright 2016 The Chromium 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_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
import 'test_widgets.dart';
void main() {
testWidgets('simultaneously dispose a widget and end the scroll animation', (WidgetTester tester) async {
List<Widget> textWidgets = <Widget>[];
for (int i = 0; i < 250; i++)
textWidgets.add(new Text('$i'));
await tester.pumpWidget(new FlipWidget(
left: new Block(children: textWidgets),
right: new Container()
));
await tester.fling(find.byType(Scrollable), new Offset(0.0, -200.0), 1000.0);
await tester.pump();
FlipWidgetState flipWidget = tester.state(find.byType(FlipWidget));
flipWidget.flip();
await tester.pump(new Duration(hours: 5));
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment