Hero Animation: ما هي حركة العناصر المشتركة في Flutter

المؤلف: IT Sectr نُشر: 2026-03-02 وقت القراءة: 8 دق
Hero Animation هو نمط رسوم متحركة يتحرك فيه عنصر واجهة المستخدم بسلاسة ويتحول بين الشاشات، مما يخلق تأثير «bطل» يسافر من سياق إلى آخر. على عكس الانتقال القياسي push/pop، تحافظ الرسوم المتحركة البطل على الاستمرارية البصرية: يرى المستخدم صورة مصغرة تتحول إلى صورة ملء الشاشة أو عنوان خلية ينتقل إلى عنوان الشاشة. وفقًا لـ Google Material Design (2025)، يقلل Hero Animation من الحمل المعرفي بنسبة 30% عند التنقل بين القائمة والتفاصيل — لا يفقد المستخدم التركيز بفضل تذكر موضع العنصر. في Flutter يتم تنفيذ النمط من خلال عنصر واجهة Hero، في SwiftUI — من خلال معدل matchedGeometryEffect، في Jetpack Compose — من خلال Modifier.sharedElement.

الخلاصة

  • Hero Animation — انتقال سلس لعنصر مشترك بين الشاشات يحافظ على السياق البصري للمستخدم.
  • Flutter Hero — عنصر واجهة يجد تلقائيًا زوجًا بالعلامة ويطلق رسمًا متحركًا FlightShuttle بين الشاشات.
  • matchedGeometryEffect — معدل SwiftUI يربط العناصر من خلال معرف في namespace للانتقالات المتحركة.
  • Shared Element Transition — آلية Android يتم تنفيذها من خلال ActivityOptions أو Modifier.sharedElement في Jetpack Compose.
  • FlightShuttle — الآلية الداخلية لـ Flutter التي تتحكم في مسار وتحويل عنصر Hero أثناء الانتقال.

ما هي Hero Animation؟

Hero Animation هي تقنية رسوم متحركة للواجهة يتحرك فيها عنصر مشترك بسلاسة من شاشة المصدر إلى شاشة الوجهة، محافظًا على هويته. يعكس الاسم استعارة «bطل» — عنصر رئيسي يمر بانتقال نصي بين الشاشات، مما يجذب انتباه المستخدم. على عكس الانتقالات القياسية، لا يكسر Hero Animation الاتصال البصري: يرى المستخدم مسارًا مستمرًا للعنصر، مما يحسن فهم التسلسل الهرمي للتنقل.

تم إرساء مفهوم Hero Animation في Material Design كتوصية لشاشات القائمة والتفاصيل والمعارض والبطاقات. تتكون خوارزمية الرسوم المتحركة من ثلاث مراحل: في شاشة المصدر، يتم تمييز العنصر وتثبيته في موضع مطلق؛ أثناء الانتقال، يتم تغيير حجمه وتحريكه وتغيير شكله وفقًا لحاوية الوجهة؛ في الشاشة النهائية، يتم دمج العنصر في التخطيط الجديد. وفقًا Material Design 3، يقلل تنفيذ الحركة البطل بشكل صحيح من عدد الضغطات الخاطئة على زر «الرجوع» بنسبة 22%.

Hero في Flutter: العلامات و FlightShuttle

في Flutter، يتم تنفيذ Hero Animation من خلال عنصر Hero، الذي يأخذ معلمة إلزامية tag — معرف فريد. يجد Flutter تلقائيًا زوجًا من عناصر Hero بنفس العلامة في مسارات المصدر والوجهة ويبدأ الرسم المتحرك. لا يتطلب عنصر Hero إدارة يدوية للرسوم المتحركة — يحسب الإطار المسار بشكل مستقل ويطبق التحويل من خلال الكائن الداخلي FlightShuttle.

