تخصيص نقل العنصر المشترَك

لتخصيص طريقة تشغيل الرسوم المتحركة للانتقال بين العناصر المشترَكة، هناك بعض المَعلمات التي يمكن استخدامها لتغيير طريقة انتقال العناصر المشترَكة.

مواصفات الصورة المتحركة

لتغيير مواصفات الحركة المستخدَمة في تغيير الحجم والموضع، يمكنك تحديد مَعلمة boundsTransform مختلفة في Modifier.sharedElement(). يوفّر ذلك موضع Rect الأوّلي وموضع Rect المستهدف.

على سبيل المثال، لجعل النص في المثال السابق يتحرك بحركة قوسية، حدِّد المَعلمة boundsTransform لاستخدام مواصفات keyframes:

val textBoundsTransform = BoundsTransform { initialBounds, targetBounds ->
    keyframes {
        durationMillis = boundsAnimationDurationMillis
        initialBounds at 0 using ArcMode.ArcBelow using FastOutSlowInEasing
        targetBounds at boundsAnimationDurationMillis
    }
}
Text(
    "Cupcake", fontSize = 28.sp,
    modifier = Modifier.sharedBounds(
        rememberSharedContentState(key = "title"),
        animatedVisibilityScope = animatedVisibilityScope,
        boundsTransform = textBoundsTransform
    )
)

يمكنك استخدام أي AnimationSpec. يستخدم هذا المثال مواصفات keyframes.

الشكل 1. مثال يعرض مَعلمات boundsTransform مختلفة

وضع تغيير الحجم

عند تحريك العناصر بين حدود مشترَكة، يمكنك ضبط المَعلمة resizeMode على RemeasureToBounds أو ScaleToBounds. تحدّد هذه المَعلمة كيفية انتقال العنصر المشترَك بين الحالتَين. تعمل السمة ScaleToBounds first على قياس تخطيط العنصر التابع باستخدام قيود البحث المسبق (أو القيود المستهدَفة). بعد ذلك، يتم تغيير حجم التنسيق الثابت الخاص بالنافذة الفرعية ليناسب الحدود المشتركة. يمكن اعتبار ScaleToBounds "مقياسًا بيانيًا" بين الحالات.

في المقابل، تعيد RemeasureToBounds قياس وتنسيق التخطيط الفرعي الخاص بـ sharedBounds باستخدام قيود ثابتة متحركة استنادًا إلى الحجم المستهدف. يتم تشغيل إعادة القياس عند تغيير حجم الحدود، وهو ما قد يحدث في كل لقطة.

بالنسبة إلى عناصر Text القابلة للإنشاء، يُنصح باستخدام ScaleToBounds لأنّه يتجنّب إعادة التخطيط وإعادة تدفّق النص على أسطر مختلفة. ننصح باستخدام RemeasureToBounds للحدود التي تختلف فيها نسبة العرض إلى الارتفاع، وفي حال أردت الحصول على انتقال سلس بين العنصرَين المشترَكَين.

يمكن ملاحظة الفرق بين وضعَي تغيير الحجم في الأمثلة التالية:

ScaleToBounds

RemeasureToBounds

الانتقال إلى التنسيق النهائي

عند الانتقال بين تخطيطَين، يتم تلقائيًا تحريك حجم التخطيط بين حالتَي البداية والنهاية. وقد يكون هذا السلوك غير مرغوب فيه عند تحريك محتوى، مثل النص.

يوضّح المثال التالي نص الوصف "Lorem Ipsum" الذي يظهر على الشاشة بطريقتين مختلفتين. في المثال الأول، يتم إعادة تدفّق النص عند إدخاله مع زيادة حجم الحاوية. في المثال الثاني، لا يتم إعادة ترتيب النص عند توسيعه. تؤدي إضافة Modifier.skipToLookaheadSize() إلى منع إعادة التدفق أثناء زيادة حجمه.

