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