dismissable.dart 5.75 KB
Newer Older
1 2 3 4 5 6
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:sky' as sky;

7
import 'package:sky/animation/animated_value.dart';
8
import 'package:sky/animation/animation_performance.dart';
9
import 'package:sky/animation/curves.dart';
10
import 'package:sky/widgets/basic.dart';
11
import 'package:sky/widgets/transitions.dart';
12
import 'package:sky/widgets/framework.dart';
13

14
const Duration _kCardDismissFadeout = const Duration(milliseconds: 200);
15
const Duration _kCardDismissResize = const Duration(milliseconds: 300);
16
final Interval _kCardDismissResizeInterval = new Interval(0.4, 1.0);
17 18
const double _kMinFlingVelocity = 700.0;
const double _kMinFlingVelocityDelta = 400.0;
19
const double _kFlingVelocityScale = 1.0 / 300.0;
20 21
const double _kDismissCardThreshold = 0.6;

22
typedef void ResizedCallback();
23 24
typedef void DismissedCallback();

25
class Dismissable extends StatefulComponent {
26 27

  Dismissable({
28
    Key key,
29
    this.child,
30
    this.onResized,
31 32 33 34 35
    this.onDismissed
    // TODO(hansmuller): direction
  }) : super(key: key);

  Widget child;
36
  ResizedCallback onResized;
37 38
  DismissedCallback onDismissed;

39 40
  AnimationPerformance _fadePerformance;
  AnimationPerformance _resizePerformance;
41

42
  Size _size;
43 44 45 46
  double _dragX = 0.0;
  bool _dragUnderway = false;

  void initState() {
47
    _fadePerformance = new AnimationPerformance(duration: _kCardDismissFadeout);
48 49
  }

50 51 52
  void _handleFadeCompleted() {
    if (!_dragUnderway)
      _startResizePerformance();
53 54
  }

55
  void syncConstructorArguments(Dismissable source) {
56
    child = source.child;
57
    onResized = source.onResized;
58 59 60 61
    onDismissed = source.onDismissed;
  }

  Point get _activeCardDragEndPoint {
62 63
    if (!_isActive)
      return Point.origin;
64 65
    assert(_size != null);
    return new Point(_dragX.sign * _size.width * _kDismissCardThreshold, 0.0);
66 67 68
  }

  bool get _isActive {
69 70 71 72 73 74
    return _size != null && (_dragUnderway || _fadePerformance.isAnimating);
  }

  void _maybeCallOnResized() {
    if (onResized != null)
      onResized();
75 76 77 78 79 80 81
  }

  void _maybeCallOnDismissed() {
    if (onDismissed != null)
      onDismissed();
  }

82 83 84
  void _startResizePerformance() {
    assert(_size != null);
    assert(_fadePerformance != null);
85
    assert(_fadePerformance.isCompleted);
86 87
    assert(_resizePerformance == null);

88 89 90 91 92
    setState(() {
      _resizePerformance = new AnimationPerformance()
        ..duration = _kCardDismissResize
        ..addListener(_handleResizeProgressChanged);
    });
93 94 95
  }

  void _handleResizeProgressChanged() {
96 97 98 99
    if (_resizePerformance.isCompleted)
      _maybeCallOnDismissed();
    else
      _maybeCallOnResized();
100 101
  }

102
  EventDisposition _handlePointerDown(sky.PointerEvent event) {
103 104 105
    if (_fadePerformance.isAnimating)
      return EventDisposition.processed;

106 107 108
    _dragUnderway = true;
    _dragX = 0.0;
    _fadePerformance.progress = 0.0;
109
    return EventDisposition.processed;
110 111
  }

112
  EventDisposition _handlePointerMove(sky.PointerEvent event) {
113
    if (!_isActive)
114
      return EventDisposition.ignored;
115

116 117 118
    if (_fadePerformance.isAnimating)
      return EventDisposition.processed;

119 120
    double oldDragX = _dragX;
    _dragX += event.dx;
121 122 123 124
    if (oldDragX.sign != _dragX.sign)
      setState(() {}); // Rebuild to update the new drag endpoint.
    if (!_fadePerformance.isAnimating)
      _fadePerformance.progress = _dragX.abs() / (_size.width * _kDismissCardThreshold);
125
    return EventDisposition.processed;
126 127
  }

128
  EventDisposition _handlePointerUpOrCancel(_) {
129
    if (!_isActive)
130
      return EventDisposition.ignored;
131

132 133 134
    if (_fadePerformance.isAnimating)
      return EventDisposition.processed;

135 136 137 138 139
    _dragUnderway = false;
    if (_fadePerformance.isCompleted)
      _startResizePerformance();
    else if (!_fadePerformance.isAnimating)
      _fadePerformance.reverse();
140
    return EventDisposition.processed;
141 142 143 144 145 146 147 148
  }

  bool _isHorizontalFlingGesture(sky.GestureEvent event) {
    double vx = event.velocityX.abs();
    double vy = event.velocityY.abs();
    return vx - vy > _kMinFlingVelocityDelta && vx > _kMinFlingVelocity;
  }

149
  EventDisposition _handleFlingStart(sky.GestureEvent event) {
150
    if (!_isActive)
151
      return EventDisposition.ignored;
152

153
    _dragUnderway = false;
154 155
    if (_isHorizontalFlingGesture(event)) {
      _dragX = event.velocityX.sign;
156 157 158 159
      if (_fadePerformance.isCompleted)
        _startResizePerformance();
      else
        _fadePerformance.fling(velocity: event.velocityX.abs() * _kFlingVelocityScale);
160 161
    } else {
      _fadePerformance.reverse();
162
    }
163

164
    return EventDisposition.processed;
165 166
  }

167
  void _handleSizeChanged(Size newSize) {
168 169 170
    setState(() {
      _size = new Size.copy(newSize);
    });
171 172
  }

173
  Widget build() {
Adam Barth's avatar
Adam Barth committed
174
    if (_resizePerformance != null) {
175 176 177 178 179 180 181 182 183 184 185
      AnimatedValue<double> dismissHeight = new AnimatedValue<double>(
        _size.height,
        end: 0.0,
        curve: ease,
        interval: _kCardDismissResizeInterval
      );

      return new SquashTransition(
        performance: _resizePerformance,
        direction: Direction.forward,
        height: dismissHeight);
Adam Barth's avatar
Adam Barth committed
186
    }
187

188 189 190 191 192 193 194 195
    return new Listener(
      onPointerDown: _handlePointerDown,
      onPointerMove: _handlePointerMove,
      onPointerUp: _handlePointerUpOrCancel,
      onPointerCancel: _handlePointerUpOrCancel,
      onGestureFlingStart: _handleFlingStart,
      child: new SizeObserver(
        callback: _handleSizeChanged,
196
        child: new FadeTransition(
197
          performance: _fadePerformance,
198 199 200
          onCompleted: _handleFadeCompleted,
          opacity: new AnimatedValue<double>(1.0, end: 0.0),
          child: new SlideTransition(
201
            performance: _fadePerformance,
202
            position: new AnimatedValue<Point>(Point.origin, end: _activeCardDragEndPoint),
203 204 205 206 207 208 209
            child: child
          )
        )
      )
    );
  }
}