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