This repository was archived by the owner on Feb 25, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 6k
Card Collection dismiss animation #122
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,62 +2,145 @@ | |
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
import 'package:sky/animation/animation_performance.dart'; | ||
import 'package:sky/animation/curves.dart'; | ||
import 'package:sky/base/lerp.dart'; | ||
import 'package:sky/painting/text_style.dart'; | ||
import 'package:sky/theme/colors.dart'; | ||
import 'package:sky/widgets/animated_component.dart'; | ||
import 'package:sky/widgets/basic.dart'; | ||
import 'package:sky/widgets/block_viewport.dart'; | ||
import 'package:sky/widgets/card.dart'; | ||
import 'package:sky/widgets/dismissable.dart'; | ||
import 'package:sky/widgets/scaffold.dart'; | ||
import 'package:sky/widgets/variable_height_scrollable.dart'; | ||
import 'package:sky/widgets/scaffold.dart'; | ||
import 'package:sky/widgets/theme.dart'; | ||
import 'package:sky/widgets/tool_bar.dart'; | ||
import 'package:sky/widgets/widget.dart'; | ||
import 'package:sky/theme/colors.dart' as colors; | ||
import 'package:sky/widgets/task_description.dart'; | ||
|
||
class CardModel { | ||
CardModel(this.value, this.height, this.color); | ||
int value; | ||
double height; | ||
Color color; | ||
AnimationPerformance performance; | ||
String get label => "Item $value"; | ||
String get key => value.toString(); | ||
bool operator ==(other) => other is CardModel && other.value == value; | ||
int get hashCode => 373 * 37 * value.hashCode; | ||
} | ||
|
||
class ShrinkingCard extends AnimatedComponent { | ||
|
||
ShrinkingCard({ | ||
String key, | ||
CardModel this.card, | ||
Function this.onUpdated, | ||
Function this.onCompleted | ||
}) : super(key: key); | ||
|
||
CardModel card; | ||
Function onUpdated; | ||
Function onCompleted; | ||
|
||
double get currentHeight => card.performance.variable.value; | ||
|
||
void initState() { | ||
assert(card.performance != null); | ||
card.performance.addListener(handleAnimationProgress); | ||
watch(card.performance); | ||
} | ||
|
||
void handleAnimationProgress() { | ||
if (card.performance.isCompleted) { | ||
if (onCompleted != null) | ||
onCompleted(); | ||
} else if (onUpdated != null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will fire onUpdated when you're complete if you have no onCompleted, but not if you do have an onCompleted. It seems weird that setting onComplete would affect whether you get an onUpdate for a particular condition. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree; I've fixed that. |
||
onUpdated(); | ||
} | ||
} | ||
|
||
void syncFields(ShrinkingCard source) { | ||
card = source.card; | ||
onCompleted = source.onCompleted; | ||
onUpdated = source.onUpdated; | ||
super.syncFields(source); | ||
} | ||
|
||
Widget build() => new Container(height: currentHeight); | ||
} | ||
|
||
class CardCollectionApp extends App { | ||
|
||
final TextStyle cardLabelStyle = | ||
new TextStyle(color: white, fontSize: 18.0, fontWeight: bold); | ||
|
||
final List<double> cardHeights = [ | ||
48.0, 64.0, 82.0, 46.0, 60.0, 55.0, 84.0, 96.0, 50.0, | ||
48.0, 64.0, 82.0, 46.0, 60.0, 55.0, 84.0, 96.0, 50.0, | ||
48.0, 64.0, 82.0, 46.0, 60.0, 55.0, 84.0, 96.0, 50.0, | ||
48.0, 64.0, 82.0, 46.0, 60.0, 55.0, 84.0, 96.0, 50.0 | ||
]; | ||
|
||
List<int> visibleCardIndices; | ||
BlockViewportLayoutState layoutState = new BlockViewportLayoutState(); | ||
List<CardModel> cardModels; | ||
|
||
void initState() { | ||
visibleCardIndices = new List.generate(cardHeights.length, (i) => i); | ||
List<double> cardHeights = <double>[ | ||
48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0, | ||
48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0, | ||
48.0, 63.0, 82.0, 146.0, 60.0, 55.0, 84.0, 96.0, 50.0 | ||
]; | ||
cardModels = new List.generate(cardHeights.length, (i) { | ||
Color color = lerpColor(Red[300], Blue[900], i / cardHeights.length); | ||
return new CardModel(i, cardHeights[i], color); | ||
}); | ||
super.initState(); | ||
} | ||
|
||
void dismissCard(int cardIndex) { | ||
void shrinkCard(CardModel card, int index) { | ||
if (card.performance != null) | ||
return; | ||
layoutState.invalidate([index]); | ||
setState(() { | ||
visibleCardIndices.remove(cardIndex); | ||
assert(card.performance == null); | ||
card.performance = new AnimationPerformance() | ||
..duration = const Duration(milliseconds: 300) | ||
..variable = new AnimatedType<double>( | ||
card.height + kCardMargins.top + kCardMargins.bottom, | ||
end: 0.0, | ||
curve: ease, | ||
interval: new Interval(0.5, 1.0) | ||
) | ||
..play(); | ||
}); | ||
} | ||
|
||
Widget _builder(int index) { | ||
if (index >= visibleCardIndices.length) | ||
void dismissCard(CardModel card) { | ||
if (cardModels.contains(card)) { | ||
setState(() { | ||
cardModels.remove(card); | ||
}); | ||
} | ||
} | ||
|
||
Widget builder(int index) { | ||
if (index >= cardModels.length) | ||
return null; | ||
CardModel card = cardModels[index]; | ||
|
||
if (card.performance != null) { | ||
return new ShrinkingCard( | ||
key: card.key, | ||
card: card, | ||
onUpdated: () { layoutState.invalidate([index]); }, | ||
onCompleted: () { dismissCard(card); } | ||
); | ||
} | ||
|
||
int cardIndex = visibleCardIndices[index]; | ||
Color color = lerpColor(Red[500], Blue[500], cardIndex / cardHeights.length); | ||
Widget label = new Text("Item ${cardIndex}", style: cardLabelStyle); | ||
return new Dismissable( | ||
key: cardIndex.toString(), | ||
onDismissed: () { dismissCard(cardIndex); }, | ||
key: card.key, | ||
onDismissed: () { shrinkCard(card, index); }, | ||
child: new Card( | ||
color: color, | ||
color: card.color, | ||
child: new Container( | ||
height: cardHeights[cardIndex], | ||
height: card.height, | ||
padding: const EdgeDims.all(8.0), | ||
child: new Center(child: label) | ||
child: new Center(child: new Text(card.label, style: cardLabelStyle)) | ||
) | ||
) | ||
); | ||
|
@@ -68,16 +151,17 @@ class CardCollectionApp extends App { | |
padding: const EdgeDims.symmetric(vertical: 12.0, horizontal: 8.0), | ||
decoration: new BoxDecoration(backgroundColor: Theme.of(this).primarySwatch[50]), | ||
child: new VariableHeightScrollable( | ||
builder: _builder, | ||
token: visibleCardIndices.length | ||
builder: builder, | ||
token: cardModels.length, | ||
layoutState: layoutState | ||
) | ||
); | ||
|
||
return new Theme( | ||
data: new ThemeData( | ||
brightness: ThemeBrightness.light, | ||
primarySwatch: colors.Blue, | ||
accentColor: colors.RedAccent[200] | ||
primarySwatch: Blue, | ||
accentColor: RedAccent[200] | ||
), | ||
child: new TaskDescription( | ||
label: 'Cards', | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
where do you remove the listener?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't remove it. The entire CardModel, including its lazily created AnimationPerformance, is thrown away once the animation has completed.
In the next version of this code I will replace ShrikingCard with Animated Container.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We want our examples to be copy-pasta-safe, so that if people copy them they end up with good code. So in general we should assume that the classes we write for the examples are as generic as those in the main library. If this class were to stay, we should make sure we remove the listener when the instance goes away.
If you're getting rid of the class soon anyway, though, whatever. :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The next version of this part of the example should be relatively permanent. I will make it copy-pasta-safe.