Shared Element Transition — ما هو، العناصر المشتركة في الانتقالات

المؤلف: IT Sectr نُشر: 2026-03-02 وقت القراءة: 10 دق

Shared Element Transition هي رسوم متحركة يتحرك ويتحول خلالها عنصر مشترك (صورة، نص، بطاقة) بسلاسة بين شاشتين أثناء التنقل. بدلاً من الانتقال المفاجئ، يرى المستخدم حركة مستمرة: العنصر من الشاشة A ينتقل إلى الشاشة B، محافظاً على السياق والاتصال البصري. وفقاً لـ Material Design Guidelines (2026)، يزيد Shared Element Transition من الأداء المدرك بنسبة 35% ويحسن فهم التنقل بنسبة 28%. تعرف على المزيد حول أنواع الرسوم المتحركة الأخرى في الدليل العام للرسوم المتحركة.

أهم النقاط

  • Shared Element Transition — رسوم متحركة لعنصر مشترك بين شاشتين أثناء التنقل.
  • sharedElementEnterTransition — سمة XML في Android لتكوين دخول العنصر المشترك.
  • UIViewPropertyAnimator — أساس الرسوم المتحركة المشتركة المخصصة بين UIViewControllers في iOS.
  • يتم تعريف العنصر المشترك عبر transitionName في Android و tag أو snapshot في iOS.
  • تعمل الرسوم المتحركة مع ImageView و TextView و CardView و Button وأي View بمعرف فريد.

ما هو Shared Element Transition؟

Shared Element Transition هي رسوم متحركة لانتقال الشاشة حيث يتحرك عنصر بصري واحد أو أكثر من الشاشة السابقة إلى التالية. في لحظة التنقل، يأخذ النظام لقطة للعنصر على الشاشة المصدر، ويحرك تحوله (الموضع، الحجم، الشكل، اللون) إلى الحالة المستهدفة على الشاشة الجديدة. ونتيجة لذلك، يدرك المستخدم الشاشتين كجزء من مساحة واحدة بدلاً من صفحات منفصلة.

حالات الاستخدام الرئيسية: بطاقة المنتج ← شاشة التفاصيل (تتكبر الصورة وتتحرك للأعلى)، معرض الصور ← العارض (تتمدد الصورة بسلاسة إلى ملء الشاشة)، قائمة جهات الاتصال ← الملف الشخصي (يتحرك الصورة الرمزية والاسم). لكي يعمل بشكل صحيح، يجب أن تحتوي كلتا الشاشتين على Views بنفس transitionName (Android) أو معرف اللقطة (iOS).

وفقاً لـ Google I/O 2025، يعد Shared Element Transition من بين أكثر ثلاثة أنماط UX فعالية لتطبيقات الجوال: حيث يقل احتمال عودة المستخدمين فوراً بعد الانتقال بنسبة 22% إذا شاهدوا رسوم العنصر المشترك. ومع ذلك، فإن الاستخدام المفرط (أكثر من 3 عناصر مشتركة لكل انتقال) يقلل من التأثير وقد يتسبب في انخفاض FPS إلى أقل من 40 على الأجهزة متوسطة المدى.

Shared Element Transition في Android (Kotlin)

في Android، يتم تنفيذ Shared Element Transition عبر FragmentTransaction.addSharedElement(view, transitionName) أو ActivityOptions.makeSceneTransitionAnimation. يتم تعيين android:transitionName فريد لكل View — نفس القيمة على كلتا الشاشتين. يقوم النظام تلقائياً بإنشاء رسوم متحركة للحركة (ChangeBounds)، وتغيير الحجم (ChangeTransform)، وتغيير حجم الصورة (ChangeImageTransform).

xml

<androidx.cardview.widget.CardView
    android:id="@+id/itemCard"
    android:transitionName="shared_element_@{item.id}"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <ImageView
        android:id="@+id/itemImage"
        android:transitionName="shared_image_@{item.id}" />
</androidx.cardview.widget.CardView>


<ImageView
    android:id="@+id/detailImage"
    android:transitionName="shared_image_@{item.id}"
    android:layout_width="match_parent"
    android:layout_height="300dp" />
kotlin
import androidx.core.app.ActivityOptionsCompat
import androidx.core.util.Pair
import androidx.fragment.app.FragmentTransaction

// Activity: Shared Element عبر ActivityOptions
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(
    this,
    Pair(imageView, getString(R.string.transition_name_image)),
    Pair(titleView, getString(R.string.transition_name_title))
)
startActivity(intent, options.toBundle())

