You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Hero组件无法在Widget与Dialog间实现过渡动画

Flutter Hero 过渡动画未生效排查与修复

我实现了一个包含图标、文本和容器背景的SquareTile组件,同时封装了基于showDialog的自定义showCustomDialog函数。点击SquareTile会弹出对话框,我给背景、图标、文本三个元素设置了Hero标签,预期触发过渡动画,但动画完全没效果,也没有报错信息。


tile.dart(SquareTile组件代码)

import 'package:flutter/material.dart';

// ignore: must_be_immutable
class SquareTile extends StatelessWidget {
  Icon icon;
  String text = '';
  String bgTag;
  String iconTag;
  String textTag;
  SquareTile({
    required this.icon,
    required this.text,
    required this.bgTag,
    required this.iconTag,
    required this.textTag,
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        Hero(
          tag: bgTag,
          child: Material(
            type: MaterialType.transparency,
            child: Container(
              width: 300,
              height: 50,
              decoration: BoxDecoration(
                color: Colors.grey[100],
                border: Border.all(color: Colors.white),
                borderRadius: const BorderRadius.all(
                  Radius.circular(10),
                ),
              ),
            ),
          ),
        ),
        Positioned(
          left: 10,
          top: 0,
          bottom: 0,
          child: SizedBox(
            height: 50,
            width: 50,
            child: Hero(
              tag: iconTag,
              child: Material(type: MaterialType.transparency, child: icon),
            ),
          ),
        ),
        Positioned(
          left: 80,
          top: 0,
          bottom: 0,
          child: SizedBox(
            width: 150,
            child: Center(
              child: Hero(
                tag: textTag,
                child: Material(
                  type: MaterialType.transparency,
                  child: Text(
                    text,
                    textAlign: TextAlign.center,
                    style: const TextStyle(
                      color: Colors.black,
                      fontWeight: FontWeight.w800,
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
      ],
    );
  }
}

showCustomDialog.dart(自定义对话框代码)

import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:smooth_like_flutter/components/service_field.dart';

void showCustomDialog(
  BuildContext context,
  Icon icon,
  String title,
  String rates,
  String bgTag,
  String iconTag,
  String textTag,
) {
  final quantityController = TextEditingController();
  final linkController = TextEditingController();
  showDialog(
      context: context,
      builder: (BuildContext context) {
        return Scaffold(
          resizeToAvoidBottomInset: true,
          backgroundColor: Colors.transparent,
          body: Stack(
            children: [
              GestureDetector(
                onTap: () => Navigator.pop(context),
                child: SizedBox(
                  width: double.infinity,
                  height: double.infinity,
                  child: BackdropFilter(
                    filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
                    child: Container(
                      color: Colors.black.withOpacity(0),
                    ),
                  ),
                ),
              ),
              Align(
                alignment: Alignment.center,
                child: Hero(
                  tag: bgTag,
                  child: Container(
                    height: 400,
                    width: 280,
                    decoration: BoxDecoration(
                        border: Border.all(color: Colors.white, width: 1.5),
                        color: Colors.grey[300],
                        borderRadius:
                            const BorderRadius.all(Radius.circular(10))),
                  ),
                ),
              ),
              Align(
                alignment: Alignment.center,
                child: ConstrainedBox(
                  constraints: const BoxConstraints(maxHeight: 350),
                  child: Column(
                    children: [
                      const SizedBox(
                        height: 10,
                      ),
                      Hero(
                        tag: iconTag,
                        child: const Icon(
                          Icons.group_rounded,
                          color: Colors.black,
                          size: 40,
                        ),
                      ),
                      Hero(
                        tag: textTag,
                        child: Material(
                          type: MaterialType.transparency,
                          child: Text(
                            title,
                            textAlign: TextAlign.center,
                            style: const TextStyle(
                              color: Colors.black,
                              fontWeight: FontWeight.w800,
                            ),
                          ),
                        ),
                      ),
                      const SizedBox(
                        height: 5,
                      ),
                      Text(
                        rates,
                        style: TextStyle(color: Colors.grey[700]),
                      ),
                      const SizedBox(
                        height: 30,
                      ),
                      MyServiceField(
                        type: TextInputType.number,
                        controller: quantityController,
                        hint: 'Quantity',
                      ),
                      const SizedBox(
                        height: 10,
                      ),
                      MyServiceField(
                        type: TextInputType.emailAddress,
                        controller: linkController,
                        hint: 'Link',
                      ),
                      const SizedBox(
                        height: 35,
                      ),
                      Container(
                        width: 200,
                        height: 50,
                        decoration: const BoxDecoration(
                            color: Colors.black,
                            borderRadius:
                                BorderRadius.all(Radius.circular(30))),
                        child: const Center(
                          child: Text(
                            'Place Order',
                            style: TextStyle(
                                color: Colors.white,
                                fontWeight: FontWeight.bold),
                          ),
                        ),
                      )
                    ],
                  ),
                ),
              ),
            ],
          ),
        );
      });
}

instagram.dart(SquareTile初始化代码)

SizedBox(
            height: 400,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                GestureDetector(
                  onTap: () {
                    showCustomDialog(
                      context,
                      const Icon(Icons.group_rounded),
                      'Followers',
                      '100 -> 100',
                      'followers_bg',
                      'followers-icon',
                      'followers-text',
                    );
                  },
                  child: SquareTile(
                      icon: const Icon(Icons.group_rounded),
                      text: 'Followers',
                      bgTag: 'followers_bg',
                      iconTag: 'followers-icon',
                      textTag: 'follwers-text'),
                ),
              ],
            ),
          )

问题排查与修复方案

1. 标签拼写错误(核心问题之一)

instagram.dart中SquareTile的textTag拼写为'follwers-text',但调用showCustomDialog时传入的是'followers-text',标签不匹配直接导致Hero动画失效。修正后确保两端标签完全一致:

SquareTile(
  // ...其他参数
  textTag: 'followers-text', // 修正拼写
)

2. 对话框Hero组件的Material包裹缺失

Hero动画要求两端组件的渲染上下文一致,对话框中的背景和图标Hero缺少Material(type: MaterialType.transparency)包裹,导致渲染异常:

  • 修复背景Hero:
Hero(
  tag: bgTag,
  child: Material(
    type: MaterialType.transparency,
    child: Container(/* ...原有容器属性 */),
  ),
)
  • 修复图标Hero:同时替换硬编码图标为传入的icon参数,确保前后组件一致:
Hero(
  tag: iconTag,
  child: Material(
    type: MaterialType.transparency,
    child: icon, // 使用传入的icon参数
  ),
)

3. 对话框路由的兼容性调整

showDialog的默认路由处理可能干扰Hero动画,若上述修复后仍无效,可改用Navigator.push弹出自定义页面路由,或确保对话框的Scaffold处于正确的MaterialApp上下文范围内。

4. 尺寸过渡的一致性优化

SquareTile背景尺寸为300x50,对话框背景为280x400,尺寸差异较大,可适当调整两端的Decoration属性(如border宽度、圆角)保持一致,避免动画异常。


内容的提问来源于stack exchange,提问作者Sami Khan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 10:23:11