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

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

相关推荐
极梦网络无忧21 分钟前
基于 Vite + Vue3 的组件自动注册功能
前端·javascript·vue.js
Predestination王瀞潞34 分钟前
5.4.3 通信->WWW万维网内容访问标准(W3C):WWW(World Wide Web) 协议架构(分层)
前端·网络·网络协议·架构·www
爱学习的程序媛44 分钟前
【Web前端】优化Core Web Vitals提升用户体验
前端·ui·web·ux·用户体验
zabr1 小时前
花了 100+ 篇笔记,我整理出 了一套 AI Agent 工程完全指南
前端·后端·agent
软弹1 小时前
深入理解 React Ref 机制:useRef 与 forwardRef 的协作原理
前端·javascript·react.js
YaHuiLiang1 小时前
Ai Coding浪潮下的前端:“AI在左,裁员在右”
前端
雪碧聊技术1 小时前
前端vue代码架子搭建
前端·javascript·vue.js·前端项目代码框架搭建
爱学习的程序媛1 小时前
【Web前端】前端用户体验优化全攻略
前端·ui·交互·web·ux·用户体验
han_1 小时前
JavaScript设计模式(二):策略模式实现与应用
前端·javascript·设计模式
x***r1511 小时前
Notepad++ 8.6 安装教程:详细步骤+自定义安装路径(附注意事项)
linux·前端·javascript