Flutter 的 Hero Widget 有一个隐藏的超能力(大多数开发者从未使用过)

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 飞行的进度。非常适合用于 FadeTransitionScaleTransition 等动画。
  • 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"。

喜欢这篇文章吗?分享给你的开发者圈子吧,如果想获得更多类似技巧,请点击关注按钮。下次见❤️。

相关推荐
emma羊羊14 小时前
【CSRF】防御
前端·网络安全·csrf
Paddy哥14 小时前
html调起exe程序
前端·html
emma羊羊14 小时前
【CSRF】跨站请求伪造
前端·网络安全·csrf
折翼的恶魔14 小时前
HTML基本标签二:
前端·html
我是日安14 小时前
零到一打造 Vue3 响应式系统 Day 16 - 性能处理:LinkPool
前端·vue.js
一树山茶14 小时前
uniapp的双token
前端·javascript
正义的大古14 小时前
OpenLayers地图交互 -- 章节六:范围交互详解
前端·javascript·vue.js·openlayers
訾博ZiBo14 小时前
【文本朗读小工具】- 快速、免费的智能语音合成工具
前端
天蓝色的鱼鱼15 小时前
低代码是“未来”还是“骗局”?前端开发者有话说
前端
答案answer15 小时前
three.js着色器(Shader)实现数字孪生项目中常见的特效
前端·three.js