| 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 'dart:sky' as sky; | 5 import 'dart:sky' as sky; |
| 6 | 6 |
| 7 import 'package:vector_math/vector_math.dart'; | 7 import 'package:vector_math/vector_math.dart'; |
| 8 import 'package:sky/animation/animation_performance.dart'; | 8 import 'package:sky/animation/animation_performance.dart'; |
| 9 import 'package:sky/animation/scroll_behavior.dart'; |
| 9 import 'package:sky/base/lerp.dart'; | 10 import 'package:sky/base/lerp.dart'; |
| 10 import 'package:sky/painting/text_style.dart'; | 11 import 'package:sky/painting/text_style.dart'; |
| 11 import 'package:sky/theme/colors.dart'; | 12 import 'package:sky/theme/colors.dart'; |
| 12 import 'package:sky/widgets/animated_container.dart'; | 13 import 'package:sky/widgets/animated_container.dart'; |
| 13 import 'package:sky/widgets/basic.dart'; | 14 import 'package:sky/widgets/basic.dart'; |
| 15 import 'package:sky/widgets/block_viewport.dart'; |
| 14 import 'package:sky/widgets/card.dart'; | 16 import 'package:sky/widgets/card.dart'; |
| 15 import 'package:sky/widgets/scaffold.dart'; | 17 import 'package:sky/widgets/scaffold.dart'; |
| 18 import 'package:sky/widgets/scrollable.dart'; |
| 16 import 'package:sky/widgets/theme.dart'; | 19 import 'package:sky/widgets/theme.dart'; |
| 17 import 'package:sky/widgets/tool_bar.dart'; | 20 import 'package:sky/widgets/tool_bar.dart'; |
| 18 import 'package:sky/widgets/widget.dart'; | 21 import 'package:sky/widgets/widget.dart'; |
| 19 | 22 |
| 20 | 23 |
| 21 const int _kCardDismissFadeoutMS = 300; | 24 const int _kCardDismissFadeoutMS = 300; |
| 22 const double _kMinCardFlingVelocity = 0.4; | 25 const double _kMinFlingVelocity = 700.0; |
| 23 const double _kDismissCardThreshold = 0.70; | 26 const double _kMinFlingVelocityDelta = 400.0; |
| 27 const double _kDismissCardThreshold = 0.6; |
| 28 |
| 29 class VariableHeightScrollable extends Scrollable { |
| 30 VariableHeightScrollable({ |
| 31 String key, |
| 32 this.builder, |
| 33 this.token |
| 34 }) : super(key: key); |
| 35 |
| 36 IndexedBuilder builder; |
| 37 Object token; |
| 38 |
| 39 void syncFields(VariableHeightScrollable source) { |
| 40 builder = source.builder; |
| 41 token = source.token; |
| 42 super.syncFields(source); |
| 43 } |
| 44 |
| 45 ScrollBehavior createScrollBehavior() => new OverscrollBehavior(); |
| 46 OverscrollBehavior get scrollBehavior => super.scrollBehavior; |
| 47 |
| 48 void _handleSizeChanged(Size newSize) { |
| 49 setState(() { |
| 50 scrollBehavior.containerHeight = newSize.height; |
| 51 scrollBehavior.contentsHeight = 5000.0; |
| 52 }); |
| 53 } |
| 54 |
| 55 Widget buildContent() { |
| 56 return new SizeObserver( |
| 57 callback: _handleSizeChanged, |
| 58 child: new BlockViewport( |
| 59 builder: builder, |
| 60 startOffset: scrollOffset, |
| 61 token: token |
| 62 ) |
| 63 ); |
| 64 } |
| 65 } |
| 24 | 66 |
| 25 class CardCollectionApp extends App { | 67 class CardCollectionApp extends App { |
| 26 | 68 |
| 27 final TextStyle cardLabelStyle = | 69 final TextStyle cardLabelStyle = |
| 28 new TextStyle(color: White, fontSize: 18.0, fontWeight: bold); | 70 new TextStyle(color: White, fontSize: 18.0, fontWeight: bold); |
| 29 | 71 |
| 72 final List<double> cardHeights = [ |
| 73 48.0, 64.0, 82.0, 46.0, 60.0, 55.0, 84.0, 96.0, 50.0, |
| 74 48.0, 64.0, 82.0, 46.0, 60.0, 55.0, 84.0, 96.0, 50.0, |
| 75 48.0, 64.0, 82.0, 46.0, 60.0, 55.0, 84.0, 96.0, 50.0 |
| 76 ]; |
| 77 |
| 78 List<int> visibleCardIndices; |
| 79 |
| 30 CardCollectionApp() { | 80 CardCollectionApp() { |
| 31 _activeCardTransform = new AnimatedContainer() | 81 _activeCardTransform = new AnimatedContainer() |
| 32 ..position = new AnimatedType<Point>(Point.origin) | 82 ..position = new AnimatedType<Point>(Point.origin) |
| 33 ..opacity = new AnimatedType<double>(1.0, end: 0.0); | 83 ..opacity = new AnimatedType<double>(1.0, end: 0.0); |
| 84 |
| 34 _activeCardAnimation = _activeCardTransform.createPerformance( | 85 _activeCardAnimation = _activeCardTransform.createPerformance( |
| 35 [_activeCardTransform.position, _activeCardTransform.opacity], | 86 [_activeCardTransform.position, _activeCardTransform.opacity], |
| 36 duration: new Duration(milliseconds: _kCardDismissFadeoutMS)); | 87 duration: new Duration(milliseconds: _kCardDismissFadeoutMS)); |
| 37 _activeCardAnimation.addListener(_handleAnimationProgressChanged); | 88 _activeCardAnimation.addListener(_handleAnimationProgressChanged); |
| 89 |
| 90 visibleCardIndices = new List.generate(cardHeights.length, (i) => i); |
| 38 } | 91 } |
| 39 | 92 |
| 40 int _activeCardIndex = -1; | 93 int _activeCardIndex = -1; |
| 41 AnimatedContainer _activeCardTransform; | 94 AnimatedContainer _activeCardTransform; |
| 42 AnimationPerformance _activeCardAnimation; | 95 AnimationPerformance _activeCardAnimation; |
| 43 double _activeCardWidth; | 96 double _activeCardWidth; |
| 44 double _activeCardDragX = 0.0; | 97 double _activeCardDragX = 0.0; |
| 45 bool _activeCardDragUnderway = false; | 98 bool _activeCardDragUnderway = false; |
| 46 Set<int> _dismissedCardIndices = new Set<int>(); | |
| 47 | 99 |
| 48 Point get _activeCardDragEndPoint { | 100 Point get _activeCardDragEndPoint { |
| 49 return new Point(_activeCardDragX.sign * _activeCardWidth * _kDismissCardThr
eshold, 0.0); | 101 return new Point(_activeCardDragX.sign * _activeCardWidth * _kDismissCardThr
eshold, 0.0); |
| 50 } | 102 } |
| 51 | 103 |
| 52 void _handleAnimationProgressChanged() { | 104 void _handleAnimationProgressChanged() { |
| 53 setState(() { | 105 setState(() { |
| 54 if (_activeCardAnimation.isCompleted && !_activeCardDragUnderway) | 106 if (_activeCardAnimation.isCompleted && !_activeCardDragUnderway) |
| 55 _dismissedCardIndices.add(_activeCardIndex); | 107 visibleCardIndices.remove(_activeCardIndex); |
| 56 }); | 108 }); |
| 57 } | 109 } |
| 58 | 110 |
| 59 void _handleSizeChanged(Size newSize) { | 111 void _handleSizeChanged(Size newSize) { |
| 60 _activeCardWidth = newSize.width; | 112 _activeCardWidth = newSize.width; |
| 61 _activeCardTransform.position.end = _activeCardDragEndPoint; | 113 _activeCardTransform.position.end = _activeCardDragEndPoint; |
| 62 } | 114 } |
| 63 | 115 |
| 64 void _handlePointerDown(sky.PointerEvent event, int cardIndex) { | 116 void _handlePointerDown(sky.PointerEvent event, int cardIndex) { |
| 65 setState(() { | 117 setState(() { |
| (...skipping 19 matching lines...) Expand all Loading... |
| 85 }); | 137 }); |
| 86 } | 138 } |
| 87 | 139 |
| 88 void _handlePointerUpOrCancel(_) { | 140 void _handlePointerUpOrCancel(_) { |
| 89 if (_activeCardWidth == null || _activeCardIndex < 0) | 141 if (_activeCardWidth == null || _activeCardIndex < 0) |
| 90 return; | 142 return; |
| 91 | 143 |
| 92 setState(() { | 144 setState(() { |
| 93 _activeCardDragUnderway = false; | 145 _activeCardDragUnderway = false; |
| 94 if (_activeCardAnimation.isCompleted) | 146 if (_activeCardAnimation.isCompleted) |
| 95 _dismissedCardIndices.add(_activeCardIndex); | 147 visibleCardIndices.remove(_activeCardIndex); |
| 96 else if (!_activeCardAnimation.isAnimating) | 148 else if (!_activeCardAnimation.isAnimating) |
| 97 _activeCardAnimation.progress = 0.0; | 149 _activeCardAnimation.progress = 0.0; |
| 98 }); | 150 }); |
| 99 } | 151 } |
| 100 | 152 |
| 153 bool _isHorizontalFlingGesture(sky.GestureEvent event) { |
| 154 double vx = event.velocityX.abs(); |
| 155 double vy = event.velocityY.abs(); |
| 156 return vx - vy > _kMinFlingVelocityDelta && vx > _kMinFlingVelocity; |
| 157 } |
| 158 |
| 101 void _handleFlingStart(sky.GestureEvent event) { | 159 void _handleFlingStart(sky.GestureEvent event) { |
| 102 if (_activeCardWidth == null || _activeCardIndex < 0) | 160 if (_activeCardWidth == null || _activeCardIndex < 0) |
| 103 return; | 161 return; |
| 104 | 162 |
| 105 _activeCardDragUnderway = false; | 163 _activeCardDragUnderway = false; |
| 106 double velocityX = event.velocityX / 1000; | 164 |
| 107 if (velocityX.abs() >= _kMinCardFlingVelocity) { | 165 if (_isHorizontalFlingGesture(event)) { |
| 108 double distance = 1.0 - _activeCardAnimation.progress; | 166 double distance = 1.0 - _activeCardAnimation.progress; |
| 109 if (distance > 0.0) { | 167 if (distance > 0.0) { |
| 110 double duration = 150.0 * distance / velocityX.abs(); | 168 double duration = 250.0 * 1000.0 * distance / event.velocityX.abs(); |
| 111 _activeCardDragX = velocityX.sign; | 169 _activeCardDragX = event.velocityX.sign; |
| 112 _activeCardAnimation.timeline.animateTo(1.0, duration: duration); | 170 _activeCardAnimation.timeline.animateTo(1.0, duration: duration); |
| 113 } | 171 } |
| 114 } | 172 } |
| 115 } | 173 } |
| 116 | 174 |
| 117 Widget _buildCard(int index, Color color) { | 175 Widget _buildCard(int cardIndex) { |
| 118 Widget label = new Center(child: new Text("Item ${index}", style: cardLabelS
tyle)); | 176 Widget label = new Center(child: new Text("Item ${cardIndex}", style: cardLa
belStyle)); |
| 177 Color color = lerpColor(Red[500], Blue[500], cardIndex / cardHeights.length)
; |
| 119 Widget card = new Card( | 178 Widget card = new Card( |
| 120 child: new Padding(child: label, padding: const EdgeDims.all(8.0)), | 179 child: new Padding(child: label, padding: const EdgeDims.all(8.0)), |
| 121 color: color | 180 color: color |
| 122 ); | 181 ); |
| 123 | 182 |
| 124 // TODO(hansmuller) The code below changes the card's widget tree when | 183 // TODO(hansmuller) The code below changes the card's widget tree when |
| 125 // the user starts dragging it. Currently this causes Sky to drop the | 184 // the user starts dragging it. Currently this causes Sky to drop the |
| 126 // rest of the pointer gesture, see https://github.com/domokit/mojo/issues/3
12. | 185 // rest of the pointer gesture, see https://github.com/domokit/mojo/issues/3
12. |
| 127 // As a workaround, always create the Transform and Opacity nodes. | 186 // As a workaround, always create the Transform and Opacity nodes. |
| 128 if (index == _activeCardIndex) { | 187 if (cardIndex == _activeCardIndex) { |
| 129 card = _activeCardTransform.build(card); | 188 card = _activeCardTransform.build(card); |
| 130 } else { | 189 } else { |
| 131 card = new Transform(child: card, transform: new Matrix4.identity()); | 190 card = new Transform(child: card, transform: new Matrix4.identity()); |
| 132 card = new Opacity(child: card, opacity: 1.0); | 191 card = new Opacity(child: card, opacity: 1.0); |
| 133 } | 192 } |
| 134 | 193 |
| 135 return new Listener( | 194 return new Listener( |
| 136 child: card, | 195 key: "$cardIndex", |
| 137 onPointerDown: (event) { _handlePointerDown(event, index); }, | 196 child: new Container(child: card, height: cardHeights[cardIndex]), |
| 197 onPointerDown: (event) { _handlePointerDown(event, cardIndex); }, |
| 138 onPointerMove: _handlePointerMove, | 198 onPointerMove: _handlePointerMove, |
| 139 onPointerUp: _handlePointerUpOrCancel, | 199 onPointerUp: _handlePointerUpOrCancel, |
| 140 onPointerCancel: _handlePointerUpOrCancel, | 200 onPointerCancel: _handlePointerUpOrCancel, |
| 141 onGestureFlingStart: _handleFlingStart | 201 onGestureFlingStart: _handleFlingStart |
| 142 ); | 202 ); |
| 143 } | 203 } |
| 144 | 204 |
| 145 Widget _buildCardCollection(List<double> heights) { | 205 Widget _builder(int index) { |
| 146 List<Widget> items = <Widget>[]; | 206 if (index >= visibleCardIndices.length) |
| 147 for(int index = 0; index < heights.length; index++) { | 207 return null; |
| 148 if (_dismissedCardIndices.contains(index)) | 208 return _buildCard(visibleCardIndices[index]); |
| 149 continue; | |
| 150 Color color = lerpColor(Red[500], Blue[500], index / heights.length); | |
| 151 items.add(new Container( | |
| 152 child: _buildCard(index, color), | |
| 153 height: heights[index] | |
| 154 )); | |
| 155 } | |
| 156 | |
| 157 return new Container( | |
| 158 child: new SizeObserver(child: new Block(items), callback: _handleSizeChan
ged), | |
| 159 padding: const EdgeDims.symmetric(vertical: 12.0, horizontal: 8.0), | |
| 160 decoration: new BoxDecoration(backgroundColor: Theme.of(this).primarySwatc
h[50]) | |
| 161 ); | |
| 162 } | 209 } |
| 163 | 210 |
| 164 Widget build() { | 211 Widget build() { |
| 212 Widget cardCollection = new Container( |
| 213 padding: const EdgeDims.symmetric(vertical: 12.0, horizontal: 8.0), |
| 214 decoration: new BoxDecoration(backgroundColor: Theme.of(this).primarySwatc
h[50]), |
| 215 child: new VariableHeightScrollable( |
| 216 builder: _builder, |
| 217 token: visibleCardIndices.length |
| 218 ) |
| 219 ); |
| 220 |
| 165 return new Scaffold( | 221 return new Scaffold( |
| 166 toolbar: new ToolBar(center: new Text('Swipe Away')), | 222 toolbar: new ToolBar(center: new Text('Swipe Away')), |
| 167 body: _buildCardCollection( | 223 body: new SizeObserver(child: cardCollection, callback: _handleSizeChanged
) |
| 168 [48.0, 64.0, 82.0, 46.0, 60.0, 55.0, 84.0, 96.0, 50.0, | |
| 169 48.0, 64.0, 82.0, 46.0, 60.0, 55.0, 84.0, 96.0, 50.0]) | |
| 170 ); | 224 ); |
| 171 } | 225 } |
| 172 } | 226 } |
| 173 | 227 |
| 174 void main() { | 228 void main() { |
| 175 runApp(new CardCollectionApp()); | 229 runApp(new CardCollectionApp()); |
| 176 } | 230 } |
| OLD | NEW |