Create sliding animation using Flow widget, AnimationController, and GestureDetector

Sachin Kumar Singh
FlutterFlakes
Published in
4 min readJul 28, 2024

In this article, I will explain how we can use tools like AnimationController and GestureDetector along with the Flow widget to achieve some basic sliding behaviors.

But before going through this article, I recommend checking out my previous article here on the Flow widget if you are new to it.

So, let’s get started. Below, you can see what we will try to achieve.

The entire problem can be broken down into three steps:

Step 1:Create a stack of circular cards using Flow widget.

Circular stacked cards

Create a list of circular card and then provide this list as child to Flow widget.

In the code below, there are three classes:

  1. FlowExample: The main stateful widget that contains the Flow widget along with the list of circular cards.
  2. ListFlowDelegate: Defines the delegate for the Flow widget used in the FlowExample class.
  3. CircularCard: Creates the circular card, which takes two text parameters for the card.

class FlowExample extends StatefulWidget {
const FlowExample({Key? key}) : super(key: key);

@override
State<FlowExample> createState() => _FlowExampleState();
}

class _FlowExampleState extends State<FlowExample> {

@override
Widget build(BuildContext context) {
return Container(
height: 250,
padding: const EdgeInsets.only(top: 100),
child: Flow(
clipBehavior: Clip.antiAlias,
delegate: ListFlowDelegate(),
children: [
for (int i = 1; i < 4; i++)
CircularCard(
title: carsData[i].name,
subTitle: carsData[i].place,
color: carsData[i].color,
),
],
),
);
}
}

class ListFlowDelegate extends FlowDelegate {
@override
void paintChildren(FlowPaintingContext context) {
for (int i = 0; i < context.childCount; i++) {
// setting x-coordinate for each card to be 10px ahead of previous card
double dx = i * 10;
context.paintChild(i, transform: Matrix4.translationValues(dx, 0, 0));
}
}
@override
bool shouldRepaint(ListFlowDelegate oldDelegate) {
return false;
}
}

//Circular cards Widget
class CircularCard extends StatelessWidget {
//find complete code in repo
}

Step 2: To detect left and right swipe, add a gesture detector to the Flow Widget.

read more about GestureDetector here.

GestureDetector(
onHorizontalDragEnd: (DragEndDetails drag)
{
if (drag.primaryVelocity == null) return;
if (drag.primaryVelocity! < 0) {
//left to right
} else {
//right to left
}
},
child: Flow(
clipBehavior: Clip.antiAlias,
delegate: ListFlowDelegate(dragAnimation: dragAnimation),
...

),
)

Step 3: Update the x-coordinate of each card using an animation controller when the user slides left or right.

First, you need to add the animation controller and initialize it with your desired values. You can read more about AnimationController here.

class _FlowExampleState extends State<FlowExample> 
with SingleTickerProviderStateMixin {
late AnimationController dragAnimation;
@override
void initState() {
super.initState();
dragAnimation = AnimationController(
lowerBound: 0,
value: 0,
upperBound: 1,
duration: const Duration(milliseconds: 500),
vsync: this,
);
}
.
.
.

The above animation controller will create a sequence of values between 0 and 1 within 500 ms.

Call the forward() and reverse() methods of the AnimationController class inside your gesture detector to trigger the animation in the forward and reverse directions.

GestureDetector(
onHorizontalDragEnd: (DragEndDetails drag) {
if (drag.primaryVelocity == null) return;
if (drag.primaryVelocity! < 0)
{
//left to right
dragAnimation.reverse();
//generated value: 1 -> 0
} else {
//right to left
dragAnimation.forward();
//generated value: 0 -> 1
}
},
child:...,
)

Pass the AnimationController to your Flow delegate class so that you can use the sequence of values generated by the AnimationController to update the x-coordinate of each card.

Flow(
clipBehavior: Clip.antiAlias,
delegate: ListFlowDelegate(dragAnimation: dragAnimation),
...
}

class ListFlowDelegate extends FlowDelegate {
ListFlowDelegate({required this.dragAnimation})
: super(repaint: dragAnimation);

final Animation<double> dragAnimation;
...
}

The only thing remaining is to use the values generated by the animation controller to move the cards along the x-axis. The idea is simple:

  • The first card does not need to move at all.
  • The displacement for the second card should be: diameter + spacing.
  • The displacement for the third card should be: 2 * diameter + spacing.

We can achieve this by computing the x-coordinate using the formula below:

dx = cardIndex * spacing + cardSize * cardIndex

However, using this formula results in a very sudden transition, where the sliding behavior is not clearly visible. To achieve a smoother transition, use the animation controller value:

dx = cardIndex * spacing + cardSize * cardIndex * animationControllerValue

now paintChildren() will look something like this

void paintChildren(FlowPaintingContext context) {
for (int i = 0; i < context.childCount; i++) {
double dx = i * 10 +
(context.getChildSize(i)!.width.toDouble() * i * dragAnimation.value);
context.paintChild(i, transform: Matrix4.translationValues(dx, 0, 0));
}
}

So finally, everything is ready, and you can now swipe left or right to slide the cards.

you can find the complete code here.

--

--