OLD | NEW |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
| 5 import '../animation/curves.dart'; |
5 import '../animation/fling_curve.dart'; | 6 import '../animation/fling_curve.dart'; |
| 7 import '../animation/generator.dart'; |
6 import '../animation/scroll_curve.dart'; | 8 import '../animation/scroll_curve.dart'; |
7 import '../fn.dart'; | 9 import '../fn.dart'; |
8 import 'dart:sky' as sky; | 10 import 'dart:sky' as sky; |
9 | 11 |
10 abstract class Scrollable extends Component { | 12 abstract class Scrollable extends Component { |
11 ScrollCurve scrollCurve; | 13 ScrollCurve scrollCurve; |
12 double get scrollOffset => _scrollOffset; | 14 double get scrollOffset => _scrollOffset; |
13 | 15 |
14 double _scrollOffset = 0.0; | 16 double _scrollOffset = 0.0; |
15 FlingCurve _flingCurve; | 17 FlingCurve _flingCurve; |
16 int _flingAnimationId; | 18 int _flingAnimationId; |
| 19 AnimationGenerator _scrollAnimation; |
17 | 20 |
18 Scrollable({Object key, this.scrollCurve}) : super(key: key) { | 21 Scrollable({Object key, this.scrollCurve}) : super(key: key) { |
| 22 events.listen('pointerdown', _handlePointerDown); |
| 23 events.listen('pointerup', _handlePointerUpOrCancel); |
| 24 events.listen('pointercancel', _handlePointerUpOrCancel); |
19 events.listen('gestureflingstart', _handleFlingStart); | 25 events.listen('gestureflingstart', _handleFlingStart); |
20 events.listen('gestureflingcancel', _handleFlingCancel); | 26 events.listen('gestureflingcancel', _handleFlingCancel); |
21 events.listen('gesturescrollupdate', _handleScrollUpdate); | 27 events.listen('gesturescrollupdate', _handleScrollUpdate); |
22 events.listen('wheel', _handleWheel); | 28 events.listen('wheel', _handleWheel); |
23 } | 29 } |
24 | 30 |
25 void didUnmount() { | 31 void didUnmount() { |
26 super.didUnmount(); | 32 super.didUnmount(); |
27 _stopFling(); | 33 _stopFling(); |
| 34 _stopScrollAnimation(); |
28 } | 35 } |
29 | 36 |
30 bool scrollBy(double scrollDelta) { | 37 bool scrollBy(double scrollDelta) { |
31 var newScrollOffset = scrollCurve.apply(_scrollOffset, scrollDelta); | 38 var newScrollOffset = scrollCurve.apply(_scrollOffset, scrollDelta); |
32 if (newScrollOffset == _scrollOffset) | 39 if (newScrollOffset == _scrollOffset) |
33 return false; | 40 return false; |
34 setState(() { | 41 setState(() { |
35 _scrollOffset = newScrollOffset; | 42 _scrollOffset = newScrollOffset; |
36 }); | 43 }); |
37 return true; | 44 return true; |
38 } | 45 } |
39 | 46 |
| 47 void animateScrollTo(double targetScrollOffset, { |
| 48 double initialDelay: 0.0, |
| 49 double duration: 0.0, |
| 50 Curve curve: linear}) { |
| 51 _stopScrollAnimation(); |
| 52 _scrollAnimation = new AnimationGenerator( |
| 53 duration: duration, |
| 54 begin: _scrollOffset, |
| 55 end: targetScrollOffset, |
| 56 initialDelay: initialDelay, |
| 57 curve: curve); |
| 58 _scrollAnimation.onTick.listen((newScrollOffset) { |
| 59 if (!scrollBy(newScrollOffset - _scrollOffset)) |
| 60 _stopScrollAnimation(); |
| 61 }, onDone: () { |
| 62 _scrollAnimation = null; |
| 63 }); |
| 64 } |
| 65 |
40 void _scheduleFlingUpdate() { | 66 void _scheduleFlingUpdate() { |
41 _flingAnimationId = sky.window.requestAnimationFrame(_updateFling); | 67 _flingAnimationId = sky.window.requestAnimationFrame(_updateFling); |
42 } | 68 } |
43 | 69 |
44 void _stopFling() { | 70 void _stopFling() { |
45 if (_flingAnimationId == null) | 71 if (_flingAnimationId == null) |
46 return; | 72 return; |
47 sky.window.cancelAnimationFrame(_flingAnimationId); | 73 sky.window.cancelAnimationFrame(_flingAnimationId); |
48 _flingCurve = null; | 74 _flingCurve = null; |
49 _flingAnimationId = null; | 75 _flingAnimationId = null; |
50 } | 76 } |
51 | 77 |
| 78 void _stopScrollAnimation() { |
| 79 if (_scrollAnimation == null) |
| 80 return; |
| 81 _scrollAnimation.cancel(); |
| 82 _scrollAnimation = null; |
| 83 } |
| 84 |
52 void _updateFling(double timeStamp) { | 85 void _updateFling(double timeStamp) { |
53 double scrollDelta = _flingCurve.update(timeStamp); | 86 double scrollDelta = _flingCurve.update(timeStamp); |
54 if (!scrollBy(scrollDelta)) | 87 if (!scrollBy(scrollDelta)) |
55 return _stopFling(); | 88 return _settle(); |
56 _scheduleFlingUpdate(); | 89 _scheduleFlingUpdate(); |
57 } | 90 } |
58 | 91 |
| 92 void _settle() { |
| 93 _stopFling(); |
| 94 if (_scrollOffset < 0.0) |
| 95 animateScrollTo(0.0, duration: 200.0, curve: easeOut); |
| 96 } |
| 97 |
| 98 void _handlePointerDown(_) { |
| 99 _stopFling(); |
| 100 _stopScrollAnimation(); |
| 101 } |
| 102 |
| 103 void _handlePointerUpOrCancel(_) { |
| 104 if (_flingCurve == null) |
| 105 _settle(); |
| 106 } |
| 107 |
59 void _handleScrollUpdate(sky.GestureEvent event) { | 108 void _handleScrollUpdate(sky.GestureEvent event) { |
60 scrollBy(-event.dy); | 109 scrollBy(-event.dy); |
61 } | 110 } |
62 | 111 |
63 void _handleFlingStart(sky.GestureEvent event) { | 112 void _handleFlingStart(sky.GestureEvent event) { |
64 setState(() { | 113 _stopScrollAnimation(); |
65 _flingCurve = new FlingCurve(-event.velocityY, event.timeStamp); | 114 _flingCurve = new FlingCurve(-event.velocityY, event.timeStamp); |
66 _scheduleFlingUpdate(); | 115 _scheduleFlingUpdate(); |
67 }); | |
68 } | 116 } |
69 | 117 |
70 void _handleFlingCancel(sky.GestureEvent event) { | 118 void _handleFlingCancel(sky.GestureEvent event) { |
71 _stopFling(); | 119 _settle(); |
72 } | 120 } |
73 | 121 |
74 void _handleWheel(sky.WheelEvent event) { | 122 void _handleWheel(sky.WheelEvent event) { |
75 scrollBy(-event.offsetY); | 123 scrollBy(-event.offsetY); |
76 } | 124 } |
77 } | 125 } |
OLD | NEW |