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"。

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

相关推荐
WindrunnerMax15 小时前
从零实现富文本编辑器#7-基于组合事件的半受控输入模式
前端·前端框架·github
Cache技术分享15 小时前
177. Java 注释 - 重复注释
前端·后端
rocksun15 小时前
如何使用Enhance构建App:后端优先框架指南
前端·前端框架·前端工程化
Mike的AI工坊15 小时前
[知识点记录]createWebHistory的用法
前端
红色石头本尊15 小时前
8-Gsap动画库基本使用与原理
前端
小高00716 小时前
🎯v-for 先还是 v-if 先?Vue2/3 编译真相
前端·javascript·vue.js
原生高钙16 小时前
大模型的流式响应实现
前端·ai编程
zzywxc78716 小时前
如何利用AI IDE快速构建一个简易留言板系统
开发语言·前端·javascript·ide·vue.js·人工智能·前端框架
CaracalTiger16 小时前
网站漏洞早发现:cpolar+Web-Check安全扫描组合解决方案
java·开发语言·前端·python·安全·golang·wpf