// Fragment: Shared Element عبر FragmentTransaction
val detailFragment = DetailFragment().apply {
    sharedElementEnterTransition = TransitionInflater.from(context)
        .inflateTransition(android.R.transition.move)
    sharedElementReturnTransition = TransitionInflater.from(context)
        .inflateTransition(android.R.transition.move)
}

supportFragmentManager.beginTransaction()
    .addSharedElement(imageView, imageView.transitionName)
    .addSharedElement(titleView, titleView.transitionName)
    .replace(R.id.container, detailFragment)
    .addToBackStack(null)
    .commit()

// تخصيص الانتقال: ChangeBounds + ChangeImageTransform
detailFragment.sharedElementEnterTransition = TransitionSet().apply {
    ordering = TransitionSet.ORDERING_TOGETHER
    addTransition(ChangeBounds())
    addTransition(ChangeTransform())
    addTransition(ChangeImageTransform())
    duration = 400L
    interpolator = FastOutSlowInInterpolator()
}

تفاصيل مهمة في Android: للرسوم المتحركة الصحيحة لـ ImageView مع scaleType مختلف (centerCrop ← fitCenter)، استخدم ChangeImageTransform — فهو يحرك تلقائياً تغيير وضع العرض. للـ Views ذات الزوايا الدائرية (CardView)، أضف ChangeBounds الذي يحرك نصف قطر الزوايا عبر outline. لرسوم النص المتحركة (تغيير حجم الخط)، استخدم ChangeTextTransform. إذا اختفى عنصر مشترك أو ظهر على إحدى الشاشتين، يطبق النظام تلقائياً Fade.

Shared Element Transition في iOS (Swift)

لا يحتوي iOS على مكافئ مدمج لـ sharedElementEnterTransition في Android — يتم تنفيذ الرسوم المتحركة المشتركة يدوياً عبر UIViewControllerAnimatedTransitioning باستخدام UIViewPropertyAnimator أو CASpringAnimation. التقنية الرئيسية: في animateTransition(using:)، احصل على View من الشاشة المصدر، التقط لقطة، أضفها إلى containerView، أخفِ الأصل، وحرك اللقطة إلى الموضع المستهدف. في النهاية، أظهر View الهدف وأزل اللقطة.

swift
import UIKit

"> محرك مخصص لـ Shared Element
class SharedElementAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    let fromView: UIView
    let toView: UIView
    let fromFrame: CGRect
    let toFrame: CGRect
    let isPresenting: Bool

    func transitionDuration(using ctx: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.4
    }

    func animateTransition(using ctx: UIViewControllerContextTransitioning) {
        guard let toVC = ctx.viewController(forKey: .to),
              let fromVC = ctx.viewController(forKey: .from)
        else { return }

        let container = ctx.containerView

        if isPresenting {
            container.addSubview(toVC.view)
            toVC.view.layoutIfNeeded()
            toVC.view.alpha = 0
        }

        // لقطة العنصر المشترك
        let snapshot = fromView.snapshotView(afterScreenUpdates: false)!
        snapshot.frame = isPresenting ? fromFrame : toFrame
        snapshot.layer.cornerRadius = isPresenting ? 12 : 0
        container.addSubview(snapshot)

        fromView.isHidden = true
        toView.isHidden = true

        UIView.animate(withDuration: transitionDuration(using: ctx),
                       delay: 0,
                       usingSpringWithDamping: 0.7,
                       initialSpringVelocity: 0) {
            snapshot.frame = self.isPresenting ? self.toFrame : self.fromFrame
            snapshot.layer.cornerRadius = self.isPresenting ? 0 : 12
            toVC.view.alpha = 1
        } completion: { _ in
            fromView.isHidden = false
            toView.isHidden = false
            snapshot.removeFromSuperview()
            ctx.completeTransition(!ctx.transitionWasCancelled)
        }
    }
}

// Hero — مكتبة لتبسيط الانتقالات المشتركة
// heroID متطابق على كلتا الشاشتين، مماثل لـ transitionName في Android
import Hero

imageView.hero.id = "itemImage"
imageView.hero.modifiers = [.spring(stiffness: 300, damping: 20)]
titleLabel.hero.id = "itemTitle"

UIViewPropertyAnimator هي الطريقة المفضلة للرسوم المتحركة المشتركة على iOS 10+. بخلاف UIView.animate، يدعم PropertyAnimator التحكم التفاعلي (fractionComplete)، مما يسمح بدمج الانتقال المشترك مع إيماءة السحب للخلف. لمكتبة Hero (جهة خارجية)، يتم تعيين الانتقال المشترك عبر hero.id — وهو مماثل لـ transitionName من Android، الذي يحرك تلقائياً الموضع والحجم و cornerRadius و zPosition و backgroundColor بين الشاشات.

