Flutter 的 Hero Widget 很有名。每个人都见过它:点击一张图片,它会平滑地过渡到新屏幕。这简直是瞬间的魔法。但问题在于------大多数开发者止步于基础。他们使用默认的 Hero 动画,然后就完事了。
我以前也是这样,直到我在信息流中看到了这个短视频。
看到它之后,我深受触动:我们竟然可以修改 Hero 动画!😲
我一直以为它只是基于 tag
的魔法------但事实并非如此,它的幕后还有更多精彩。
认识 flightShuttleBuilder
。
Hero widget 藏着一个秘密的超能力:flightShuttleBuilder
。
你可以把它想象成你过渡动画的**"特效导演"**。通常情况下,Flutter 会决定一个 Hero 是如何在屏幕之间"飞行"的------一个简单的淡入淡出和缩放。这很不错,但有点......可预测。
有了 flightShuttleBuilder
,你就能坐上导演椅。你可以让你的 Hero 弹跳、旋转、变形,甚至把它变成一个土豆飞过屏幕,只要你喜欢。
但在我们跳到 DartPad 演示之前,让我们快速分解一下这个回调函数会给你哪些参数。
dart
Widget flightShuttleBuilder(
BuildContext flightContext,
Animation<double> animation,
HeroFlightDirection flightDirection,
BuildContext fromHeroContext,
BuildContext toHeroContext,
)
以下是每个参数的作用:
flightContext
→ 正在"飞行"的 Hero 的构建上下文(很少用到,但在你需要主题或MediaQuery
时很方便)。animation
→ 一个从 0 到 1 的值,告诉你 Hero 飞行的进度。非常适合用于FadeTransition
、ScaleTransition
等动画。flightDirection
→ 告诉你 Hero 是向前**(推入)还是向后(弹出)**飞行。你可以让你的 Hero 在返回时有不同的表现。fromHeroContext
→ 来源页面上的 Hero Widget(用户点击的那个)。toHeroContext
→ 目标页面上的 Hero Widget(你正在飞向的那个)。
这是一个展示两种自定义 Hero 动画的例子:一个会弹跳,一个会旋转。
dart
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),
),
),
),
);
}
可能性是无穷无尽的------一旦你开始尝试,你将永远不会再以同样的方式看待 Hero 动画。
所以下一次,当有人告诉你**"Flutter 动画都千篇一律"**时,你只需微笑......然后向他们展示你的"土豆 Hero"。
喜欢这篇文章吗?分享给你的开发者圈子吧,如果想获得更多类似技巧,请点击关注按钮。下次见❤️。