Tinderlike animation
Tinderlike animation, created with Flutter.
Summary
| Latest Version | Unknown |
|---|---|
| License | Unknown |
| CI Status | Failing |
| Stars | 1 |
| Forks | 0 |
| Open Issues | 0 |
| Last Commit | 2021-04-26 |
| Downloads | 0 |
| Last Indexed | 2026-08-11 05:07 |
Tags
Installation
nimble install Tinderlike animation
choosenim install Tinderlike animation
git clone https://gitlab.com/SyphonJr/tinderlike-ui-animation
OS Compatibility
| Platform | Linux | macOS | Windows | FreeBSD | OpenBSD | NetBSD | Android | iOS | WASM | Embedded |
|---|---|---|---|---|---|---|---|---|---|---|
| Tinderlike animation | ✓ | ✓ | ✓ | - | - | - | - | - | - | - |
Source
| Repository | https://gitlab.com/SyphonJr/tinderlike-ui-animation |
|---|---|
| Homepage | https://gitlab.com/SyphonJr/tinderlike-ui-animation |
| Registry Source | gitlab |
README
Project
Flutter UI challenge
Trying this Tinder-like UI and animations.
GIF of the app

Getting started
Lets break the ui / animations down first.
First we have a sort of container that shows a card on top and 2 behind it. When the top card gets removed, the ones behind it will animate to the front. If there's no more cards besides the 3, then the result should only be 2 cards after the removal.
Second, there's the card, which should be draggable and removable, either by dragging or pressing one of the buttons. The card should also be able to expand to a screen size. Last thing this card should be able to do is slide the picture up to show more content. There are also a lot of small animations when some actions happens. Not sure if I will cover that.
That's actually it. These two items can be independent from one another. Starting with the container first.
Card Container
My first thought is, I need a stack where I can show card elements on top of each other. And then wrap the cards in translate / scale widgets, since these widgets ignore hittesting.
Stack(
children: [
(Transform.scale(
scale: 0.8,
child: Transform.translate(
offset: Offset(0, -200), child: widget.listCards[1]))),
widget.listCards[0],
],
),
So the transform.translate / transform.scale widgets along with the stack widget does the job alright. Three problems at the moment. 1. I don't really know how much I have to translate the card widget since I don't know the size of the widget beforehand. 2. I also don't like that I translate the widget to outside of the Stack widgets boundary. This makes the Container widget less user-friendly for future users. 3. The transform happens after all the layouting is done, so if I want to align certain cards, the resizing happens after layout.
I could use the transform widget to create depth, so I can just 'move' the card widget back a little so it appears smaller. This way I don't need to think about the size scalings. Still need to know how far I should translate the widget tho.
I can also use the LayoutBuilder widget and know the full size of the parent widget.
Transform.translate(
offset: Offset(0, -90),
child: Container(
child: widget.listCards[2],
height: constraints.maxHeight - 90,
width: constraints.maxWidth - 70,
),
),
Transform.translate(
offset: Offset(0, -45),
child: Container(
child: widget.listCards[1],
height: constraints.maxHeight - 70,
width: constraints.maxWidth - 35,
),
),
Container(
child: widget.listCards[0],
height: constraints.maxHeight - 50,
width: constraints.maxWidth,
)
)
What irks me though is the use of fixed numbers. Should use percentages instead so the ratio doesn't look weird on bigger / smaller cards.
Transform.translate(
offset: Offset(
0,
-(constraints.maxHeight / 10) -
(constraints.maxHeight / 10 * 2)),
child: Container(
child: widget.listCards[2],
height: constraints.maxHeight / 100 * 70,
width: constraints.maxWidth / 100 * 80,
),
),
Transform.translate(
offset: Offset(
0,
-(constraints.maxHeight / 10) / 2 -
(constraints.maxHeight / 10)),
child: Container(
child: widget.listCards[1],
height: constraints.maxHeight / 100 * 80,
width: constraints.maxWidth / 100 * 90,
),
),
Container(
child: widget.listCards[0],
height: constraints.maxHeight / 100 * 90,
width: constraints.maxWidth,
),
)
Alright so this worked out. The cards dont look deformed anymore with different dimensions. Every card is 10% smaller in height and width. With the height of the front card starting with -10%, because I want all the cards to fit in the parent stack. This way it's more intuitive to use. I'll make the size multiplier a variable that can be passed through the constructor, to make it more customizable.
One more problem. This code assumes the cards don't have a size. What if the dimensions of the cards are set? First of all, it's better to align my widgets to the top. At the moment they're glued to the bottom and pushed upwards, but it should be the other way around. If the cards makes use of all the space in the Stack, then it's whatever, but if it doesn't, pushing from the top is more intuitive.
So how do I know whether a card has a set height or not. I could make the list of cards inherit from a custom widget or I could not care at all, and assume the end user always wants to use the maximum space. I'm going to go with the latter for now.
Now, for the cards sliding forward. There are 2 cards sliding forward. The second card, which has a bouncy effect. and the third one which just slides forward. I thought about using TweenAnimationBuilder / ImplicitlyAnimatedWidget, but these widgets remember the state the card is in and animates from there when values changes. I want to reset the values after animation ends.
So, the card after the swiped card looks like it slides forward and bounces, but it actually just replaces the first card and grows smaller and larger, making it look like it slid from behind.
To implement that springy curve I implemented (read: stole), a custom curve, and made the size of card springy.
/// Springy curve, from here https://blog.funwith.app/posts/custom-curves-in-flutter/
class SpringCurve extends Curve {
const SpringCurve({
this.a = 0.15,
this.w = 19.4,
});
final double a;
final double w;
@override
double transformInternal(double t) {
return -(pow(e, -t / a) * cos(t * w)) + 1;
}
}
To create a custom curve, one needs to implement their own function, with the conditions that x = 0 = 0 and x = 1 = 1. This spring curve is not exactly like the one in dribbble, but if I can always mess a little bit with the function in order to make it look more like it.
So after implementing some transforms, every card animates to their new place, when the first card gets thrown out. In order to be able to loop it, I need to add a statuslistener to my controller.
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
setState(() {
_listCards.removeAt(0);
_controller.stop();
_controller.reset();
});
}
});
So only one thing needs to be done now for this widget and that's animating the first card away
enum SwipeDirection { left, right }
class CarouselController {
Function(SwipeDirection) dismiss;
}
// In carouselState
@override
void initState() {
widget.carouselController.dismiss = dismiss;
Before creating the animation, I created a controller for the parent class to use to dismiss a card. This way parent widget is able to access a method from the child widget. More functionality can be added to this controller, like checking how many items are left in the list, but it's not needed for now.
Transform.translate(
offset: Tween<Offset>(begin: Offset.zero, end: Offset(0, -500))
.animate(CurvedAnimation(
curve: Interval(0, 0.800, curve: Curves.ease),
parent: _controller))
.value,
child: Transform.translate(
offset: Offset(_horizontalTween.value, 0),
child: Transform.rotate(
angle: _rotateTween.value,
For the animation of the first card I used a combination of transforms to create the desired effect. Still using the same controller as for the other animations. And that's all for this widget. I could also make the first card draggable, and thought of a way how to do it. Just create a second controller instead of using the same as every other animation. After that change the controllers value depending on the drag. BUT, I'm gonna leave it at this for now.
Note: Atm I'm using hard coded values for the last animation. TODO: change it, depending on the size of the card.
Expanding card
When you press on the card, a detailscreen should be created from the expanding card. Luckily Flutter already has a widget for this, the hero widget.
Hero(
tag: '$id buttons',
child:
By just adding a hero widget to both the card and the detail screen. The image will animate/fly to it's new destination on the new screen.
Notes
- The image flickers because it takes time to load
- Don't instantiate tweens everytime