Adam Barth 70aa7795c7 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
2015-03-12 14:33:37 -07:00

126 lines
3.3 KiB
Dart

// Copyright 2015 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 '../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;
abstract class Scrollable extends Component {
ScrollCurve scrollCurve;
double get scrollOffset => _scrollOffset;
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);
events.listen('wheel', _handleWheel);
}
void didUnmount() {
super.didUnmount();
_stopFling();
_stopScrollAnimation();
}
bool scrollBy(double scrollDelta) {
var newScrollOffset = scrollCurve.apply(_scrollOffset, scrollDelta);
if (newScrollOffset == _scrollOffset)
return false;
setState(() {
_scrollOffset = newScrollOffset;
});
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);
}
void _stopFling() {
if (_flingAnimationId == null)
return;
sky.window.cancelAnimationFrame(_flingAnimationId);
_flingCurve = null;
_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 _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) {
_stopScrollAnimation();
_flingCurve = new FlingCurve(-event.velocityY, event.timeStamp);
_scheduleFlingUpdate();
}
void _handleFlingCancel(sky.GestureEvent event) {
_settle();
}
void _handleWheel(sky.WheelEvent event) {
scrollBy(-event.offsetY);
}
}