Flutter Motion Kit

可预览的 Flutter 动画 + 避坑指南 · 支持 Claude Code 一键复用

← 返回

Hero 共享元素转场

分类 hero · 难度 2/5 · 验证于 Flutter 3.32 / 2026-06

点击网格项,元素「飞」到详情页对应位置。两端用同一个 Hero tag,框架自动接管过渡。

⏳ 在线预览未就绪:运行 node scripts/sync-gists.mjs 生成 gist 后即可内嵌。

代码

// ✅ 推荐:列表页与详情页用同一个唯一 tag,框架自动接管共享元素飞行。
// 可直接粘进 DartPad (https://dartpad.dev) 运行。
import 'package:flutter/material.dart';

void main() => runApp(const _App());

class _App extends StatelessWidget {
  const _App();
  @override
  Widget build(BuildContext context) => MaterialApp(
        debugShowCheckedModeBanner: false,
        theme: ThemeData(useMaterial3: true),
        home: const _GridPage(),
      );
}

const _colors = [
  Colors.red, Colors.green, Colors.blue,
  Colors.orange, Colors.purple, Colors.teal,
];

class _GridPage extends StatelessWidget {
  const _GridPage();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Hero gallery')),
      body: GridView.count(
        crossAxisCount: 3,
        padding: const EdgeInsets.all(12),
        mainAxisSpacing: 12,
        crossAxisSpacing: 12,
        children: [
          for (final (i, c) in _colors.indexed)
            GestureDetector(
              onTap: () => Navigator.of(context).push(
                MaterialPageRoute(builder: (_) => _DetailPage(index: i, color: c)),
              ),
              child: Hero(
                tag: 'box-$i', // 唯一 tag,与详情页一致
                child: Container(
                  decoration: BoxDecoration(
                    color: c,
                    borderRadius: BorderRadius.circular(12),
                  ),
                ),
              ),
            ),
        ],
      ),
    );
  }
}

class _DetailPage extends StatelessWidget {
  const _DetailPage({required this.index, required this.color});
  final int index;
  final Color color;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: Center(
        child: Hero(
          tag: 'box-$index', // 与列表页同一个 tag
          child: Container(
            width: 260,
            height: 260,
            decoration: BoxDecoration(
              color: color,
              borderRadius: BorderRadius.circular(24),
            ),
          ),
        ),
      ),
    );
  }
}

⚠️ 坑(4)

同一屏幕同时出现两个相同 tag 的 Hero 会直接抛异常。
✅ tag 必须全局唯一(用 id 拼接,如 'box-$i'),同屏不可重复。
official-docs · 出处
嵌套 Navigator(如 BottomNavigationBar 内)里的 Hero 不会跨外层路由飞行。
✅ 确保起止页在同一个 Navigator;或用 flightShuttleBuilder 自定义飞行组件。
official-docs · 出处
首尾尺寸/圆角差异大时,默认矩形插值可能出现突变或裁切。
✅ 用 createRectTween 提供更平滑的飞行轨迹,子组件保持一致的 BoxFit/形状。
official-docs · 出处
Hero 飞行期间原位置会留白。
✅ 需要占位时用 placeholderBuilder 填充。
official-docs · 出处

官方文档