Flutter implementation of the Airbnb book flip UI
interaction_demo.mp4
The 3D-like book flip animation is implemented with Transform widgets for 3D rotation, and a Stack that conditionally shows the side of the book cover that should be visible on top as the rotation reaches halfway.
![](https://private-user-images.githubusercontent.com/50345358/241061155-9aba508e-bb9c-4dde-9de6-ea40676341f9.gif?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MTUwMzE1NjEsIm5iZiI6MTcxNTAzMTI2MSwicGF0aCI6Ii81MDM0NTM1OC8yNDEwNjExNTUtOWFiYTUwOGUtYmI5Yy00ZGRlLTlkZTYtZWE0MDY3NjM0MWY5LmdpZj9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNDA1MDYlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwNTA2VDIxMzQyMVomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWUzY2JkOGQ2Zjc0Yjc0NDQ1NjUwZmQ2YjJjODcyZjI3Y2I3NjI2MTM4ZmU4NWQ5OGQwMTIwOTQ2ODVhMjNjZDYmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JmFjdG9yX2lkPTAma2V5X2lkPTAmcmVwb19pZD0wIn0.th1X_5Q6__OSzd7ir_lNNZABNiBIGy1MGwNBFl-q-70)
// book_flip.dart
Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(-pi * _flipAnimation.value),
alignment: Alignment.centerRight,
child: Stack(
children: [
// Show the book cover front when the rotation is halfway
_flipAnimation.value >= 0.5
? Positioned.fill(
child: Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateY(-pi),
alignment: Alignment.center,
child: BookCoverFront(listingData),
),
)
: Positioned.fill(
child: BookCoverBack(listingData),
),
],
),
)
The trick to make the Hero animation work is using that same widget in 3 places, the first in the initial page, the second is the next page, with the animation controller initially set to it’s end value of 1, and finally, the animated widget is returned from the flightShuttleBuilder
param of the Hero widget, and uses its animation value to run the book flip animation.
// List page
Hero(
tag: 'listing_hero_${listingData.id}',
flightShuttleBuilder: (_, Animation<double> animation, __, ___, ____) {
return ScaleTransition(
scale: Tween<double>(begin: 0.45, end: 1).animate(animation),
child: BookFlip( // 3️⃣
listingData,
animationController: animation, // This will make the Hero animation run the flip animation
),
);
},
child: Transform.scale(
scale: 0.45, // Make sure initial value matches the value when the animation starts ↖️
child: BookFlip(listingData), // 1️⃣
),
)
// Details page
Hero(
tag: 'listing_hero_${listing.id}',
child: BookFlip( // 2️⃣
listingData,
initialFlipProgress: 1, // Hero destination widget should match the animation end state
),
)