From 70aa7795c74ca5b23bff91b500eb804cc9fd347a Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Thu, 12 Mar 2015 14:33:37 -0700 Subject: [PATCH] Scrollable should settle back to 0.0 This CL teaches Scrollable how to settle back to a scroll offset of 0.0 after a fling or a scroll. There's still some room for improvement: 1) Some of this logic should be factored out into the scroll curve object. 2) We don't produce the correct animation curves when we fling into the overscroll region because we wait for the fling velocity to reach zero before we start the settling animation. R=ojan@chromium.org, eseidel@chromium.org Review URL: https://codereview.chromium.org/1005753002 --- framework/animation/generator.dart | 13 ++++-- framework/components/ink_splash.dart | 7 +++- framework/components/scrollable.dart | 60 +++++++++++++++++++++++++--- 3 files changed, 68 insertions(+), 12 deletions(-) diff --git a/framework/animation/generator.dart b/framework/animation/generator.dart index 65c373f548a..065d1e5f656 100644 --- a/framework/animation/generator.dart +++ b/framework/animation/generator.dart @@ -61,14 +61,15 @@ class AnimationGenerator extends FrameGenerator { Stream _stream; bool _done = false; - AnimationGenerator(this.duration, { + AnimationGenerator({ this.initialDelay: 0.0, + this.duration, this.begin: 0.0, this.end: 1.0, this.curve: linear, Function onDone }):super(onDone: onDone) { - assert(duration > 0); + assert(duration != null && duration > 0.0); double startTime = 0.0; _stream = super.onTick.map((timeStamp) { if (startTime == 0.0) @@ -132,8 +133,12 @@ class Animation { { Curve curve: linear, double initialDelay: 0.0 }) { stop(); - _animation = new AnimationGenerator(duration, begin: _value, end: newValue, - curve: curve, initialDelay: initialDelay); + _animation = new AnimationGenerator( + duration: duration, + begin: _value, + end: newValue, + curve: curve, + initialDelay: initialDelay); _animation.onTick.listen(_setValue, onDone: () { _animation = null; diff --git a/framework/components/ink_splash.dart b/framework/components/ink_splash.dart index a4c0a2587b4..b31cc5f9d3b 100644 --- a/framework/components/ink_splash.dart +++ b/framework/components/ink_splash.dart @@ -27,8 +27,11 @@ class SplashAnimation { : _offsetX = x - rect.left, _offsetY = y - rect.top { - _animation = new AnimationGenerator(_kSplashDuration, - end: _kSplashSize, curve: easeOut, onDone: onDone); + _animation = new AnimationGenerator( + duration:_kSplashDuration, + end: _kSplashSize, + curve: easeOut, + onDone: onDone); _styleChanged = _animation.onTick.map((p) => ''' top: ${_offsetY - p/2}px; diff --git a/framework/components/scrollable.dart b/framework/components/scrollable.dart index 20f285866ef..838dbe9c818 100644 --- a/framework/components/scrollable.dart +++ b/framework/components/scrollable.dart @@ -2,7 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import '../animation/curves.dart'; import '../animation/fling_curve.dart'; +import '../animation/generator.dart'; import '../animation/scroll_curve.dart'; import '../fn.dart'; import 'dart:sky' as sky; @@ -14,8 +16,12 @@ abstract class Scrollable extends Component { double _scrollOffset = 0.0; FlingCurve _flingCurve; int _flingAnimationId; + AnimationGenerator _scrollAnimation; Scrollable({Object key, this.scrollCurve}) : super(key: key) { + events.listen('pointerdown', _handlePointerDown); + events.listen('pointerup', _handlePointerUpOrCancel); + events.listen('pointercancel', _handlePointerUpOrCancel); events.listen('gestureflingstart', _handleFlingStart); events.listen('gestureflingcancel', _handleFlingCancel); events.listen('gesturescrollupdate', _handleScrollUpdate); @@ -25,6 +31,7 @@ abstract class Scrollable extends Component { void didUnmount() { super.didUnmount(); _stopFling(); + _stopScrollAnimation(); } bool scrollBy(double scrollDelta) { @@ -37,6 +44,25 @@ abstract class Scrollable extends Component { return true; } + void animateScrollTo(double targetScrollOffset, { + double initialDelay: 0.0, + double duration: 0.0, + Curve curve: linear}) { + _stopScrollAnimation(); + _scrollAnimation = new AnimationGenerator( + duration: duration, + begin: _scrollOffset, + end: targetScrollOffset, + initialDelay: initialDelay, + curve: curve); + _scrollAnimation.onTick.listen((newScrollOffset) { + if (!scrollBy(newScrollOffset - _scrollOffset)) + _stopScrollAnimation(); + }, onDone: () { + _scrollAnimation = null; + }); + } + void _scheduleFlingUpdate() { _flingAnimationId = sky.window.requestAnimationFrame(_updateFling); } @@ -49,26 +75,48 @@ abstract class Scrollable extends Component { _flingAnimationId = null; } + void _stopScrollAnimation() { + if (_scrollAnimation == null) + return; + _scrollAnimation.cancel(); + _scrollAnimation = null; + } + void _updateFling(double timeStamp) { double scrollDelta = _flingCurve.update(timeStamp); if (!scrollBy(scrollDelta)) - return _stopFling(); + return _settle(); _scheduleFlingUpdate(); } + void _settle() { + _stopFling(); + if (_scrollOffset < 0.0) + animateScrollTo(0.0, duration: 200.0, curve: easeOut); + } + + void _handlePointerDown(_) { + _stopFling(); + _stopScrollAnimation(); + } + + void _handlePointerUpOrCancel(_) { + if (_flingCurve == null) + _settle(); + } + void _handleScrollUpdate(sky.GestureEvent event) { scrollBy(-event.dy); } void _handleFlingStart(sky.GestureEvent event) { - setState(() { - _flingCurve = new FlingCurve(-event.velocityY, event.timeStamp); - _scheduleFlingUpdate(); - }); + _stopScrollAnimation(); + _flingCurve = new FlingCurve(-event.velocityY, event.timeStamp); + _scheduleFlingUpdate(); } void _handleFlingCancel(sky.GestureEvent event) { - _stopFling(); + _settle(); } void _handleWheel(sky.WheelEvent event) {