FlightShuttle هو «مكوك» غير مرئي يقوم، في لحظة الانتقال، بإنشاء نسخة تراكب من عنصر Hero، ويضعها في نظام إحداثيات مطلق، ويحرك التحويل (قياس، ترجمة، تدوير) من الموضع الأولي إلى الموضع النهائي. يمكن تمرير منحنيات مخصصة إلى FlightShuttle من خلال خاصية flightShuttleBuilder، مما يسمح بالتحكم في مسار الطيران: الطيران في قوس، أو الارتداد عن الحدود، أو التباطؤ قبل الوصول. في Flutter 3.16+، يدعم Hero الرسوم المتحركة مع الحفاظ على clipRect و borderRadius، مما يعطي تأثير البطاقات المستديرة التي تتحول إلى شاشات مستطيلة.

matchedGeometryEffect في SwiftUI

في SwiftUI، ما يعادل Hero Animation هو المعدل matchedGeometryEffect، الذي تم تقديمه في iOS 14. يربط عنصرين من خلال معرف (id) ومساحة أسماء (namespace). على عكس Flutter، حيث يعترض Hero الانتقال تلقائيًا، يتطلب SwiftUI إعلانًا صريحًا عن namespace وتطبيق المعدل على جزئي المشهد — المصدر والوجهة — داخل نفس التسلسل الهرمي أو من خلال GeometryReader.

يدعم رسم matchedGeometryEffect المتحرك تحويل الموضع والحجم والدوران ونصف قطر الزاوية. لتحقيق السلاسة، يتم تحديد Animation في withAnimation. يقوم SwiftUI تلقائيًا باستيفاء الحالات الوسيطة بناءً على المنحنى الافتراضي easeInOut. في iOS 17+، تمت إضافة دعم matchedTransitionSource لـ TabView و NavigationStack — وهو نظير نظامي لـ Hero Animation بمسار محدد مسبقًا للانتقالات القياسية.

Shared Element Transition في Android

في Android، يُسمى Hero Animation Shared Element Transition ويتم تنفيذه من خلال ActivityOptions.makeSceneTransitionAnimation أو FragmentTransaction.addSharedElement. يتم تمييز كل عنصر مشترك بسمة transitionName في XML أو من خلال الكود، ويتولى Android التعامل مع الرسوم المتحركة بين الأنشطة. بدءًا من Android 5.0 (API 21)، أصبحت الانتقالات جزءًا من الإطار؛ قبل ذلك، كان لا بد من كتابة رسوم متحركة مخصصة باستخدام ValueAnimator.

في Jetpack Compose، يتم تنفيذ Shared Element Transition من خلال معدّلي Modifier.sharedElement و Modifier.sharedBounds بالاقتران مع AnimatedContent أو SharedTransitionLayout (متاح منذ Compose BOM 2024.01). على عكس نظام View، يعيد Compose رسم العناصر بتكاسل، لذلك يتم تخزين العنصر المشترك مؤقتًا في CompositionLocal. يدعم Compose أيضًا رسمًا متحركًا من القائمة إلى التفاصيل مع الحفاظ على scrollOffset — وهي إمكانية رئيسية لانتقالات Hero. وفقًا لـ Google I/O 2024، فإن Compose Shared Element Transition أسرع بنسبة 15% من إصدار View بسبب الإدارة المباشرة لـ RenderNode.

أمثلة الكود

Flutter: Hero مع صورة

مثال أساسي لـ Hero Animation في Flutter: صورة مصغرة على شاشة القائمة وصورة بملء الشاشة على شاشة التفاصيل. العلامة image-hero تربط بين العنصرين، ويقوم FlightShuttle تلقائيًا بتحريك الانتقال من البطاقة إلى ملء الشاشة.

dart
import 'package:flutter/material.dart';

class ListScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ListView(
        children: [
          Hero(
            tag: 'image-hero',
            child: GestureDetector(
              onTap: () {
                Navigator.push(context,
                  MaterialPageRoute(
                    builder: (_) => DetailScreen(),
                  ),
                );
              },
              child: ClipRRect(
                borderRadius: BorderRadius.circular(12),
                child: Image.network(
                  'https://picsum.photos/200',
                  width: 100, height: 100,
                  fit: BoxFit.cover,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class DetailScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Detail')),
      body: Center(
        child: Hero(
          tag: 'image-hero',
          child: Image.network(
            'https://picsum.photos/800',
            width: 300, height: 400,
            fit: BoxFit.cover,
          ),
        ),
      ),
    );
  }
}

النقطة الأساسية هي نفس العلامة (image-hero) على كلتا الشاشتين. يطابق Flutter عناصر Hero بالعلامة أثناء انتقال المسار. إذا اختلفت العلامات، لن يبدأ الرسم المتحرك — سيظهر العنصر ببساطة على الشاشة الجديدة دون انتقال. يمكن استخدام أي نوع للعلامة، لكن السلاسل النصية هي الخيار الأكثر موثوقية.

SwiftUI: matchedGeometryEffect بين الشاشات

مثال لـ Hero Animation في SwiftUI مع NavigationStack. يربط معدل matchedGeometryEffect الصورة في القائمة وشاشة التفاصيل من خلال namespace، ويقوم withAnimation بتشغيل انتقال سلس.

swift
import SwiftUI

struct HeroExampleView: View {
    @Namespace private var heroNamespace
    @State private var isDetailShown = false

    var body: some View {
        VStack {
            if isDetailShown {
                DetailView(namespace: heroNamespace)
                    .transition(.identity)
            } else {
                ListView(namespace: heroNamespace)
                    .transition(.identity)
            }
        }
        .animation(Animation.easeInOut(duration: 0.4),
                   value: isDetailShown)
    }
}

struct ListView: View {
    let namespace: Namespace.ID

    var body: some View {
        Image("thumbnail")
            .resizable()
            .frame(width: 100, height: 100)
            .cornerRadius(12)
            .matchedGeometryEffect(id: "hero", in: namespace)
    }
}

struct DetailView: View {
    let namespace: Namespace.ID

    var body: some View {
        Image("thumbnail")
            .resizable()
            .frame(width: 300, height: 400)
            .cornerRadius(0)
            .matchedGeometryEffect(id: "hero", in: namespace)
    }
}

@Namespace ينشئ مساحة أسماء لمعرف matchedGeometryEffect. يستخدم كلا العنصرين نفس المعرف («hero») ومساحة أسماء مشتركة. عند تبديل isDetailShown، يقوم SwiftUI بتحويل الحجم والموضع ونصف قطر الزاوية بين العرضين، مما يخلق تأثير البطل. لـ NavigationLink القياسي مع iOS 16+، استخدم navigationTransition(.hero).

Android: Shared Element Transition مع Jetpack Compose

مثال لـ Shared Element Transition في Jetpack Compose مع SharedTransitionLayout. يقوم معدل sharedElement بتحريك عنصر مشترك بين الشاشات أثناء التنقل.

kotlin
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun HeroExample() {
    SharedTransitionLayout {
        var isDetail by remember { mutableStateOf(false) }

        AnimatedContent(
            targetState = isDetail,
            transitionSpec = {
                fadeIn() togetherWith fadeOut()
            }
        ) { detail ->
            if (detail) {
                Box(
                    modifier = Modifier
                        .fillMaxSize()
                        .sharedElement(
                            state = rememberSharedContentState(key = "hero"),
                            animatedVisibilityScope = this
                        )
                ) {
                    AsyncImage(
                        model = "https://picsum.photos/800",
                        contentDescription = null,
                        modifier = Modifier.fillMaxSize()
                    )
                }
            } else {
                Box(
                    modifier = Modifier
                        .size(100.dp)
                        .sharedElement(
                            state = rememberSharedContentState(key = "hero"),
                            animatedVisibilityScope = this
                        )
                ) {
                    AsyncImage(
                        model = "https://picsum.photos/200",
                        contentDescription = null,
                        modifier = Modifier.fillMaxSize()
                    )
                }
            }
        }
    }
}

يقوم Compose SharedTransitionLayout (Compose BOM 2024.01+) بتغليف التسلسل الهرمي، ويقوم معدل sharedElement بالمفتاح «hero» بربط العناصر الأولية والنهائية. يقوم AnimatedContent بتبديل الحالة، ويقوم Compose تلقائيًا بتحريك حجم الصورة وموضعها وشكلها. الميزة الرئيسية هي دعم الرسوم المتحركة للحدود و clipBounds، مما يعطي تأثير تدوير البطاقة أثناء الانتقال.

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

هل تعمل Hero Animation على iOS و Android؟

نعم، Hero Animation متاحة على كلا المنصتين. يستخدم iOS matchedGeometryEffect في SwiftUI أو UINavigationController.transitioningDelegate. يستخدم Android ActivityOptions.makeSceneTransitionAnimation أو Shared Element Transition في Jetpack Compose مع Modifier.sharedElement. ينفذ Flutter Hero Animation من خلال عنصر Hero، الذي يعمل بشكل متطابق على كلا المنصتين.

هل يمكن تحريك عدة عناصر في وقت واحد؟

نعم، يدعم Hero Animation عناصر مشتركة متعددة. في Flutter، حدد عدة عناصر Hero بعلامات مختلفة. في SwiftUI، استخدم matchedGeometryEffect بمعرفات مختلفة على كل عنصر. يتم تنفيذ جميع الرسوم المتحركة بالتوازي — كل عنصر يسافر على مساره الخاص.

هل تبطؤ Hero Animation على الصور الكبيرة؟

يعتمد الأداء على التنفيذ. يعالج FlightShuttle في Flutter الصور من خلال RenderObject، محققًا 60 إطارًا في الثانية. matchedGeometryEffect في SwiftUI متجه. للصور الكبيرة، استخدم precacheImage في Flutter أو prefetch في SwiftUI — هذا يزيل التأخير في أول انتقال. يتطلب Android Compose تحميلًا مؤجلًا من خلال AsyncImage.

كيف يختلف Hero عن الانتقال القياسي push؟

الانتقال القياسي push يزيح الشاشة بأكملها، مما يخلق وهم كومة من البطاقات. Hero Animation يثبت الانتباه البصري على عنصر واحد — يرى المستخدم حركته المستمرة، مما يحسن فهم العلاقة بين الشاشات. Push جيد للتنقل الهرمي، Hero لتسليط الضوء على المحتوى.

هل هناك حاجة لمكتبات إضافية لـ Hero Animation؟

لا، يتم تنفيذ Hero Animation بأدوات مدمجة. في Flutter، هو عنصر Hero من حزمة flutter/material.dart. في SwiftUI، هو معدل matchedGeometryEffect. في Android Jetpack Compose، هو SharedTransitionLayout و Modifier.sharedElement من Compose القياسي. المكتبات الإضافية مطلوبة فقط للمسارات غير القياسية أو التحويلات المعقدة.

الملخص

  • Hero Animation هي انتقال عنصر مشترك بين الشاشات يحافظ على الاستمرارية البصرية ويقلل العبء المعرفي.
  • Flutter Hero مع العلامة و FlightShuttle هي أبسط طريقة لتطبيق الحركة البطل دون إدارة يدوية للرسوم المتحركة.
  • SwiftUI matchedGeometryEffect مع @Namespace.id يحرك الموضع والحجم ونصف قطر الزاوية لعنصر بين حالتين.
  • Android SharedTransitionLayout مع Modifier.sharedElement في Jetpack Compose يوفر انتقالات مع تخزين RenderNode مؤقت.
  • الأطر الثلاثة جميعها تدعم عناصر مشتركة متعددة مع رسوم متحركة متوازية.
  • FlightShuttle يسمح بتخصيص مسار الطيران من خلال flightShuttleBuilder في Flutter.
  • Hero Animation موصى به من قبل Material Design لشاشات القائمة والتفاصيل والمعارض والبطاقات.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا