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

Flutter:页面跳转时Hero动画失效,仅返回时生效问题求助

Hero Animation Not Working When Navigating to Detail Page (Only Works on Pop) in Flutter

I've run into this exact issue before! The problem here is that when you navigate to the detail page, the Hero widget on that page hasn't been built yet. Let me break it down:

Your list page's Hero is rendered once the FutureBuilder finishes loading products, so it's ready when you tap to navigate. But the detail page's Hero is wrapped inside its own FutureBuilder—meaning it only gets created after the single-product API call completes. By that time, the navigation transition has already started, and Flutter can't find a matching Hero tag on the destination page, so the animation skips entirely. The return animation works because the list page's Hero is already loaded and waiting.

Here are two solid fixes, depending on your needs:


Since your list page already has the full product data from the API, there's no need to make a second call for the same item. Just pass the entire product object to the detail page—this lets you render the Hero immediately, no waiting for API responses.

Step 1: Update the navigation in all_products.dart

Change your onTap to pass the full product instead of just the ID:

onTap: () {
  Navigator.push(
    context,
    MaterialPageRoute(
      builder: (context) => SingleProduct(product: snapshot.data[index]),
    ),
  );
},

Step 2: Rewrite single_product.dart to use the passed data

You can simplify this to a StatelessWidget now, since you don't need to fetch data:

import 'package:flutter/material.dart';

class SingleProduct extends StatelessWidget {
  final Map<String, dynamic> product;