Shared Element في Jetpack Compose

في Jetpack Compose، يتم تنفيذ Shared Element Transition عبر معدل graphicsLayer مع رسوم SharedTransitionScope (Compose 1.7+). يتم تمييز كل عنصر مشترك باستخدام sharedElement() بمفتاح فريد. يقوم النظام تلقائياً بتحريك تغييرات الموضع والحجم والشكل واللون وقناع القص عند التنقل بين دوال composable. وفقاً لـ Android Developers Blog (2026)، يدعم Compose Shared Element Transition كلاً من AnimatedContent و NavHost والحاويات المخصصة.

kotlin
import androidx.compose.animation.core.spring
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.SharedTransitionLayout
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut

// SharedTransitionLayout — حاوية للعناصر المشتركة
SharedTransitionLayout {
    val sharedTransitionState = remember { SharedTransitionState() }

    // الشاشة A: قائمة
    LazyColumn {
        items(items) { item ->
            Card(
                modifier = Modifier.sharedElement(
                    state = sharedTransitionState,
                    key = SharedContentKey("image_${item.id}"),
                    boundsTransform = { _, _ ->
                        spring(dampingRatio = Spring.DampingRatioMediumBouncy)
                    }
                )
            ) {
                AsyncImage(model = item.imageUrl, contentDescription = null)
            }
        }
    }

    // الشاشة B: التفاصيل (تظهر عند اختيار عنصر)
    AnimatedContent(
        targetState = selectedItem,
        transitionSpec = {
            EnterTransition.None using SizeTransform(clip = false)
        }
    ) { item ->
        item?.let {
            Image(
                modifier = Modifier.sharedElement(
                    state = sharedTransitionState,
                    key = SharedContentKey("image_${it.id}")
                ),
                painter = rememberAsyncImagePainter(it.imageUrl),
                contentDescription = null,
                contentScale = ContentScale.FillWidth
            )
        }
    }
}

Compose 1.7+ SharedTransitionScope — واجهة برمجة تطبيقات جديدة متاحة في الوضع التجريبي. تحل محل الإدارة اليدوية للرسوم المتحركة المشتركة في NavHost. الميزات الرئيسية: boundsTransform لمنحنى رسوم متحركة مخصص، clipToBounds للزوايا الدائرية، zIndex لترتيب التراص. لتحريك الصور بـ contentScale مختلف، استخدم Modifier.sharedElement مع غلاف SharedContentKey. للإصدارات الأقدم من Compose (< 1.7)، استخدم مكتبة SharedTransitionScope من Google.

تحسين الأداء والمشكلات الشائعة

Shared Element Transition قد يسبب مشاكل في الأداء إذا لم تؤخذ قيود المنصة في الاعتبار. في Android، المشكلة الرئيسية هي ChangeImageTransform مع الصور الكبيرة (فوق 2048 بكسل). في iOS — اللقطات ذات الطبقات الثقيلة (CAGradientLayer، CAShapeLayer مع العديد من العقد). الحل: استخدم صوراً مضغوطة للرسوم المتحركة المشتركة والتقط اللقطة مع afterScreenUpdates: false.

المشكلةالسببالحل
الرسوم المتحركة متقطعةتخطيط مختلف قبل/بعد الانتقال / لقطة ثقيلةاستخدم Placeholder: أظهر معاينة صغيرة أثناء التحميل
العنصر المشترك لا يتحركtransitionName غير متطابق على كلتا الشاشتينتحقق من التطابق التام لـ transitionName في XML/الكود
FPS < 40 على Androidالعنصر المشترك يحتوي على تسلسل هرمي معقد للـ Viewشارك فقط ImageView، وليس CardView بالكامل
iOS: تشوهات اللقطةrenderInContext على طبقة بأقنعة/رسوم متحركةاستخدم drawHierarchy(in:afterScreenUpdates: true)
Compose: العنصر لا يظهرSharedTransitionLayout لا يحيط بكلتا الشاشتينيجب أن يكون SharedTransitionLayout هو الأصل لكلتيهما
kotlin
// تحسين: Placeholder للصورة في الانتقال المشترك
import androidx.core.widget.ImageViewCompat
import com.bumptech.glide.Glide

"> تحميل preview قبل التنقل
Glide.with(context)
    .load(imageUrl)
    .override(300, 300) // حد الحجم للانتقال المشترك
    .placeholder(R.drawable.placeholder)
    .into(imageView)

