O Widget Hero do Flutter tem um Superpoder Oculto (Que a maioria dos desenvolvedores nunca usa)
O widget Hero do Flutter é famoso. Todo mundo já viu: você toca em uma imagem e ela faz uma transição suave para uma nova tela. Magia instantânea. Mas aqui está o detalhe: a maioria dos desenvolvedores para no básico. Eles usam a animação padrão do Hero e dão o trabalho por encerrado.
Eu também era assim, até que encontrei um short (vídeo curto) no meu feed. Após assisti-lo, a ficha caiu: nós podemos, de fato, modificar as animações do Hero! 😲
Eu sempre presumi que fosse apenas uma “bruxaria” baseada em tags — mas não, há muito mais sob o capô.
Conheça o flightShuttleBuilder.
O widget Hero esconde um superpoder secreto: o flightShuttleBuilder.
Pense nele como o diretor de efeitos especiais da sua transição. Normalmente, o Flutter decide como um Hero “voa” entre as telas — um simples esmaecer (fade) e redimensionar (scale). Legal, mas um pouco… previsível.
Com o flightShuttleBuilder, você assume a cadeira do diretor. Você pode fazer seu Hero quicar, girar, transformar-se em outra coisa ou, sim — até voar pela tela como uma batata, se essa for a sua vibe.
Mas antes de mergulharmos em uma demonstração no DartPad, vamos analisar rapidamente os parâmetros que este callback te entrega.
Widget flightShuttleBuilder( BuildContext flightContext, Animation<double> animation, HeroFlightDirection flightDirection, BuildContext fromHeroContext, BuildContext toHeroContext, )
Aqui está o que cada parâmetro te oferece:
-
flightContext→ O contexto de construção (build context) do Hero em pleno voo (raramente usado, mas útil se você precisar de temas ou media queries). -
animation→ Um valor de 0 a 1 que indica o progresso do “voo” do Hero. Perfeito para conectar a widgets comoFadeTransition,ScaleTransition, etc. -
flightDirection→ Informa se o Hero está indo para frente (push) ou voltando para trás (pop). Você pode fazer seu Hero se comportar de forma diferente ao retornar. -
fromHeroContext→ O widget Hero na página de origem (aquele em que o usuário tocou). -
toHeroContext→ O widget Hero na página de destino (aquele para onde você está voando).
Aqui está um exemplo que mostra duas animações customizadas de Hero: uma que quica e outra que gira.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Hero Animation Shortcut',
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Hero Animations")),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
onTap: () => Navigator.push(context,
MaterialPageRoute(builder: (_) => const BouncyPage())),
child: Hero(
tag: 'bouncy-hero',
flightShuttleBuilder:
(ctx, animation, direction, from, to) {
return ScaleTransition(
scale: animation.drive(
Tween(begin: 0.5, end: 1.2).chain(
CurveTween(curve: Curves.elasticOut),
),
),
child: to.widget,
);
},
child: const CircleAvatar(
radius: 40,
backgroundColor: Colors.blue,
child:
Icon(Icons.flutter_dash, color: Colors.white, size: 40),
),
),
),
const SizedBox(height: 50),
GestureDetector(
onTap: () => Navigator.push(context,
MaterialPageRoute(builder: (_) => const SpinPage())),
child: Hero(
tag: 'spin-hero',
flightShuttleBuilder:
(ctx, animation, direction, from, to) {
return RotationTransition(
turns: animation,
child: FadeTransition(
opacity: animation,
child: to.widget,
),
);
},
child: const CircleAvatar(
radius: 40,
backgroundColor: Colors.green,
child: Icon(Icons.star, color: Colors.white, size: 40),
),
),
),
],
),
);
}
}
class BouncyPage extends StatelessWidget {
const BouncyPage({super.key});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text("Bouncy Hero")),
body: Center(
child: Hero(
tag: 'bouncy-hero',
child: const CircleAvatar(
radius: 120,
backgroundColor: Colors.purple,
child: Icon(Icons.flutter_dash,
color: Colors.white, size: 120),
),
),
),
);
}
class SpinPage extends StatelessWidget {
const SpinPage({super.key});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text("Spinning Hero")),
body: Center(
child: Hero(
tag: 'spin-hero',
child: const CircleAvatar(
radius: 120,
backgroundColor: Colors.red,
child: Icon(Icons.star, color: Colors.white, size: 120),
),
),
),
);
}
As possibilidades são infinitas — e assim que você começar a experimentar, nunca mais verá as animações de Hero da mesma forma.
Então, na próxima vez que alguém lhe disser que ‘as animações do Flutter são todas iguais’, apenas sorria… e mostre a eles o seu Hero de batata.