OLD | NEW |
(Empty) | |
| 1 part of widgets; |
| 2 |
| 3 const double _kWidth = 256.0; |
| 4 const double _kMinFlingVelocity = 0.4; |
| 5 const double _kMinAnimationDurationMS = 246.0; |
| 6 const double _kMaxAnimationDurationMS = 600.0; |
| 7 const Cubic _kAnimationCurve = easeOut; |
| 8 |
| 9 class DrawerAnimation { |
| 10 |
| 11 Stream<double> get onPositionChanged => _controller.stream; |
| 12 |
| 13 StreamController _controller; |
| 14 AnimationGenerator _animation; |
| 15 double _position; |
| 16 bool get _isAnimating => _animation != null; |
| 17 bool get _isMostlyClosed => _position <= -_kWidth / 2; |
| 18 |
| 19 DrawerAnimation() { |
| 20 _controller = new StreamController(sync: true); |
| 21 _setPosition(-_kWidth); |
| 22 } |
| 23 |
| 24 void toggle(_) => _isMostlyClosed ? _open() : _close(); |
| 25 |
| 26 void handleMaskTap(_) => _close(); |
| 27 |
| 28 void handlePointerDown(_) => _cancelAnimation(); |
| 29 |
| 30 void handlePointerMove(sky.PointerEvent event) { |
| 31 assert(_animation == null); |
| 32 _setPosition(_position + event.dx); |
| 33 } |
| 34 |
| 35 void handlePointerUp(_) { |
| 36 if (!_isAnimating) |
| 37 _settle(); |
| 38 } |
| 39 |
| 40 void handlePointerCancel(_) { |
| 41 if (!_isAnimating) |
| 42 _settle(); |
| 43 } |
| 44 |
| 45 void _open() => _animateToPosition(0.0); |
| 46 |
| 47 void _close() => _animateToPosition(-_kWidth); |
| 48 |
| 49 void _settle() => _isMostlyClosed ? _close() : _open(); |
| 50 |
| 51 void _setPosition(double value) { |
| 52 _position = math.min(0.0, math.max(value, -_kWidth)); |
| 53 _controller.add(_position); |
| 54 } |
| 55 |
| 56 void _cancelAnimation() { |
| 57 if (_animation != null) { |
| 58 _animation.cancel(); |
| 59 _animation = null; |
| 60 } |
| 61 } |
| 62 |
| 63 void _animate(double duration, double begin, double end, Curve curve) { |
| 64 _cancelAnimation(); |
| 65 |
| 66 _animation = new AnimationGenerator(duration, begin: begin, end: end, |
| 67 curve: curve); |
| 68 |
| 69 _animation.onTick.listen(_setPosition, onDone: () { |
| 70 _animation = null; |
| 71 }); |
| 72 } |
| 73 |
| 74 void _animateToPosition(double targetPosition) { |
| 75 double distance = (targetPosition - _position).abs(); |
| 76 double duration = math.max( |
| 77 _kMinAnimationDurationMS, |
| 78 _kMaxAnimationDurationMS * distance / _kWidth); |
| 79 |
| 80 _animate(duration, _position, targetPosition, _kAnimationCurve); |
| 81 } |
| 82 |
| 83 void handleFlingStart(event) { |
| 84 double direction = event.velocityX.sign; |
| 85 double velocityX = event.velocityX.abs() / 1000; |
| 86 if (velocityX < _kMinFlingVelocity) |
| 87 return; |
| 88 |
| 89 double targetPosition = direction < 0.0 ? -_kWidth : 0.0; |
| 90 double distance = (targetPosition - _position).abs(); |
| 91 double duration = distance / velocityX; |
| 92 |
| 93 _animate(duration, _position, targetPosition, linear); |
| 94 } |
| 95 } |
| 96 |
| 97 class Drawer extends Component { |
| 98 |
| 99 static Style _style = new Style(''' |
| 100 position: absolute; |
| 101 z-index: 2; |
| 102 top: 0; |
| 103 left: 0; |
| 104 bottom: 0; |
| 105 right: 0; |
| 106 box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);''' |
| 107 ); |
| 108 |
| 109 static Style _maskStyle = new Style(''' |
| 110 background-color: black; |
| 111 will-change: opacity; |
| 112 position: absolute; |
| 113 top: 0; |
| 114 left: 0; |
| 115 bottom: 0; |
| 116 right: 0;''' |
| 117 ); |
| 118 |
| 119 static Style _contentStyle = new Style(''' |
| 120 background-color: #FAFAFA; |
| 121 will-change: transform; |
| 122 position: absolute; |
| 123 width: 256px; |
| 124 top: 0; |
| 125 left: 0; |
| 126 bottom: 0;''' |
| 127 ); |
| 128 |
| 129 Stream<double> onPositionChanged; |
| 130 sky.EventListener handleMaskFling; |
| 131 sky.EventListener handleMaskTap; |
| 132 sky.EventListener handlePointerCancel; |
| 133 sky.EventListener handlePointerDown; |
| 134 sky.EventListener handlePointerMove; |
| 135 sky.EventListener handlePointerUp; |
| 136 List<Node> children; |
| 137 |
| 138 Drawer({ |
| 139 Object key, |
| 140 this.onPositionChanged, |
| 141 this.handleMaskFling, |
| 142 this.handleMaskTap, |
| 143 this.handlePointerCancel, |
| 144 this.handlePointerDown, |
| 145 this.handlePointerMove, |
| 146 this.handlePointerUp, |
| 147 this.children |
| 148 }) : super(key: key); |
| 149 |
| 150 double _position = -_kWidth; |
| 151 |
| 152 bool _listening = false; |
| 153 |
| 154 void _ensureListening() { |
| 155 if (_listening) |
| 156 return; |
| 157 |
| 158 _listening = true; |
| 159 onPositionChanged.listen((position) { |
| 160 setState(() { |
| 161 _position = position; |
| 162 }); |
| 163 }); |
| 164 } |
| 165 |
| 166 Node render() { |
| 167 _ensureListening(); |
| 168 |
| 169 bool isClosed = _position <= -_kWidth; |
| 170 String inlineStyle = 'display: ${isClosed ? 'none' : ''}'; |
| 171 String maskInlineStyle = 'opacity: ${(_position / _kWidth + 1) * 0.25}'; |
| 172 String contentInlineStyle = 'transform: translateX(${_position}px)'; |
| 173 |
| 174 return new Container( |
| 175 style: _style, |
| 176 inlineStyle: inlineStyle, |
| 177 onPointerDown: handlePointerDown, |
| 178 onPointerMove: handlePointerMove, |
| 179 onPointerUp: handlePointerUp, |
| 180 onPointerCancel: handlePointerCancel, |
| 181 |
| 182 children: [ |
| 183 new Container( |
| 184 key: 'Mask', |
| 185 style: _maskStyle, |
| 186 inlineStyle: maskInlineStyle, |
| 187 onGestureTap: handleMaskTap, |
| 188 onFlingStart: handleMaskFling |
| 189 ), |
| 190 new Container( |
| 191 key: 'Content', |
| 192 style: _contentStyle, |
| 193 inlineStyle: contentInlineStyle, |
| 194 children: children |
| 195 ) |
| 196 ] |
| 197 ); |
| 198 } |
| 199 } |
OLD | NEW |