> تحميل مؤجل بدقة كاملة بعد الانتقال
val detailImage = findViewById<ImageView>(R.id.detailImage)
detailImage.doOnPreDraw {
    Glide.with(context).load(imageUrl).into(detailImage)
}

// iOS: تحسين اللقطة باستخدام drawHierarchy
// snapshotView(afterScreenUpdates: false) — أسرع، يتجاهل التحديثات المعلقة
// لـ Views المعقدة: drawHierarchy(in: view.bounds, afterScreenUpdates: true)

أفضل الممارسات: لا تضف أكثر من 3 عناصر مشتركة لكل انتقال — كل عنصر إضافي يزيد من حمل العرض. للقوائم (RecyclerView/UICollectionView)، اجعل العنصر المشترك هو العنصر الذي تم النقر عليه، وليس القائمة بأكملها. للبطاقات ذات الزوايا الدائرية، استخدم clipToOutline في Android أو cornerRadius في اللقطة في iOS. إذا كان العنصر المشترك على الشاشة الثانية داخل ScrollView، انتظر حتى اكتمال التخطيط (view.doOnPreDraw) قبل بدء الرسوم المتحركة.

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

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

يوصى باستخدام لا يزيد عن 3 عناصر مشتركة لكل انتقال. كل عنصر إضافي يزيد من حمل العرض وقد يتسبب في انخفاض FPS. الأمثل: صورة واحدة + نص واحد (عنوان). في Android، يضاف كل عنصر عبر addSharedElement(view, transitionName). في iOS، تضاف كل لقطة إلى containerView بشكل منفصل.

Shared Element Transition لا يعمل مع RecyclerView — ماذا أفعل؟

تأكد من تعيين transitionName ديناميكياً في onBindViewHolder: holder.imageView.transitionName = "image_${position}". لـ FragmentTransaction.addSharedElement، مرر View المحددة من holder. لـ ActivityOptions، استخدم Pair.create(holder.imageView, holder.imageView.transitionName). أخر الانتقال المشترك حتى اكتمال التخطيط عبر postponeEnterTransition() و startPostponedEnterTransition().

كيفية عمل Shared Element Transition مع تغيير لون الخلفية؟

في Android: أضف ChangeBackground إلى TransitionSet أو استخدم Transition مخصصاً يتجاوز captureStartValues و captureEndValues. في iOS: حرك backgroundColor للقطة أو استخدم UIViewPropertyAnimator مع تغيير اللون. في Jetpack Compose: Modifier.sharedElement يحرك الخلفية تلقائياً عبر انتقال Color. للبطاقات، حرك أيضاً الارتفاع — من 2dp في القائمة إلى 8dp على شاشة التفاصيل.

هل يعمل Shared Element على Android مع Jetpack Navigation؟

نعم، مع Navigation Component، يتم دعم Shared Element عبر NavOptions: val navOptions = NavOptions.Builder().setEnterAnim(R.transition.move).applyNavOptions(fragment). استخدم FragmentNavigatorExtras لتمرير العناصر المشتركة: FragmentNavigatorExtras(view to transitionName). لـ Navigation Compose، استخدم Modifier.sharedElement من SharedTransitionScope (Compose 1.7+).

كيفية تصحيح Shared Element Transition؟

في Android، فعّل Transition Debugging: adb shell setprop debug.transition 1 — ستظهر رسائل في logcat حول التقاط وتحريك العناصر المشتركة. فعّل "Show layout bounds" في Developer Options للتحقق من الحدود قبل/بعد. في iOS، استخدم Debug View Hierarchy للتحقق من اللقطة في containerView. لـ FPS، استخدم Profile GPU Rendering (Android) أو أداة Core Animation (iOS).

الملخص

  • Shared Element Transition — رسوم متحركة لعنصر مشترك بين الشاشات مع الحفاظ على السياق البصري.
  • Android: FragmentTransaction.addSharedElement أو ActivityOptions.makeSceneTransitionAnimation.
  • iOS: تنفيذ مخصص عبر UIViewControllerAnimatedTransitioning + لقطة + PropertyAnimator.
  • Jetpack Compose: Modifier.sharedElement من SharedTransitionScope (Compose 1.7+).
  • الحد الأقصى 3 عناصر مشتركة لكل انتقال لتجنب انخفاض FPS.
  • لـ ImageView بـ scaleType مختلف، استخدم ChangeImageTransform (Android) أو لقطة (iOS).
  • مكتبة Hero (iOS) — حل خارجي بـ hero.id، مماثل لـ transitionName في Android.

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

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

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

اقرأ أيضًا