  const SingleProduct({Key? key, required this.product}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Products"),
      ),
      body: Container(
        color: Colors.white,
        child: Column(
          children: [
            Hero(
              tag: product["id"],
              child: Container(
                color: Colors.transparent,
                child: Center(
                  child: Image.network(product["image"], height: 200, width: 200),
                ),
              ),
            ),
            Expanded(
              child: Container(
                color: Colors.transparent,
                child: Card(
                  color: Colors.white,
                  elevation: 20.0,
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.only(
                      topRight: Radius.circular(20),
                      topLeft: Radius.circular(20),
                    ),
                  ),
                  child: Container(
                    padding: EdgeInsets.all(10.0),
                    child: Column(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        Container(
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Text(
                                product["title"],
                                textAlign: TextAlign.center,
                                style: TextStyle(
                                  fontWeight: FontWeight.bold,
                                  fontSize: 16,
                                ),
                              ),
                              SizedBox(height: 5),
                              Row(
                                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                                children: [
                                  Text(
                                    "\$: ${product["price"]}",
                                    style: TextStyle(fontSize: 16),
                                  ),
                                  Row(
                                    children: [
                                      Text(
                                        product["rating"]["rate"].toString(),
                                        style: TextStyle(fontSize: 16),
                                      ),
                                      Icon(Icons.star, color: Colors.yellow, size: 20),
                                    ],
                                  ),
                                ],
                              ),
                              SizedBox(height: 5),
                              Text(
                                "Category: ${product["category"]}",
                                textAlign: TextAlign.left,
                                style: TextStyle(fontSize: 16),
                              ),
                              SizedBox(height: 5),
                              Text(
                                product["description"],
                                textAlign: TextAlign.justify,
                                style: TextStyle(fontSize: 16),
                              ),
                            ],
                          ),
                        ),
                        Align(
                          alignment: Alignment.bottomCenter,
                          child: Container(
                            padding: EdgeInsets.all(15),
                            decoration: BoxDecoration(
                              shape: BoxShape.rectangle,
                              borderRadius: BorderRadius.circular(50),
                              color: Colors.black,
                            ),
                            height: 50,
                            width: 130,
                            child: Row(
                              mainAxisAlignment: MainAxisAlignment.spaceBetween,
                              children: [
                                Icon(Icons.shopping_cart, color: Colors.white),
                                Text("Add to cart", style: TextStyle(color: Colors.white)),
                              ],
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

This not only fixes the Hero animation but also cuts down on unnecessary API calls—win-win!


Fix 2: Add a Placeholder Hero (If You Must Fetch Data)

If you need to fetch fresh data for the detail page (e.g., for real-time updates), you can render a placeholder Hero that matches the size of your list page's Hero before the API call completes. This gives Flutter a matching tag to animate to immediately.

Update single_product.dart like this:

import 'package:flutter/material.dart';
import 'package:httprequest/services/api_services.dart';

class SingleProduct extends StatefulWidget {
  final int id;
  SingleProduct(this.id);

  @override
  _SingleProductState createState() => _SingleProductState();
}

class _SingleProductState extends State<SingleProduct> {
  Future? product;

  @override
  void initState() {
    product = ApiServices().getSingleProduct(widget.id);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Products"),
      ),
      body: FutureBuilder(
        future: product,
        builder: (context, AsyncSnapshot snapshot) {
          // Always render the Hero first, even before data loads
          return Container(
            color: Colors.white,
            child: Column(
              children: [
                Hero(
                  tag: widget.id,
                  child: Container(
                    color: Colors.transparent,
                    height: 200,
                    width: 200,
                    child: snapshot.hasData
                        ? Image.network(snapshot.data["image"], height: 200, width: 200)
                        : // Match the list page's image container style here
                        Container(color: Colors.grey[200]),
                  ),
                ),
                // Show content once data is ready, else loading indicator
                snapshot.hasData
                    ? Expanded(
                        child: Container(
                          color: Colors.transparent,
                          child: Card(
                            color: Colors.white,
                            elevation: 20.0,
                            shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.only(
                                topRight: Radius.circular(20),
                                topLeft: Radius.circular(20),
                              ),
                            ),
                            child: Container(
                              padding: EdgeInsets.all(10.0),
                              child: Column(
                                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                                children: [
                                  Container(
                                    child: Column(
                                      crossAxisAlignment: CrossAxisAlignment.start,
                                      children: [
                                        Text(
                                          snapshot.data["title"],
                                          textAlign: TextAlign.center,
                                          style: TextStyle(
                                            fontWeight: FontWeight.bold,
                                            fontSize: 16,
                                          ),
                                        ),
                                        SizedBox(height: 5),
                                        Row(
                                          mainAxisAlignment: MainAxisAlignment.spaceBetween,
                                          children: [
                                            Text(
                                              "\$: ${snapshot.data["price"]}",
                                              style: TextStyle(fontSize: 16),
                                            ),
                                            Row(
                                              children: [
                                                Text(
                                                  snapshot.data["rating"]["rate"].toString(),
                                                  style: TextStyle(fontSize: 16),
                                                ),
                                                Icon(Icons.star, color: Colors.yellow, size: 20),
                                              ],
                                            ),
                                          ],
                                        ),
                                        SizedBox(height: 5),
                                        Text(
                                          "Category: ${snapshot.data["category"]}",
                                          textAlign: TextAlign.left,
                                          style: TextStyle(fontSize: 16),
                                        ),
                                        SizedBox(height: 5),
                                        Text(
                                          snapshot.data["description"],
                                          textAlign: TextAlign.justify,
                                          style: TextStyle(fontSize: 16),
                                        ),
                                      ],
                                    ),
                                  ),
                                  Align(
                                    alignment: Alignment.bottomCenter,
                                    child: Container(
                                      padding: EdgeInsets.all(15),
                                      decoration: BoxDecoration(
                                        shape: BoxShape.rectangle,
                                        borderRadius: BorderRadius.circular(50),
                                        color: Colors.black,
                                      ),
                                      height: 50,
                                      width: 130,
                                      child: Row(
                                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                                        children: [
                                          Icon(Icons.shopping_cart, color: Colors.white),
                                          Text("Add to cart", style: TextStyle(color: Colors.white)),
                                        ],
                                      ),
                                    ),
                                  ),
                                ],
                              ),
                            ),
                          ),
                        ),
                      )
                    : Expanded(child: Center(child: CircularProgressIndicator())),
              ],
            ),
          );
        },
      ),
    );
  }
}

This way, the Hero exists on both pages during the navigation transition, so the animation works as expected.


Either approach should fix your issue. The first one is cleaner and more efficient, so I'd go with that unless you have a specific reason to refetch the product data.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.06 06:49:42