| part of widgets; |
| |
| const double _kWidth = 256.0; |
| const double _kMinFlingVelocity = 0.4; |
| const double _kBaseSettleDurationMS = 246.0; |
| const double _kMaxSettleDurationMS = 600.0; |
| const Cubic _kAnimationCurve = easeOut; |
| |
| class DrawerAnimation { |
| |
| Stream<double> get onPositionChanged => _controller.stream; |
| |
| StreamController _controller; |
| AnimationGenerator _animation; |
| double _position; |
| bool get _isAnimating => _animation != null; |
| bool get _isMostlyClosed => _position <= -_kWidth / 2; |
| |
| DrawerAnimation() { |
| _controller = new StreamController(sync: true); |
| _setPosition(-_kWidth); |
| } |
| |
| void toggle(_) => _isMostlyClosed ? _open() : _close(); |
| |
| void handleMaskTap(_) => _close(); |
| |
| void handlePointerDown(_) => _cancelAnimation(); |
| |
| void handlePointerMove(sky.PointerEvent event) { |
| assert(_animation == null); |
| _setPosition(_position + event.dx); |
| } |
| |
| void handlePointerUp(_) { |
| if (!_isAnimating) |
| _settle(); |
| } |
| |
| void handlePointerCancel(_) { |
| if (!_isAnimating) |
| _settle(); |
| } |
| |
| void _open() => _animateToPosition(0.0); |
| |
| void _close() => _animateToPosition(-_kWidth); |
| |
| void _settle() => _isMostlyClosed ? _close() : _open(); |
| |
| void _setPosition(double value) { |
| _position = math.min(0.0, math.max(value, -_kWidth)); |
| _controller.add(_position); |
| } |
| |
| void _cancelAnimation() { |
| if (_animation != null) { |
| _animation.cancel(); |
| _animation = null; |
| } |
| } |
| |
| void _animate(double duration, double begin, double end, Curve curve) { |
| _cancelAnimation(); |
| |
| _animation = new AnimationGenerator(duration, begin: begin, end: end, |
| curve: curve); |
| |
| _animation.onTick.listen(_setPosition, onDone: () { |
| _animation = null; |
| }); |
| } |
| |
| void _animateToPosition(double targetPosition) { |
| double distance = (targetPosition - _position).abs(); |
| double targetDuration = distance / _kWidth * _kBaseSettleDurationMS; |
| double duration = math.min(targetDuration, _kMaxSettleDurationMS); |
| _animate(duration, _position, targetPosition, _kAnimationCurve); |
| } |
| |
| void handleFlingStart(event) { |
| double direction = event.velocityX.sign; |
| double velocityX = event.velocityX.abs() / 1000; |
| if (velocityX < _kMinFlingVelocity) |
| return; |
| |
| double targetPosition = direction < 0.0 ? -_kWidth : 0.0; |
| double distance = (targetPosition - _position).abs(); |
| double duration = distance / velocityX; |
| |
| _animate(duration, _position, targetPosition, linear); |
| } |
| } |
| |
| class Drawer extends Component { |
| |
| static Style _style = new Style(''' |
| position: absolute; |
| z-index: 2; |
| top: 0; |
| left: 0; |
| bottom: 0; |
| right: 0; |
| box-shadpw: ${Shadow[3]};''' |
| ); |
| |
| static Style _maskStyle = new Style(''' |
| background-color: black; |
| will-change: opacity; |
| position: absolute; |
| top: 0; |
| left: 0; |
| bottom: 0; |
| right: 0;''' |
| ); |
| |
| static Style _contentStyle = new Style(''' |
| background-color: ${Grey[50]}; |
| will-change: transform; |
| position: absolute; |
| z-index: 3; |
| width: 256px; |
| top: 0; |
| left: 0; |
| bottom: 0;''' |
| ); |
| |
| DrawerAnimation animation; |
| List<Node> children; |
| |
| Drawer({ |
| Object key, |
| this.animation, |
| this.children |
| }) : super(key: key); |
| |
| double _position = -_kWidth; |
| |
| bool _listening = false; |
| |
| void _ensureListening() { |
| if (_listening) |
| return; |
| |
| _listening = true; |
| animation.onPositionChanged.listen((position) { |
| setState(() { |
| _position = position; |
| }); |
| }); |
| } |
| |
| Node render() { |
| _ensureListening(); |
| |
| bool isClosed = _position <= -_kWidth; |
| String inlineStyle = 'display: ${isClosed ? 'none' : ''}'; |
| String maskInlineStyle = 'opacity: ${(_position / _kWidth + 1) * 0.25}'; |
| String contentInlineStyle = 'transform: translateX(${_position}px)'; |
| |
| Container mask = new Container( |
| key: 'Mask', |
| style: _maskStyle, |
| inlineStyle: maskInlineStyle |
| )..events.listen('gesturetap', animation.handleMaskTap) |
| ..events.listen('gestureflingstart', animation.handleFlingStart); |
| |
| Container content = new Container( |
| key: 'Content', |
| style: _contentStyle, |
| inlineStyle: contentInlineStyle, |
| children: children |
| ); |
| |
| return new Container( |
| style: _style, |
| inlineStyle: inlineStyle, |
| children: [ mask, content ] |
| )..events.listen('pointerdown', animation.handlePointerDown) |
| ..events.listen('pointermove', animation.handlePointerMove) |
| ..events.listen('pointerup', animation.handlePointerUp) |
| ..events.listen('pointercancel', animation.handlePointerCancel); |
| |
| } |
| } |