From 1b76bf43d1c6af7ce50cfd3ca8d56ede005de1e5 Mon Sep 17 00:00:00 2001 From: Hixie Date: Mon, 20 Apr 2015 15:51:24 -0700 Subject: [PATCH] [Effen] Reduce splashes when scrolling. Pipe the remaining time for an animation all the way out to the AnimatedValue. Make splashes abortable, which causes them to continue to fade but no longer grow, by having them fade in 100ms or however long the animation was still going to go for, whichever is quickest. Make Scrollables support objects registering with them to be told when scrolling happens. Make UINode support subclasses being informed when _remove() was called. Hook all that together to make splashes go away when scrolling by having them register with any ancestor Scrollables such that when those scroll, all the splashes get aborted. Unregister when removed. R=eseidel@chromium.org Review URL: https://codereview.chromium.org/1092423003 --- framework/animation/animated_value.dart | 7 +++++ framework/animation/generators.dart | 10 ++++++- framework/components/ink_splash.dart | 31 +++++++++++++++++----- framework/components/ink_well.dart | 32 +++++++++++++++++++++- framework/components/scrollable.dart | 35 +++++++++++++++++++++++++ framework/fn.dart | 3 +++ 6 files changed, 110 insertions(+), 8 deletions(-) diff --git a/framework/animation/animated_value.dart b/framework/animation/animated_value.dart index 11f1dea2719..d07dac56461 100644 --- a/framework/animation/animated_value.dart +++ b/framework/animation/animated_value.dart @@ -67,4 +67,11 @@ class AnimatedValue { _completer = new Completer(); return _completer.future; } + + double get remainingTime { + if (_animation == null) + return 0.0; + return _animation.remainingTime; + } + } diff --git a/framework/animation/generators.dart b/framework/animation/generators.dart index 3f05eee382c..7dab878a1b1 100644 --- a/framework/animation/generators.dart +++ b/framework/animation/generators.dart @@ -68,6 +68,7 @@ class AnimationGenerator extends Generator { FrameGenerator _generator; Stream _stream; bool _done = false; + double _lastTime; AnimationGenerator({ this.initialDelay: 0.0, @@ -87,13 +88,20 @@ class AnimationGenerator extends Generator { startTime = timeStamp; double t = (timeStamp - (startTime + initialDelay)) / duration; - return math.max(0.0, math.min(t, 1.0)); + _lastTime = math.max(0.0, math.min(t, 1.0)); + return _lastTime; }) .takeWhile(_checkForCompletion) .where((t) => t >= 0.0) .map(_transform); } + double get remainingTime { + if (_lastTime == null) + return duration; + return duration - _lastTime; + } + void cancel() { _generator.cancel(); } diff --git a/framework/components/ink_splash.dart b/framework/components/ink_splash.dart index 335d49d22be..6b897680d6e 100644 --- a/framework/components/ink_splash.dart +++ b/framework/components/ink_splash.dart @@ -13,6 +13,8 @@ import 'dart:sky' as sky; const double _kSplashConfirmedDuration = 350.0; const double _kSplashUnconfirmedDuration = config.kDefaultLongPressTimeout; +const double _kSplashAbortDuration = 100.0; +const double _kSplashInitialDelay = 0.0; // we could delay initially in case the user scrolls double _getSplashTargetSize(sky.ClientRect rect, double x, double y) { return 2.0 * math.max(math.max(x - rect.left, rect.right - x), @@ -26,11 +28,13 @@ class SplashController { final AnimatedValue _size = new AnimatedValue(0.0); double _offsetX; double _offsetY; + double _lastSize = 0.0; + bool _growing = true; double _targetSize; Stream _styleStream; void start() { - _size.animateTo(_targetSize, _kSplashUnconfirmedDuration, curve: easeOut); + _size.animateTo(_targetSize, _kSplashUnconfirmedDuration, curve: easeOut, initialDelay: _kSplashInitialDelay); } void confirm() { @@ -41,6 +45,14 @@ class SplashController { _size.animateTo(_targetSize, duration, curve: easeOut); } + void abort() { + _growing = false; + double durationRemaining = _size.remainingTime; + if (durationRemaining <= _kSplashAbortDuration) + return; + _size.animateTo(_targetSize, _kSplashAbortDuration, curve: easeOut); + } + void cancel() { _size.stop(); } @@ -55,12 +67,19 @@ class SplashController { if (p == _targetSize) { onDone(); } + double size; + if (_growing) { + sise = p; + _lastSize = p; + } else { + size = _lastSize; + } return ''' - top: ${_offsetY - p/2}px; - left: ${_offsetX - p/2}px; - width: ${p}px; - height: ${p}px; - border-radius: ${p}px; + top: ${_offsetY - size/2}px; + left: ${_offsetX - size/2}px; + width: ${size}px; + height: ${size}px; + border-radius: ${size}px; opacity: ${1.0 - (p / _targetSize)};'''; }); diff --git a/framework/components/ink_well.dart b/framework/components/ink_well.dart index 205e9f38f77..096fa007963 100644 --- a/framework/components/ink_well.dart +++ b/framework/components/ink_well.dart @@ -6,8 +6,9 @@ import '../fn.dart'; import 'dart:collection'; import 'dart:sky' as sky; import 'ink_splash.dart'; +import 'scrollable.dart'; -class InkWell extends Component { +class InkWell extends Component implements ScrollClient { static final Style _containmentStyleHack = new Style(''' transform: translateX(0);'''); @@ -55,9 +56,30 @@ class InkWell extends Component { pointer: event.primaryPointer, onDone: () { _splashDone(splash); }); _splashes.add(splash); + UINode node = parent; + while (node != null) { + if (node is Scrollable) + node.registerScrollClient(this); + node = node.parent; + } }); } + bool ancestorScrolled(Scrollable ancestor) { + _abortSplashes(); + return false; + } + + void handleRemoved() { + UINode node = parent; + while (node != null) { + if (node is Scrollable) + node.unregisterScrollClient(this); + node = node.parent; + } + super.handleRemoved(); + } + void _confirmSplash(sky.GestureEvent event) { if (_splashes == null) return; @@ -65,6 +87,14 @@ class InkWell extends Component { .forEach((splash) { splash.confirm(); }); } + void _abortSplashes() { + if (_splashes == null) + return; + setState(() { + _splashes.forEach((s) { s.abort(); }); + }); + } + void _cancelSplashes(sky.Event event) { if (_splashes == null) return; diff --git a/framework/components/scrollable.dart b/framework/components/scrollable.dart index dadcf775a67..36bbaffef4c 100644 --- a/framework/components/scrollable.dart +++ b/framework/components/scrollable.dart @@ -17,6 +17,10 @@ double _velocityForFlingGesture(sky.GestureEvent event) { -event.velocityY)) / _kMillisecondsPerSecond; } +abstract class ScrollClient { + bool ancestorScrolled(Scrollable ancestor); +} + abstract class Scrollable extends Component { ScrollBehavior scrollBehavior; double get scrollOffset => _scrollOffset; @@ -43,12 +47,43 @@ abstract class Scrollable extends Component { ); } + List _registeredScrollClients; + + void registerScrollClient(ScrollClient notifiee) { + if (_registeredScrollClients == null) + _registeredScrollClients = new List(); + setState(() { + _registeredScrollClients.add(notifiee); + }); + } + + void unregisterScrollClient(ScrollClient notifiee) { + if (_registeredScrollClients == null) + return; + setState(() { + _registeredScrollClients.remove(notifiee); + }); + } + bool scrollTo(double newScrollOffset) { if (newScrollOffset == _scrollOffset) return false; setState(() { _scrollOffset = newScrollOffset; }); + if (_registeredScrollClients != null) { + var newList = null; + _registeredScrollClients.forEach((target) { + if (target.ancestorScrolled(this)) { + if (newList == null) + newList = new List(); + newList.add(target); + } + }); + setState(() { + _registeredScrollClients = newList; + }); + } return true; } diff --git a/framework/fn.dart b/framework/fn.dart index e7ff9927644..850ffd2597e 100644 --- a/framework/fn.dart +++ b/framework/fn.dart @@ -62,6 +62,7 @@ enum _SyncOperation { IDENTICAL, INSERTION, STATEFUL, STATELESS, REMOVAL } abstract class UINode { String _key; UINode _parent; + UINode get parent => _parent; sky.Node _root; bool _defunct = false; @@ -78,7 +79,9 @@ abstract class UINode { void _remove() { _defunct = true; _root = null; + handleRemoved(); } + void handleRemoved() { } int _nodeDepth; void _ensureDepth() {