Skip to content

Commit

Permalink
Created different ProfileCard for each CardsSection (why? because I c…
Browse files Browse the repository at this point in the history
…an :p), added animations to the cards_section_alignment.dart, added a preview to the README.md
  • Loading branch information
Ivaskuu committed May 6, 2018
1 parent 5f9f956 commit 3c749db
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 68 deletions.
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
# Tinder cards

Hi! After showcasing my app (Focus for Reddit client), people asked me how did I do the tinder like cards swipe (posts media are shown as a stack of 3 swipable cards) and if I could make tutorial or open-source it.
Hi! After showcasing [Focus for Reddit](https://play.google.com/store/apps/details?id=com.skuu.focusreddit), the app I am working on, people asked me how did I do the tinder like cards swipe (posts media are shown as a stack of 3 swipable cards) and if I could make tutorial or open-source it:

![Focus for Reddit preview](https://i.imgur.com/vVyY7O5.gif)

And I did it! Here it is. I've created a Tinder like user interface (not working, that's not the point).

![Focus for Reddit preview](https://i.imgur.com/PM9AhLX.gif)

I've found 2 ways of doing this (there may be way more ways)
[ ] Using Draggable
[ ] Using GestureDetector and Alignment (what I use for my app)
- Using Draggable
- Using GestureDetector and Alignment (what I use for my app)

P.S: Use the appBar switch to go from Draggable cards section to GestureDetector and Alignment cards section.

# Draggable
This technique uses the already implemented drag system in Flutter. It's also pretty easy to set up:
Expand All @@ -29,6 +35,11 @@ The card rotation is then calculated based on the front card x alignment. On fin
- ProfileCard
- ProfileCard
- GestureDetector

## How animation works
// TODO

<hr>

For help getting started with Flutter, view our online
[documentation](https://flutter.io/).
240 changes: 184 additions & 56 deletions lib/cards_section_alignment.dart
Original file line number Diff line number Diff line change
@@ -1,35 +1,54 @@
import 'package:flutter/material.dart';
import 'profile_card.dart';
import 'profile_card_alignment.dart';
import 'dart:math';

List<Alignment> cardsAlign = [ new Alignment(0.0, 1.0), new Alignment(0.0, 0.8), new Alignment(0.0, 0.0) ];
List<Size> cardsSize = new List(3);

class CardsSectionAlignment extends StatefulWidget
{
CardsSectionAlignment(BuildContext context)
{
cardsSize[0] = new Size(MediaQuery.of(context).size.width * 0.9, MediaQuery.of(context).size.height * 0.6);
cardsSize[1] = new Size(MediaQuery.of(context).size.width * 0.85, MediaQuery.of(context).size.height * 0.55);
cardsSize[2] = new Size(MediaQuery.of(context).size.width * 0.8, MediaQuery.of(context).size.height * 0.5);
}

@override
_CardsSectionState createState() => new _CardsSectionState();
}

class _CardsSectionState extends State<CardsSectionAlignment>
class _CardsSectionState extends State<CardsSectionAlignment> with SingleTickerProviderStateMixin
{
bool dragOverTarget = false;
List<ProfileCard> cards = new List();
int cardsCounter = 0;

final Alignment backCardAlign = new Alignment(0.0, 0.8);
final Alignment middleCardAlign = new Alignment(0.0, 0.55);
final Alignment defaultFrontCardAlign = new Alignment(0.0, 0.0);
Alignment frontCardAlign = new Alignment(0.0, 0.0);
List<ProfileCardAlignment> cards = new List();
AnimationController _controller;

final Alignment defaultFrontCardAlign = new Alignment(0.0, 0.0);
Alignment frontCardAlign;
double frontCardRot = 0.0;

@override
void initState()
{
super.initState();

// Init cards
for (cardsCounter = 0; cardsCounter < 3; cardsCounter++)
{
cards.add(new ProfileCard(cardsCounter));
cards.add(new ProfileCardAlignment(cardsCounter));
}

frontCardAlign = cardsAlign[2];

// Init the animation controller
_controller = new AnimationController(duration: new Duration(milliseconds: 700), vsync: this);
_controller.addListener(() => setState(() {}));
_controller.addStatusListener((AnimationStatus status)
{
if(status == AnimationStatus.completed) changeCardsOrder();
});
}

@override
Expand All @@ -41,41 +60,12 @@ class _CardsSectionState extends State<CardsSectionAlignment>
(
children: <Widget>
[
// Back card
new Align
(
alignment: backCardAlign,
child: new IgnorePointer(child: new SizedBox.fromSize
(
size: new Size(MediaQuery.of(context).size.width * 0.8, MediaQuery.of(context).size.height * 0.5),
child: cards[2],
)),
),
// Middle card
new Align
(
alignment: middleCardAlign,
child: new IgnorePointer(child: new SizedBox.fromSize
(
size: new Size(MediaQuery.of(context).size.width * 0.85, MediaQuery.of(context).size.height * 0.55),
child: cards[1],
)),
),
// Front card
new Align
(
alignment: frontCardAlign,
child: new SizedBox.fromSize
(
size: new Size(MediaQuery.of(context).size.width * 0.9, MediaQuery.of(context).size.height * 0.6),
child: new Transform.rotate
(
angle: (pi / 180.0) * frontCardRot,
child: cards[0],
),
),
),
new SizedBox.expand
backCard(),
middleCard(),
frontCard(),

// Prevent swiping if the cards are animating
_controller.status == AnimationStatus.forward ? new SizedBox.expand
(
child: new GestureDetector
(
Expand All @@ -89,10 +79,10 @@ class _CardsSectionState extends State<CardsSectionAlignment>
frontCardAlign = new Alignment
(
frontCardAlign.x + 20 * details.delta.dx / MediaQuery.of(context).size.width,
frontCardAlign.y + 20 * details.delta.dy / MediaQuery.of(context).size.height
frontCardAlign.y + 40 * details.delta.dy / MediaQuery.of(context).size.height
);

frontCardRot = frontCardAlign.x /* * rotation speed */;
frontCardRot = frontCardAlign.x; // * rotation speed;
});
},
// When releasing the first card
Expand All @@ -101,35 +91,173 @@ class _CardsSectionState extends State<CardsSectionAlignment>
// If the front card was swiped far enough to count as swiped
if(frontCardAlign.x > 3.0 || frontCardAlign.x < -3.0)
{
changeCardsOrder();
animateCards();
}

// Return to the initial rotation and alignment
setState(()
else
{
frontCardAlign = defaultFrontCardAlign;
frontCardRot = 0.0;
});
// Return to the initial rotation and alignment
setState(()
{
frontCardAlign = defaultFrontCardAlign;
frontCardRot = 0.0;
});
}
},
)
),
) : new Container(),
],
)
);
}

