0Pricing
Flutter Mobile Development · درس

انتقالات Hero وحركة العناصر المشتركة

نسّق عناصر Hero ومكوكات الطيران المخصصة لإنشاء انتقالات متقنة بين الشاشات

انتقالات Hero وحركة العناصر المشتركة درس مجاني في Flutter Mobile Development على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Flutter Mobile Development، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Flutter Mobile Development 4 دروس في المجموع.

بعض أجزاء هذا الدرس لم تُترجم بعد وتظهر باللغة الإنجليزية.

What Shared Element Motion Means

A Hero transition animates a single widget so it appears to fly from one screen to another during a route push or pop. Instead of the user losing track of an element across screens, the same visual object morphs in place.

  • Think of a thumbnail in a list that expands into a full-screen detail image.
  • Flutter computes the start and end rectangles and tweens the widget between them.
  • This continuity is what designers call shared element motion.

In this lesson you will coordinate Hero widgets and customize the flight shuttle that actually renders during the in-between frames.

The Hero Widget Contract

To create a Hero animation you wrap the source and destination widgets in a Hero widget and give both the same tag. The tag is how Flutter pairs them across routes.

  • Tags must be unique per screen but identical between the two screens.
  • If two Heroes on the same screen share a tag, Flutter throws an assertion at navigation time.

Below, a product thumbnail on the list screen uses the product id as its tag.

Hero(
  tag: 'product-${product.id}',
  child: Image.network(
    product.thumbnailUrl,
    width: 80,
    height: 80,
    fit: BoxFit.cover,
  ),
)

Matching the Destination Hero

The detail screen must wrap its large image in a Hero with the exact same tag. When you push the detail route, Flutter detects both Heroes and starts the flight automatically.

  • The child widgets do not have to be identical — only the tag must match.
  • Here the destination is a full-width image, while the source was an 80x80 thumbnail.
Hero(
  tag: 'product-${product.id}',
  child: Image.network(
    product.imageUrl,
    width: double.infinity,
    height: 320,
    fit: BoxFit.cover,
  ),
)

How Tags Drive Pairing

During a route transition Flutter scans both the outgoing and incoming routes for Heroes, then builds a map keyed by tag. Only tags present on both routes animate; an unmatched Hero simply fades with its page.

Because tags are matched by equality, you can use any object as a tag — strings, ints, or enums — as long as == and hashCode behave correctly.

bool heroesMatch(Object tagA, Object tagB) {
  return tagA == tagB;
}

void main() {
  print(heroesMatch('product-7', 'product-7')); // true -> flies
  print(heroesMatch('product-7', 'product-9')); // false -> fades
  print(heroesMatch(42, 42));                    // true
}

The Default Flight Shuttle

The widget rendered during the flight is called the flight shuttle. By default Flutter uses the destination Hero's child for the entire flight, cross-fading where needed.

This works well when the two children look alike. But when the source and destination have very different shapes — for example a square avatar becoming a rounded banner — the default can look like an abrupt swap. That is when you supply a custom flightShuttleBuilder.

Customizing the Flight Shuttle

The flightShuttleBuilder lets you control exactly what is painted mid-flight. Its signature gives you the animation, the direction (push or pop), and both Hero contexts.

  • flightDirection is HeroFlightDirection.push or .pop.
  • Return any widget; it will be positioned inside the interpolated rectangle for you.

Here we rotate the child slightly as it flies to add polish.

Hero(
  tag: 'product-${product.id}',
  flightShuttleBuilder: (
    BuildContext flightContext,
    Animation<double> animation,
    HeroFlightDirection direction,
    BuildContext fromContext,
    BuildContext toContext,
  ) {
    return RotationTransition(
      turns: Tween<double>(begin: 0, end: 0.05).animate(animation),
      child: toContext.widget,
    );
  },
  child: Image.network(product.imageUrl, fit: BoxFit.cover),
)

Cross-Fading Between Two Children

A common shuttle pattern is cross-fading from the source child to the destination child so the morph reads smoothly. You can drive a FadeTransition from the same flight animation.

  • On push, fade the source out and the destination in.
  • On pop, reverse the roles using flightDirection.
Widget buildShuttle(
  BuildContext flightContext,
  Animation<double> animation,
  HeroFlightDirection direction,
  BuildContext fromContext,
  BuildContext toContext,
) {
  final Widget fromHero = (fromContext.widget as Hero).child;
  final Widget toHero = (toContext.widget as Hero).child;
  final bool isPush = direction == HeroFlightDirection.push;
  return Stack(
    fit: StackFit.expand,
    children: [
      FadeTransition(
        opacity: Tween<double>(begin: isPush ? 1 : 0, end: isPush ? 0 : 1)
            .animate(animation),
        child: fromHero,
      ),
      FadeTransition(
        opacity: Tween<double>(begin: isPush ? 0 : 1, end: isPush ? 1 : 0)
            .animate(animation),
        child: toHero,
      ),
    ],
  );
}

Shaping the Flight Path

By default the Hero moves along a straight line. You can bend that path with createRectTween, which controls how the bounding rectangle is interpolated.

Material's MaterialRectArcTween gives a curved, more natural arc — the same motion Material Design recommends for shared element transitions.

import 'package:flutter/material.dart';

RectTween arcTween(Rect? begin, Rect? end) {
  return MaterialRectArcTween(begin: begin, end: end);
}

// Usage on the Hero:
// Hero(
//   tag: 'product-7',
//   createRectTween: arcTween,
//   child: ...,
// )

Avoiding Tag Collisions in Lists

The most frequent Hero bug is a duplicate tag on a single screen. In a ListView of items, every Hero needs a tag unique to its data row.

  • Never use a constant string like 'hero' for all items.
  • Derive the tag from a stable identity such as the model id.

This helper guarantees uniqueness when building list rows.

String heroTagFor(String screen, Object id) {
  return '$screen::$id';
}

void main() {
  final ids = [1, 2, 2, 3];
  final tags = <String>{};
  for (final id in ids) {
    final tag = heroTagFor('list', id);
    if (!tags.add(tag)) {
      print('Collision on $tag');
    } else {
      print('OK $tag');
    }
  }
}

Wrapping Heroes in Material

When a Hero's child has elevation, rounded corners, or ink — like a Card or Material surface — the flight can show clipped or square corners because the in-flight widget is detached from its original ancestors.

Wrap the shuttle (or the children) in a Material with type: MaterialType.transparency so text, shadows, and clipping render correctly mid-flight.

Hero(
  tag: 'card-${item.id}',
  flightShuttleBuilder: (ctx, anim, dir, from, to) {
    return Material(
      type: MaterialType.transparency,
      child: (to.widget as Hero).child,
    );
  },
  child: Material(
    type: MaterialType.transparency,
    child: ProductCard(item: item),
  ),
)

Coordinating Hero with Rive

In this track we pair Hero transitions with Rive animations for richer motion. A clean approach is to let the Hero handle position and size, while a Rive state machine plays a micro-animation on arrival.

  • Drive a Rive SMITrigger when the destination route finishes its transition.
  • Use animation.addStatusListener on the flight to fire the trigger at AnimationStatus.completed.

Keep the Hero shuttle simple; let Rive own the expressive part so the two systems do not fight over the same frames.

void fireOnArrival(Animation<double> animation, void Function() onArrived) {
  animation.addStatusListener((status) {
    if (status == AnimationStatus.completed) {
      onArrived(); // e.g. riveTrigger.fire();
    }
  });
}

Quick Check

You have a grid of avatars; tapping one pushes a profile screen. The avatar is a 56x56 circle, and the destination is a 200-tall rounded banner. The default flight looks like an abrupt swap and the corners flicker square during motion. Which combination best fixes this?

Recap

You now know how to build polished shared element transitions:

  • Matching tags on source and destination Heroes drive the pairing; tags must be unique per screen and identical across screens.
  • The flight shuttle is what renders mid-flight; customize it with flightShuttleBuilder to cross-fade differing children.
  • Wrap shuttle content in a transparent Material to keep shadows, text, and rounded corners correct.
  • Bend the path with createRectTween and MaterialRectArcTween for a natural arc.
  • Let Hero own position and hand expressive micro-motion to a Rive state machine, fired on AnimationStatus.completed.

Combine these to make screen changes feel continuous and intentional.

الأسئلة الشائعة

هل درس «انتقالات Hero وحركة العناصر المشتركة» مجاني؟

نعم — نص درس «انتقالات Hero وحركة العناصر المشتركة» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Flutter Mobile Development، انتقل إلى CoddyKit PRO. تتضمن دورة Flutter Mobile Development 4 دروس في المجموع.

ماذا ستتعلم في «انتقالات Hero وحركة العناصر المشتركة»؟

نسّق عناصر Hero ومكوكات الطيران المخصصة لإنشاء انتقالات متقنة بين الشاشات تتمرن على Flutter Mobile Development مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Flutter Mobile Development؟

لا تُشترط خبرة سابقة. Flutter Mobile Development على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.

كم من الوقت يستغرق درس «انتقالات Hero وحركة العناصر المشتركة»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Flutter Mobile Development هذا؟

نعم. كل درس في Flutter Mobile Development يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. تضمين أصول Rive ووحدات التحكم
  2. آلات الحالات والحركة المدفوعة بالإدخال
  3. انتقالات Hero وحركة العناصر المشتركة
  4. وحدات AnimationController المتدرجة والمنسقة
← العودة إلى Flutter Mobile Development