Flutter Tutorial
Parallax Gallery View Animation in Flutter

Recently, while I was viewing google photos, the memory carousel at the of the page got my attention. It was moving horizontally with a nice parallax animation from both left and right end. I really liked it and decided to give it a try in Flutter and the final result looks like as shown below

In the beginning it looked slightly tough to build but I managed to get through and let’s see how it was built.
The Conceptual part
Let’s see what are the details in this animation so as to use them in our implementation.

- there are 2 complete images along with either one image with 85% portion visible or two images with ~42% portion is visible at any given time
- as soon as any image hits the left side of the screen or is out to the right side 15%, its parallax starts
- if image is entering in the left side then the image parallax is towards right and vice versa
- there is 8 px of left margin on each image which is required to be considered in the calculations
- lastly, scroll offset is the key for all the calculations

The Implementation
Create GalleryCard
We first create our main GalleryCard as follows
class GalleryCard extends StatelessWidget {
final String image;
final int index;
final double screenWidth;
final double scrollOffset;
const GalleryCard(
{super.key,
required this.image,
required this.index,
required this.screenWidth,
required this.scrollOffset});
@override
Widget build(BuildContext context) {
// basic calculations
final itemWidth = screenWidth / 2.85;
final itemHeight = itemWidth * 1.5;
return Container(
width: itemWidth,
height: itemHeight,
clipBehavior: Clip.antiAlias,
decoration: const BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.all(Radius.circular(24))),
child: Image.asset(
image,
fit: BoxFit.cover,
alignment: Alignment(
0,
0
),
),
);
}
}
Now we add this card few times in a row and put that row in a horizontal scrollview as follows
@override
Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return SingleChildScrollView( // Scrollview
scrollDirection: Axis.horizontal,
controller: controller,
child: Container(
color: Colors.white,
child: Row( // Row containing our Gallery card
children: [
...List.generate(images.length, (index) {
return Padding(
padding: const EdgeInsets.only(left: 8.0),
child: GalleryCard(
screenWidth: width,
scrollOffset: scrollOffset,
image: images[index],
index: index,
),
);
})
],
),
),
);
}
If run our code upto this step then we would get something as shown

Card Dimensions Calculations
We’ll set up the left side parallax by doing the calculations as per the conceptual section as follows
final marginOffset = 8 * (index + 1);
final lowerRange = (index * itemWidth) + marginOffset;
final highRange = ((index + 1) * itemWidth) + marginOffset;
- calculating the margin offset for each card. this is the padding that we provided to each card from left. This margin is to be considered otherwise the movement for each card will not be consistent.
- then we calculate the lower (left) and high (right) position of each card
Left Side Parallax
Using the card dimensions, we can calculate few values for left side as follows
// left side calculations
final isInLeftSideRange = scrollOffset >= lowerRange && scrollOffset <= highRange;
final leftSideOffset = highRange - scrollOffset;
final leftSideDivisor = index > 0 ? (lowerRange - marginOffset) / index : highRange - marginOffset;
- isInLeftSideRange : indicates if the scrolling offset lies within this card’s area. If yes then it means the card has started entering the left side of the screen.
- leftSideOffset : value which indicates how much the card has been moved into the left side
- leftSideDivisor : a denominator to calculate the percentage value for parallax
Now we calculate the fraction offset which indicates the percentage of the card into the left side as follows
final fractionOffset = (offset < 0 ? 1 : leftSideOffset) / leftSideDivisor;
Finally we can calculate the value for our parallax as follows
final leftParallaxPercent = isInLeftSideRange ? (1 - fractionOffset) : 0.0;
We can apply this percent into the x coordinate of the image Alignment as follows
child: Image.asset(
image,
fit: BoxFit.cover,
alignment: Alignment(
leftParallaxPercent,
0
),
),
If we run our code upto this step, we would get our gallery view with only left side parallax as follows

Right Side Parallax
Similarly as we found out the left side parallax percentage, we can find out the right side one but with slight updates.
// right side calculations
final rightSideScrollOffset = lowerRange - scrollOffset;
final rightSideOffsetThreshold = screenWidth - (itemWidth * 0.85);
final isInRightSideRange = rightSideScrollOffset > rightSideOffsetThreshold && rightSideScrollOffset < screenWidth;
final rightSideOffset = rightSideScrollOffset - rightSideOffsetThreshold;
final rightSideDivisor = screenWidth - rightSideOffsetThreshold + marginOffset;
- rightSideScrollOffset: value indicate the amount of card scrolled into the right side of the screen
- rightSideOffsetThreshold: maximum value upto which parallax to be shown when scrolling from right to left
- isInRightSideRange: if the card has entered into the right side of the screen when scrolling from left to right
- rightSideOffset: amount by which the card is visible when scrolling from right to left
- rightSideDivisor: a denominator to calculate the percentage value for parallax
Final caluclations
As we already have left side values, we can adjust the final calculation with both left and right side values as follows
// final calculations
final offset = isInLeftSideRange ? leftSideOffset : rightSideOffset;
final divisor = isInLeftSideRange ? leftSideDivisor : rightSideDivisor;
final fractionOffset = (offset < 0 ? 1 : offset) / divisor;
final leftParallaxPercent = isInLeftSideRange ? (1 - fractionOffset) : 0.0;
final rightParallaxPercent = isInRightSideRange ? fractionOffset : 1.0;
final leftPercent = -leftParallaxPercent;
final rightPercent = rightParallaxPercent;
Replacing the above values in our image widget alignment as follows
child: Image.asset(
image,
fit: BoxFit.cover,
alignment: Alignment(
isInLeftSideRange ? leftPercent : (isInRightSideRange ? rightPercent : 0.0),
0
),
),
Running our code now would give us our final result.
Bamn! We’ve successfully created our Parallax Gallery animation from both end.
Hope you’ve enjoyed it! You can watch the speed code video on YouTube
Or can checkout the repo below
Comment down to let me know if you have any inputs on this.
That is all for now! Stay tuned!
Connect with me (if the content is helpful to you ) on
Until next time…
Cheers!