I’m building an app with a Hero
animation that transitions from a home screen to a second screen. Here’s a simplified version of my code:
// A single tile, placed in a grid
class BeverageTile extends StatelessWidget {
final Beverage beverage;
const BeverageTile({
super.key,
required this.beverage,
});
@override
Widget build(BuildContext context) {
final String price = beverage.price == null ? 'Free' : '$${beverage.price}';
return GestureDetector(
onTap: () => Navigator.of(context).push(
FadePageRoute(
page: BeverageSelectionScreen(beverage: beverage),
),
),
child: Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.1),
borderRadius: BorderRadius.circular(16.0),
),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(16.0),
topRight: Radius.circular(16.0),
),
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12.0,
vertical: 6.0,
),
child: Text(
price,
style: const TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w700,
),
),
),
),
],
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(
top: 16.0,
left: 16.0,
right: 16.0,
),
// SETUP OF THE HERO
child: Hero(
createRectTween: (begin, end) {
return RectTween(begin: begin, end: end);
},
tag: 'beverage_${beverage.name}',
child: SvgPicture.asset(
beverage.imagePath,
fit: BoxFit.contain,
),
),
),
),
Padding(
padding: const EdgeInsets.symmetric(
vertical: 16.0,
horizontal: 8.0,
),
child: Text(
beverage.name,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 16.0),
),
),
],
),
),
);
}
}
// Second screen
class BeverageSelectionScreen extends StatelessWidget {
final Beverage beverage;
const BeverageSelectionScreen({super.key, required this.beverage});
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
color: Color(0xFF1a1718),
),
child: Padding(
padding: const EdgeInsets.all(72.0),
child: Hero(
tag: 'beverage_${beverage.name}',
child: SvgPicture.asset(
beverage.imagePath,
fit: BoxFit.contain,
),
),
),
);
}
}
I’ve also implemented a custom PageRouteBuilder to handle the transition animation between pages. The Hero animation works as expected, but the image currently follows a curved path, which is the default behavior according to the Material Design motion spec.
Question: How can I customize the path of the Hero animation so that the image moves along a linear path instead of the default curved trajectory?
2
Answers
If you want to have a little bit more control over the curve of the
Hero
liniear path, create a new class that extendsRectTween
:By default,
Hero
usesMaterialRectArcTween
, which creates a curved animation path. To make the hero follow a linear path, use aRectTween
in thecreateRectTween
property:Important:
Make sure to override
createRectTween
in bothHero
widgets (on both pages) to ensure that the reverse animation is linear as well.Full example: