Commit f08b51b8 authored by Adam Barth's avatar Adam Barth

Merge pull request #2858 from abarth/physics_with_changing_behavior

Scrollable physics should be reasonable when sizes change
parents 1db09ffe f7f1259b
...@@ -124,6 +124,12 @@ class AnimationController extends Animation<double> ...@@ -124,6 +124,12 @@ class AnimationController extends Animation<double>
_checkStatusChanged(); _checkStatusChanged();
} }
/// 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;
Duration get lastElapsedDuration => _lastElapsedDuration;
Duration _lastElapsedDuration;
/// Whether this animation is currently animating in either the forward or reverse direction. /// Whether this animation is currently animating in either the forward or reverse direction.
bool get isAnimating => _ticker.isTicking; bool get isAnimating => _ticker.isTicking;
...@@ -205,6 +211,7 @@ class AnimationController extends Animation<double> ...@@ -205,6 +211,7 @@ class AnimationController extends Animation<double>
assert(simulation != null); assert(simulation != null);
assert(!isAnimating); assert(!isAnimating);
_simulation = simulation; _simulation = simulation;
_lastElapsedDuration = const Duration();
_value = simulation.x(0.0).clamp(lowerBound, upperBound); _value = simulation.x(0.0).clamp(lowerBound, upperBound);
Future<Null> result = _ticker.start(); Future<Null> result = _ticker.start();
_checkStatusChanged(); _checkStatusChanged();
...@@ -214,6 +221,7 @@ class AnimationController extends Animation<double> ...@@ -214,6 +221,7 @@ class AnimationController extends Animation<double>
/// Stops running this animation. /// Stops running this animation.
void stop() { void stop() {
_simulation = null; _simulation = null;
_lastElapsedDuration = null;
_ticker.stop(); _ticker.stop();
} }
...@@ -233,6 +241,7 @@ class AnimationController extends Animation<double> ...@@ -233,6 +241,7 @@ class AnimationController extends Animation<double>
} }
void _tick(Duration elapsed) { void _tick(Duration elapsed) {
_lastElapsedDuration = elapsed;
double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND; double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND;
_value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound); _value = _simulation.x(elapsedInSeconds).clamp(lowerBound, upperBound);
if (_simulation.isDone(elapsedInSeconds)) if (_simulation.isDone(elapsedInSeconds))
......
...@@ -768,7 +768,7 @@ class _TabBarState<T> extends ScrollableState<TabBar<T>> implements TabBarSelect ...@@ -768,7 +768,7 @@ class _TabBarState<T> extends ScrollableState<TabBar<T>> implements TabBarSelect
} }
void _updateScrollBehavior() { void _updateScrollBehavior() {
scrollTo(scrollBehavior.updateExtents( didUpdateScrollBehavior(scrollBehavior.updateExtents(
containerExtent: config.scrollDirection == Axis.vertical ? _viewportSize.height : _viewportSize.width, containerExtent: config.scrollDirection == Axis.vertical ? _viewportSize.height : _viewportSize.width,
contentExtent: _tabWidths.reduce((double sum, double width) => sum + width), contentExtent: _tabWidths.reduce((double sum, double width) => sum + width),
scrollOffset: scrollOffset scrollOffset: scrollOffset
...@@ -943,11 +943,11 @@ class _TabBarViewState<T> extends PageableListState<TabBarView<T>> implements Ta ...@@ -943,11 +943,11 @@ class _TabBarViewState<T> extends PageableListState<TabBarView<T>> implements Ta
void _updateScrollBehaviorForSelectedIndex(int selectedIndex) { void _updateScrollBehaviorForSelectedIndex(int selectedIndex) {
if (selectedIndex == 0) { if (selectedIndex == 0) {
scrollTo(scrollBehavior.updateExtents(contentExtent: 2.0, containerExtent: 1.0, scrollOffset: 0.0)); didUpdateScrollBehavior(scrollBehavior.updateExtents(contentExtent: 2.0, containerExtent: 1.0, scrollOffset: 0.0));
} else if (selectedIndex == _tabCount - 1) { } else if (selectedIndex == _tabCount - 1) {
scrollTo(scrollBehavior.updateExtents(contentExtent: 2.0, containerExtent: 1.0, scrollOffset: 1.0)); didUpdateScrollBehavior(scrollBehavior.updateExtents(contentExtent: 2.0, containerExtent: 1.0, scrollOffset: 1.0));
} else { } else {
scrollTo(scrollBehavior.updateExtents(contentExtent: 3.0, containerExtent: 1.0, scrollOffset: 1.0)); didUpdateScrollBehavior(scrollBehavior.updateExtents(contentExtent: 3.0, containerExtent: 1.0, scrollOffset: 1.0));
} }
} }
......
...@@ -235,7 +235,7 @@ class RawInputLineState extends ScrollableState<RawInputLine> { ...@@ -235,7 +235,7 @@ class RawInputLineState extends ScrollableState<RawInputLine> {
// render object via our return value. // render object via our return value.
_containerWidth = dimensions.containerSize.width; _containerWidth = dimensions.containerSize.width;
_contentWidth = dimensions.contentSize.width; _contentWidth = dimensions.contentSize.width;
scrollTo(scrollBehavior.updateExtents( didUpdateScrollBehavior(scrollBehavior.updateExtents(
contentExtent: _contentWidth, contentExtent: _contentWidth,
containerExtent: _containerWidth, containerExtent: _containerWidth,
// Set the scroll offset to match the content width so that the // Set the scroll offset to match the content width so that the
......
...@@ -147,7 +147,7 @@ class PageableListState<T extends PageableList> extends ScrollableState<T> { ...@@ -147,7 +147,7 @@ class PageableListState<T extends PageableList> extends ScrollableState<T> {
void _updateScrollBehavior() { void _updateScrollBehavior() {
config.scrollableListPainter?.contentExtent = _itemCount.toDouble(); config.scrollableListPainter?.contentExtent = _itemCount.toDouble();
scrollTo(scrollBehavior.updateExtents( didUpdateScrollBehavior(scrollBehavior.updateExtents(
contentExtent: _itemCount.toDouble(), contentExtent: _itemCount.toDouble(),
containerExtent: 1.0, containerExtent: 1.0,
scrollOffset: scrollOffset scrollOffset: scrollOffset
......
...@@ -226,10 +226,12 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -226,10 +226,12 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
_scrollOffset = PageStorage.of(context)?.readState(context) ?? config.initialScrollOffset ?? 0.0; _scrollOffset = PageStorage.of(context)?.readState(context) ?? config.initialScrollOffset ?? 0.0;
} }
Simulation _simulation;
AnimationController _controller; AnimationController _controller;
@override @override
void dispose() { void dispose() {
_simulation = null;
_controller.stop(); _controller.stop();
super.dispose(); super.dispose();
} }
...@@ -358,6 +360,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -358,6 +360,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
return new Future<Null>.value(); return new Future<Null>.value();
if (duration == null) { if (duration == null) {
_simulation = null;
_controller.stop(); _controller.stop();
_setScrollOffset(newScrollOffset); _setScrollOffset(newScrollOffset);
return new Future<Null>.value(); return new Future<Null>.value();
...@@ -368,12 +371,27 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -368,12 +371,27 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
} }
Future<Null> _animateTo(double newScrollOffset, Duration duration, Curve curve) { Future<Null> _animateTo(double newScrollOffset, Duration duration, Curve curve) {
_simulation = null;
_controller.stop(); _controller.stop();
_controller.value = scrollOffset; _controller.value = scrollOffset;
_startScroll(); _startScroll();
return _controller.animateTo(newScrollOffset, duration: duration, curve: curve).then(_endScroll); return _controller.animateTo(newScrollOffset, duration: duration, curve: curve).then(_endScroll);
} }
void didUpdateScrollBehavior(double newScrollOffset) {
if (newScrollOffset == _scrollOffset)
return;
if (_numberOfInProgressScrolls > 0) {
if (_simulation != null) {
double dx = _simulation.dx(_controller.lastElapsedDuration.inMicroseconds / Duration.MICROSECONDS_PER_SECOND);
// TODO(abarth): We should be consistent about the units we use for velocity (i.e., per second).
_startToEndAnimation(dx / Duration.MILLISECONDS_PER_SECOND);
}
return;
}
scrollTo(newScrollOffset);
}
/// Fling the scroll offset with the given velocity. /// Fling the scroll offset with the given velocity.
/// ///
/// Calling this function starts a physics-based animation of the scroll /// Calling this function starts a physics-based animation of the scroll
...@@ -390,17 +408,18 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -390,17 +408,18 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
/// Calling this function starts a physics-based animation of the scroll /// Calling this function starts a physics-based animation of the scroll
/// offset either to a snap point or to within the scrolling bounds. The /// offset either to a snap point or to within the scrolling bounds. The
/// physics simulation used is determined by the scroll behavior. /// physics simulation used is determined by the scroll behavior.
Future<Null> settleScrollOffset() { Future<Null> settleScrollOffset() {
return _startToEndAnimation(0.0); return _startToEndAnimation(0.0);
} }
Future<Null> _startToEndAnimation(double scrollVelocity) { Future<Null> _startToEndAnimation(double scrollVelocity) {
_simulation = null;
_controller.stop(); _controller.stop();
Simulation simulation = _createSnapSimulation(scrollVelocity) ?? _createFlingSimulation(scrollVelocity); _simulation = _createSnapSimulation(scrollVelocity) ?? _createFlingSimulation(scrollVelocity);
if (simulation == null) if (_simulation == null)
return new Future<Null>.value(); return new Future<Null>.value();
_startScroll(); _startScroll();
return _controller.animateWith(simulation).then(_endScroll); return _controller.animateWith(_simulation).then(_endScroll);
} }
/// Whether this scrollable should attempt to snap scroll offsets. /// Whether this scrollable should attempt to snap scroll offsets.
...@@ -471,6 +490,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -471,6 +490,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
} }
void _handleDragDown(_) { void _handleDragDown(_) {
_simulation = null;
_controller.stop(); _controller.stop();
} }
...@@ -653,7 +673,7 @@ class _ScrollableViewportState extends ScrollableState<ScrollableViewport> { ...@@ -653,7 +673,7 @@ class _ScrollableViewportState extends ScrollableState<ScrollableViewport> {
// render object via our return value. // render object via our return value.
_viewportSize = config.scrollDirection == Axis.vertical ? dimensions.containerSize.height : dimensions.containerSize.width; _viewportSize = config.scrollDirection == Axis.vertical ? dimensions.containerSize.height : dimensions.containerSize.width;
_childSize = config.scrollDirection == Axis.vertical ? dimensions.contentSize.height : dimensions.contentSize.width; _childSize = config.scrollDirection == Axis.vertical ? dimensions.contentSize.height : dimensions.contentSize.width;
scrollTo(scrollBehavior.updateExtents( didUpdateScrollBehavior(scrollBehavior.updateExtents(
contentExtent: _childSize, contentExtent: _childSize,
containerExtent: _viewportSize, containerExtent: _viewportSize,
scrollOffset: scrollOffset scrollOffset: scrollOffset
...@@ -819,7 +839,7 @@ class ScrollableMixedWidgetListState extends ScrollableState<ScrollableMixedWidg ...@@ -819,7 +839,7 @@ class ScrollableMixedWidgetListState extends ScrollableState<ScrollableMixedWidg
// setState() callback because we are called during layout and all // setState() callback because we are called during layout and all
// we're updating is the new offset, which we are providing to the // we're updating is the new offset, which we are providing to the
// render object via our return value. // render object via our return value.
scrollTo(scrollBehavior.updateExtents( didUpdateScrollBehavior(scrollBehavior.updateExtents(
contentExtent: dimensions.contentSize.height, contentExtent: dimensions.contentSize.height,
containerExtent: dimensions.containerSize.height, containerExtent: dimensions.containerSize.height,
scrollOffset: scrollOffset scrollOffset: scrollOffset
......
...@@ -50,7 +50,7 @@ class _ScrollableGridState extends ScrollableState<ScrollableGrid> { ...@@ -50,7 +50,7 @@ class _ScrollableGridState extends ScrollableState<ScrollableGrid> {
void _handleExtentsChanged(double contentExtent, double containerExtent) { void _handleExtentsChanged(double contentExtent, double containerExtent) {
setState(() { setState(() {
scrollTo(scrollBehavior.updateExtents( didUpdateScrollBehavior(scrollBehavior.updateExtents(
contentExtent: contentExtent, contentExtent: contentExtent,
containerExtent: containerExtent, containerExtent: containerExtent,
scrollOffset: scrollOffset scrollOffset: scrollOffset
......
...@@ -55,7 +55,7 @@ class _ScrollableListState extends ScrollableState<ScrollableList> { ...@@ -55,7 +55,7 @@ class _ScrollableListState extends ScrollableState<ScrollableList> {
void _handleExtentsChanged(double contentExtent, double containerExtent) { void _handleExtentsChanged(double contentExtent, double containerExtent) {
config.scrollableListPainter?.contentExtent = contentExtent; config.scrollableListPainter?.contentExtent = contentExtent;
setState(() { setState(() {
scrollTo(scrollBehavior.updateExtents( didUpdateScrollBehavior(scrollBehavior.updateExtents(
contentExtent: config.itemsWrap ? double.INFINITY : contentExtent, contentExtent: config.itemsWrap ? double.INFINITY : contentExtent,
containerExtent: containerExtent, containerExtent: containerExtent,
scrollOffset: scrollOffset scrollOffset: scrollOffset
...@@ -338,7 +338,7 @@ class _ScrollableLazyListState extends ScrollableState<ScrollableLazyList> { ...@@ -338,7 +338,7 @@ class _ScrollableLazyListState extends ScrollableState<ScrollableLazyList> {
void _handleExtentsChanged(double contentExtent, double containerExtent) { void _handleExtentsChanged(double contentExtent, double containerExtent) {
config.scrollableListPainter?.contentExtent = contentExtent; config.scrollableListPainter?.contentExtent = contentExtent;
setState(() { setState(() {
scrollTo(scrollBehavior.updateExtents( didUpdateScrollBehavior(scrollBehavior.updateExtents(
contentExtent: contentExtent, contentExtent: contentExtent,
containerExtent: containerExtent, containerExtent: containerExtent,
scrollOffset: scrollOffset scrollOffset: scrollOffset
......
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