لا Modifier.skipToLookahead() - لاحظ إعادة تدفق نص "Lorem Ipsum"

Modifier.skipToLookahead() - لاحظ أنّ نص "Lorem Ipsum" يحتفظ بحالته النهائية في بداية الصورة المتحركة

المقاطع والصور المركّبة

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

سيتم عرضه فوق عناصر واجهة المستخدم الأخرى غير المشترَكة. بعد انتهاء عملية النقل، سيتم نقل العنصر من الطبقة المتراكبة إلى DrawScope خاص به.

لقص عنصر مشترك إلى شكل، استخدِم الدالة العادية Modifier.clip(). ضَعها بعد sharedElement():

Image(
    painter = painterResource(id = R.drawable.cupcake),
    contentDescription = "Cupcake",
    modifier = Modifier
        .size(100.dp)
        .sharedElement(
            rememberSharedContentState(key = "image"),
            animatedVisibilityScope = this@AnimatedContent
        )
        .clip(RoundedCornerShape(16.dp)),
    contentScale = ContentScale.Crop
)

إذا كنت بحاجة إلى التأكّد من أنّ عنصرًا مشتركًا لا يتم عرضه مطلقًا خارج حاوية رئيسية، يمكنك ضبط clipInOverlayDuringTransition على sharedElement(). بشكل تلقائي، بالنسبة إلى الحدود المشتركة المتداخلة، تستخدم clipInOverlayDuringTransition مسار القطع من العنصر الأصل sharedBounds().

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

على سبيل المثال، في Jetsnack، يجب وضع BottomAppBar أعلى العنصر المشترَك إلى أن تصبح الشاشة غير مرئية. تؤدي إضافة المعدِّل إلى العنصر القابل للإنشاء إلى إبقائه مرتفعًا.

بدون Modifier.renderInSharedTransitionScopeOverlay()

مع Modifier.renderInSharedTransitionScopeOverlay()

قد تريد أن يتم تحريك العنصر القابل للإنشاء غير المشترَك بعيدًا بالإضافة إلى أن يظل في أعلى العناصر القابلة للإنشاء الأخرى قبل الانتقال. في مثل هذه الحالات، استخدِم renderInSharedTransitionScopeOverlay().animateEnterExit() لتحريك العنصر القابل للإنشاء أثناء تنفيذ عملية الانتقال بين العناصر المشترَكة:

JetsnackBottomBar(
    modifier = Modifier
        .renderInSharedTransitionScopeOverlay(
            zIndexInOverlay = 1f,
        )
        .animateEnterExit(
            enter = fadeIn() + slideInVertically {
                it
            },
            exit = fadeOut() + slideOutVertically {
                it
            }
        )
)

الشكل 2. شريط التطبيق السفلي ينزلق للداخل والخارج أثناء انتقال الصورة المتحركة.

في الحالات النادرة التي تريد فيها عدم عرض العنصر المشترَك في تراكب، يمكنك ضبط renderInOverlayDuringTransition على sharedElement() على القيمة false.

إشعار التنسيقات المتجاورة بالتغييرات التي تم إجراؤها على حجم العنصر المشترَك

بشكلٍ تلقائي، لا يرسل العنصران sharedBounds() وsharedElement() إشعارًا إلى الحاوية الرئيسية بشأن أي تغييرات في الحجم أثناء انتقال التنسيق.

من أجل نقل تغييرات الحجم إلى الحاوية الرئيسية أثناء انتقالها، غيِّر المَعلمة placeHolderSize إلى PlaceHolderSize.animatedSize. سيؤدي ذلك إلى تكبير العنصر أو تصغيره. وتستجيب جميع العناصر الأخرى في التنسيق لهذا التغيير.

PlaceholderSize.contentSize (تلقائي)

PlaceholderSize.animatedSize

(لاحظ كيف تتحرك العناصر الأخرى في القائمة إلى الأسفل استجابةً للعنصر الذي يزداد حجمه)