Widget backCard()
{
return new Align
(
alignment: _controller.status == AnimationStatus.forward ? CardsAnimation.backCardAlignmentAnim(_controller).value : cardsAlign[0],
child: new SizedBox.fromSize
(
size: _controller.status == AnimationStatus.forward ? CardsAnimation.backCardSizeAnim(_controller).value : cardsSize[2],
child: cards[2]
),
);
}

Widget middleCard()
{
return new Align
(
alignment: _controller.status == AnimationStatus.forward ? CardsAnimation.middleCardAlignmentAnim(_controller).value : cardsAlign[1],
child: new SizedBox.fromSize
(
size: _controller.status == AnimationStatus.forward ? CardsAnimation.middleCardSizeAnim(_controller).value : cardsSize[1],
child: cards[1]
),
);
}

Widget frontCard()
{
return new Align
(
alignment: _controller.status == AnimationStatus.forward ? CardsAnimation.frontCardDisappearAlignmentAnim(_controller, frontCardAlign).value : frontCardAlign,
child: new Transform.rotate
(
angle: (pi / 180.0) * frontCardRot,
child: new SizedBox.fromSize
(
size: cardsSize[0],
child: cards[0]
),
)
);
}

void changeCardsOrder()
{
setState(()
{
// Swap cards
// Swap cards (back card becomes the middle card; middle card becomes the front card, front card becomes a new bottom card)
var temp = cards[0];
cards[0] = cards[1];
cards[1] = cards[2];
cards[2] = temp;

cards[2] = new ProfileCard(cardsCounter);
cards[2] = new ProfileCardAlignment(cardsCounter);
cardsCounter++;

frontCardAlign = defaultFrontCardAlign;
frontCardRot = 0.0;
});
}

