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/animation.dart'; | 5 import '../animation/animated_value.dart'; |
6 import '../animation/curves.dart'; | 6 import '../animation/curves.dart'; |
7 import '../fn.dart'; | 7 import '../fn.dart'; |
8 import '../theme/colors.dart'; | 8 import '../theme/colors.dart'; |
9 import '../theme/shadows.dart'; | 9 import '../theme/shadows.dart'; |
10 import 'dart:async'; | 10 import 'dart:async'; |
11 import 'dart:math' as math; | 11 import 'dart:math' as math; |
12 import 'dart:sky' as sky; | 12 import 'dart:sky' as sky; |
13 import 'material.dart'; | 13 import 'material.dart'; |
14 | 14 |
15 const double _kWidth = 304.0; | 15 const double _kWidth = 304.0; |
16 const double _kMinFlingVelocity = 0.4; | 16 const double _kMinFlingVelocity = 0.4; |
17 const double _kBaseSettleDurationMS = 246.0; | 17 const double _kBaseSettleDurationMS = 246.0; |
18 const double _kMaxSettleDurationMS = 600.0; | 18 const double _kMaxSettleDurationMS = 600.0; |
19 const Curve _kAnimationCurve = parabolicRise; | 19 const Curve _kAnimationCurve = parabolicRise; |
20 | 20 |
21 class DrawerAnimation extends Animation { | 21 class DrawerController { |
22 Stream<double> get onPositionChanged => onValueChanged; | 22 final AnimatedValue position = new AnimatedValue(-_kWidth); |
23 | 23 |
24 bool get _isMostlyClosed => value <= -_kWidth / 2; | 24 bool get _isMostlyClosed => position.value <= -_kWidth / 2; |
25 | |
26 DrawerAnimation() { | |
27 value = -_kWidth; | |
28 } | |
29 | 25 |
30 void toggle(_) => _isMostlyClosed ? _open() : _close(); | 26 void toggle(_) => _isMostlyClosed ? _open() : _close(); |
31 | 27 |
32 void handleMaskTap(_) => _close(); | 28 void handleMaskTap(_) => _close(); |
33 | 29 |
34 void handlePointerDown(_) => stop(); | 30 void handlePointerDown(_) => position.stop(); |
35 | 31 |
36 void handlePointerMove(sky.PointerEvent event) { | 32 void handlePointerMove(sky.PointerEvent event) { |
37 if (isAnimating) | 33 if (position.isAnimating) |
38 return; | 34 return; |
39 value = math.min(0.0, math.max(value + event.dx, -_kWidth)); | 35 position.value = math.min(0.0, math.max(position.value + event.dx, -_kWidth)
); |
40 } | 36 } |
41 | 37 |
42 void handlePointerUp(_) { | 38 void handlePointerUp(_) { |
43 if (!isAnimating) | 39 if (!position.isAnimating) |
44 _settle(); | 40 _settle(); |
45 } | 41 } |
46 | 42 |
47 void handlePointerCancel(_) { | 43 void handlePointerCancel(_) { |
48 if (!isAnimating) | 44 if (!position.isAnimating) |
49 _settle(); | 45 _settle(); |
50 } | 46 } |
51 | 47 |
52 void _open() => _animateToPosition(0.0); | 48 void _open() => _animateToPosition(0.0); |
53 | 49 |
54 void _close() => _animateToPosition(-_kWidth); | 50 void _close() => _animateToPosition(-_kWidth); |
55 | 51 |
56 void _settle() => _isMostlyClosed ? _close() : _open(); | 52 void _settle() => _isMostlyClosed ? _close() : _open(); |
57 | 53 |
58 void _animateToPosition(double targetPosition) { | 54 void _animateToPosition(double targetPosition) { |
59 double distance = (targetPosition - value).abs(); | 55 double distance = (targetPosition - position.value).abs(); |
60 if (distance != 0) { | 56 if (distance != 0) { |
61 double targetDuration = distance / _kWidth * _kBaseSettleDurationMS; | 57 double targetDuration = distance / _kWidth * _kBaseSettleDurationMS; |
62 double duration = math.min(targetDuration, _kMaxSettleDurationMS); | 58 double duration = math.min(targetDuration, _kMaxSettleDurationMS); |
63 animateTo(targetPosition, duration, curve: _kAnimationCurve); | 59 position.animateTo(targetPosition, duration, curve: _kAnimationCurve); |
64 } | 60 } |
65 } | 61 } |
66 | 62 |
67 void handleFlingStart(event) { | 63 void handleFlingStart(event) { |
68 double direction = event.velocityX.sign; | 64 double direction = event.velocityX.sign; |
69 double velocityX = event.velocityX.abs() / 1000; | 65 double velocityX = event.velocityX.abs() / 1000; |
70 if (velocityX < _kMinFlingVelocity) | 66 if (velocityX < _kMinFlingVelocity) |
71 return; | 67 return; |
72 | 68 |
73 double targetPosition = direction < 0.0 ? -_kWidth : 0.0; | 69 double targetPosition = direction < 0.0 ? -_kWidth : 0.0; |
74 double distance = (targetPosition - value).abs(); | 70 double distance = (targetPosition - position.value).abs(); |
75 double duration = distance / velocityX; | 71 double duration = distance / velocityX; |
76 | 72 |
77 animateTo(targetPosition, duration, curve: linear); | 73 position.animateTo(targetPosition, duration, curve: linear); |
78 } | 74 } |
79 } | 75 } |
80 | 76 |
81 class Drawer extends Component { | 77 class Drawer extends Component { |
82 static final Style _style = new Style(''' | 78 static final Style _style = new Style(''' |
83 position: absolute; | 79 position: absolute; |
84 top: 0; | 80 top: 0; |
85 left: 0; | 81 left: 0; |
86 bottom: 0; | 82 bottom: 0; |
87 right: 0;''' | 83 right: 0;''' |
88 ); | 84 ); |
89 | 85 |
90 static final Style _maskStyle = new Style(''' | 86 static final Style _maskStyle = new Style(''' |
91 background-color: black; | 87 background-color: black; |
92 will-change: opacity; | 88 will-change: opacity; |
93 position: absolute; | 89 position: absolute; |
94 top: 0; | 90 top: 0; |
95 left: 0; | 91 left: 0; |
96 bottom: 0; | 92 bottom: 0; |
97 right: 0;''' | 93 right: 0;''' |
98 ); | 94 ); |
99 | 95 |
100 static final Style _contentStyle = new Style(''' | 96 static final Style _contentStyle = new Style(''' |
101 background-color: ${Grey[50]}; | 97 background-color: ${Grey[50]}; |
102 will-change: transform; | 98 will-change: transform; |
103 position: absolute; | 99 position: absolute; |
104 width: 304px; | 100 width: ${_kWidth}px; |
105 top: 0; | 101 top: 0; |
106 left: 0; | 102 left: 0; |
107 bottom: 0;''' | 103 bottom: 0;''' |
108 ); | 104 ); |
109 | 105 |
110 DrawerAnimation animation; | |
111 List<Node> children; | 106 List<Node> children; |
112 int level; | 107 int level; |
| 108 DrawerController controller; |
| 109 |
| 110 AnimatedValueListener _position; |
113 | 111 |
114 Drawer({ | 112 Drawer({ |
115 Object key, | 113 Object key, |
116 this.animation, | 114 this.controller, |
117 this.children, | 115 this.children, |
118 this.level: 0 | 116 this.level: 0 |
119 }) : super(key: key) { | 117 }) : super(key: key) { |
120 events.listen('pointerdown', animation.handlePointerDown); | 118 events.listen('pointerdown', controller.handlePointerDown); |
121 events.listen('pointermove', animation.handlePointerMove); | 119 events.listen('pointermove', controller.handlePointerMove); |
122 events.listen('pointerup', animation.handlePointerUp); | 120 events.listen('pointerup', controller.handlePointerUp); |
123 events.listen('pointercancel', animation.handlePointerCancel); | 121 events.listen('pointercancel', controller.handlePointerCancel); |
| 122 _position = new AnimatedValueListener(this, controller.position); |
124 } | 123 } |
125 | 124 |
126 double _position = -_kWidth; | 125 void didUnmount() { |
127 | 126 _position.stopListening(); |
128 bool _listening = false; | |
129 | |
130 void _ensureListening() { | |
131 if (_listening) | |
132 return; | |
133 | |
134 _listening = true; | |
135 animation.onPositionChanged.listen((position) { | |
136 setState(() { | |
137 _position = position; | |
138 }); | |
139 }); | |
140 } | 127 } |
141 | 128 |
142 Node build() { | 129 Node build() { |
143 _ensureListening(); | 130 _position.ensureListening(); |
144 | 131 |
145 bool isClosed = _position <= -_kWidth; | 132 bool isClosed = _position.value <= -_kWidth; |
146 String inlineStyle = 'display: ${isClosed ? 'none' : ''}'; | 133 String inlineStyle = 'display: ${isClosed ? 'none' : ''}'; |
147 String maskInlineStyle = 'opacity: ${(_position / _kWidth + 1) * 0.5}'; | 134 String maskInlineStyle = 'opacity: ${(_position.value / _kWidth + 1) * 0.5}'
; |
148 String contentInlineStyle = 'transform: translateX(${_position}px)'; | 135 String contentInlineStyle = 'transform: translateX(${_position.value}px)'; |
149 | 136 |
150 Container mask = new Container( | 137 Container mask = new Container( |
151 key: 'Mask', | 138 key: 'Mask', |
152 style: _maskStyle, | 139 style: _maskStyle, |
153 inlineStyle: maskInlineStyle | 140 inlineStyle: maskInlineStyle |
154 )..events.listen('gesturetap', animation.handleMaskTap) | 141 )..events.listen('gesturetap', controller.handleMaskTap) |
155 ..events.listen('gestureflingstart', animation.handleFlingStart); | 142 ..events.listen('gestureflingstart', controller.handleFlingStart); |
156 | 143 |
157 Material content = new Material( | 144 Material content = new Material( |
158 key: 'Content', | 145 key: 'Content', |
159 style: _contentStyle, | 146 style: _contentStyle, |
160 inlineStyle: contentInlineStyle, | 147 inlineStyle: contentInlineStyle, |
161 children: children, | 148 children: children, |
162 level: level | 149 level: level |
163 ); | 150 ); |
164 | 151 |
165 return new Container( | 152 return new Container( |
166 style: _style, | 153 style: _style, |
167 inlineStyle: inlineStyle, | 154 inlineStyle: inlineStyle, |
168 children: [ mask, content ] | 155 children: [ mask, content ] |
169 ); | 156 ); |
170 } | 157 } |
171 } | 158 } |
OLD | NEW |