void animateCards()
{
_controller.stop();
_controller.value = 0.0;
_controller.forward();
}
}

class CardsAnimation
{
static Animation<Alignment> backCardAlignmentAnim(AnimationController parent)
{
return new AlignmentTween
(
begin: cardsAlign[0],
end: cardsAlign[1]
).animate
(
new CurvedAnimation
(
parent: parent,
curve: new Interval(0.4, 0.7, curve: Curves.easeIn)
)
);
}

static Animation<Size> backCardSizeAnim(AnimationController parent)
{
return new SizeTween
(
begin: cardsSize[2],
end: cardsSize[1]
).animate
(
new CurvedAnimation
(
parent: parent,
curve: new Interval(0.4, 0.7, curve: Curves.easeIn)
)
);
}

static Animation<Alignment> middleCardAlignmentAnim(AnimationController parent)
{
return new AlignmentTween
(
begin: cardsAlign[1],
end: cardsAlign[2]
).animate
(
new CurvedAnimation
(
parent: parent,
curve: new Interval(0.2, 0.5, curve: Curves.easeIn)
)
);
}

static Animation<Size> middleCardSizeAnim(AnimationController parent)
{
return new SizeTween
(
begin: cardsSize[1],
end: cardsSize[0]
).animate
(
new CurvedAnimation
(
parent: parent,
curve: new Interval(0.2, 0.5, curve: Curves.easeIn)
)
);
}

static Animation<Alignment> frontCardDisappearAlignmentAnim(AnimationController parent, Alignment beginAlign)
{
return new AlignmentTween
(
begin: beginAlign,
end: new Alignment(beginAlign.x > 0 ? beginAlign.x + 30.0 : beginAlign.x - 30.0, 0.0) // Has swiped to the left or right?
).animate
(
new CurvedAnimation
(
parent: parent,
curve: new Interval(0.0, 0.5, curve: Curves.easeIn)
)
);
}
}
12 changes: 6 additions & 6 deletions lib/cards_section_draggable.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'profile_card.dart';
import 'profile_card_draggable.dart';

class CardsSectionDraggable extends StatefulWidget
{
Expand All @@ -10,7 +10,7 @@ class CardsSectionDraggable extends StatefulWidget
class _CardsSectionState extends State<CardsSectionDraggable>
{
bool dragOverTarget = false;
List<ProfileCard> cards = new List();
List<ProfileCardDraggable> cards = new List();
int cardsCounter = 0;

@override
Expand All @@ -20,7 +20,7 @@ class _CardsSectionState extends State<CardsSectionDraggable>

for (cardsCounter = 0; cardsCounter < 3; cardsCounter++)
{
cards.add(new ProfileCard(cardsCounter));
cards.add(new ProfileCardDraggable(cardsCounter));
}
}

Expand Down Expand Up @@ -51,7 +51,7 @@ class _CardsSectionState extends State<CardsSectionDraggable>
// Back card
new Align
(
alignment: new Alignment(0.0, 0.8),
alignment: new Alignment(0.0, 1.0),
child: new IgnorePointer(child: new SizedBox.fromSize
(
size: new Size(MediaQuery.of(context).size.width * 0.8, MediaQuery.of(context).size.height * 0.5),
Expand All @@ -61,7 +61,7 @@ class _CardsSectionState extends State<CardsSectionDraggable>
// Middle card
new Align
(
alignment: new Alignment(0.0, 0.55),
alignment: new Alignment(0.0, 0.8),
child: new IgnorePointer(child: new SizedBox.fromSize
(
size: new Size(MediaQuery.of(context).size.width * 0.85, MediaQuery.of(context).size.height * 0.55),
Expand Down Expand Up @@ -102,7 +102,7 @@ class _CardsSectionState extends State<CardsSectionDraggable>
cards[1] = cards[2];
cards[2] = temp;

cards[2] = new ProfileCard(cardsCounter);
cards[2] = new ProfileCardDraggable(cardsCounter);
cardsCounter++;
});
}
Expand Down
Loading

0 comments on commit 3c749db

Please sign in to comment.