Shared Element Transition — какво е, общи елементи в преходите

Автор: IT Sectr Публикувано: 2026-03-02 Време за четене: 10 мин

Shared Element Transition — анимация, при която общ елемент (изображение, текст, карта) плавно се движи и трансформира между два екрана по време на навигация. Вместо рязък преход, потребителят вижда непрекъснато движение: елементът от екран А «преминава» към екран Б, запазвайки контекст и визуална връзка. Според Material Design Guidelines (2026), Shared Element Transition повишава възприеманата производителност с 35% и подобрява разбирането на навигацията с 28%. Прочетете повече за други видове анимация в общото ръководство за анимация.

Основни точки

  • Shared Element Transition — анимация на общ елемент между два екрана по време на навигация.
  • sharedElementEnterTransition — XML атрибут на Android за настройка на входа на общия елемент.
  • UIViewPropertyAnimator — основа на персонализирани shared-анимации между UIViewController в iOS.
  • Общият елемент се задава чрез transitionName в Android и tag или snapshot в iOS.
  • Анимацията работи с ImageView, TextView, CardView, Button и всякакви View с уникален идентификатор.

Какво е Shared Element Transition?

Shared Element Transition — е анимация за смяна на екрани, при която един или повече визуални елементи «се преместват» от предишния екран на следващия. В момента на навигацията системата прави snapshot на елемента на изходния екран, анимира неговата трансформация (позиция, размер, форма, цвят) до целевото състояние на новия екран. В резултат потребителят възприема двата екрана като части от единно пространство, а не като отделни страници.

Основни сценарии за използване: карта на продукт → детайлен екран (изображението се уголемява и отива отгоре), галерия с изображения → преглед (изображението плавно се отваря на цял екран), списък с контакти → профил (аватарът и името се преместват). За правилна работа и двата екрана трябва да имат View с еднакъв transitionName (Android) или идентификатор на snapshot (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. На всяко View се присвоява уникален android:transitionName — еднакъв и на двата екрана. Системата автоматично създава анимация за движение (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 — той автоматично анимира промяната на начина на показване. За View със заоблени ъгли (CardView) добавете ChangeBounds, който анимира радиуса на заобляне чрез outline. За анимация на текст (промяна на размера на шрифта) използвайте ChangeTextTransform. Ако споделеният елемент изчезва или се появява на един от екраните — системата автоматично прилага Fade.

Shared Element Transition в iOS (Swift)

В iOS няма вграден аналог на Android sharedElementEnterTransition — споделените анимации се имплементират ръчно чрез UIViewControllerAnimatedTransitioning с използване на UIViewPropertyAnimator или CASpringAnimation. Основна техника: в animateTransition(using:) вземете View от изходния екран, направете негов snapshot, добавете го в containerView, скрийте оригинала и анимирайте snapshot до целевата позиция. Накрая покажете целевото View и премахнете snapshot.

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
        }

        // Snapshot на общия елемент
        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 — библиотека за опростяване на shared transitions
// 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), което позволява интегриране на shared transition с жест swipe-back. За библиотеката Hero (трета страна) shared transition се задава чрез 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 — ново API, достъпно в експериментален режим. Заменя ръчното управление на споделени анимации в NavHost. Ключови възможности: boundsTransform за персонализирана крива на анимация, clipToBounds за заоблени ъгли, zIndex за ред на наслагване. За анимация на изображения с различен contentScale използвайте Modifier.sharedElement с обвивка SharedContentKey. За поддръжка на по-стари версии на Compose (< 1.7) използвайте библиотеката SharedTransitionScope от Google.

Оптимизация и типични проблеми

Shared Element Transition може да причини проблеми с производителността, ако не се вземат предвид ограниченията на платформата. В Android основният проблем е ChangeImageTransform с големи изображения (над 2048 px). В iOS — snapshot с тежки слоеве (CAGradientLayer, CAShapeLayer с много възли). Решение: използвайте компресирани изображения за споделени анимации и правете snapshot с afterScreenUpdates: false.

ПроблемПричинаРешение
Прекъсната анимацияРазличен layout преди и след прехода / тежък snapshotИзползвайте Placeholder: показвайте малък преглед при зареждане
Споделеният елемент не се анимираtransitionName не съвпада и на двата екранаПроверете точното съвпадение на transitionName в XML/код
FPS < 40 на AndroidСподеленият елемент съдържа сложна йерархия от ViewПравете shared само ImageView, не целия CardView
iOS: артефакти на snapshotrenderInContext върху слой с маски/анимацииИзползвайте drawHierarchy(in:afterScreenUpdates: true)
Compose: елементът не се появяваSharedTransitionLayout не обхваща и двата екранаSharedTransitionLayout трябва да бъде родител и за двата
kotlin
// Оптимизация: Placeholder за изображение в shared transition
import androidx.core.widget.ImageViewCompat
import com.bumptech.glide.Glide

"> Загрузка preview до навигации
Glide.with(context)
    .load(imageUrl)
    .override(300, 300) // Ограничение на размера за shared transition
    .placeholder(R.drawable.placeholder)
    .into(imageView)

> Отложенная загрузка full-resolution после перехода
val detailImage = findViewById<ImageView>(R.id.detailImage)
detailImage.doOnPreDraw {
    Glide.with(context).load(imageUrl).into(detailImage)
}

// iOS: оптимизация на snapshot с drawHierarchy
// snapshotView(afterScreenUpdates: false) — по-бързо, игнорира чакащи актуализации
// За сложни View: drawHierarchy(in: view.bounds, afterScreenUpdates: true)

Най-добри практики: не добавяйте повече от 3 споделени елемента на преход — всеки допълнителен елемент увеличава натоварването на рендирането. За списъци (RecyclerView/UICollectionView) правете споделения елемент върху кликнатия елемент, а не върху целия списък. За карти със заоблени ъгли използвайте clipToOutline в Android или cornerRadius в snapshot в iOS. Ако споделеният елемент на втория екран е в ScrollView, изчакайте завършването на layout (view.doOnPreDraw) преди да започнете анимацията.

Често задавани въпроси

Колко споделени елемента могат да се анимират в един преход?

Препоръчва се не повече от 3 споделени елемента на преход. Всеки допълнителен елемент увеличава натоварването на рендирането и може да доведе до спад на FPS. Оптимално: 1 изображение + 1 текст (заглавие). За Android всеки елемент се добавя чрез addSharedElement(view, transitionName). За iOS всеки snapshot се добавя в containerView отделно.

Shared Element Transition не работи с RecyclerView — какво да правя?

Уверете се, че transitionName е зададен динамично в onBindViewHolder: holder.imageView.transitionName = «image_${position}». За FragmentTransaction.addSharedElement предайте конкретния View от holder. За ActivityOptions използвайте Pair.create(holder.imageView, holder.imageView.transitionName). Отложете shared transition до завършване на layout чрез postponeEnterTransition() и startPostponedEnterTransition().

Как да направя Shared Element Transition с промяна на цвета на фона?

В Android: добавете ChangeBackground към TransitionSet или използвайте персонализиран Transition, който презаписва captureStartValues и captureEndValues. В iOS: анимирайте backgroundColor на snapshot или UIViewPropertyAnimator с промяна на цвета. В Jetpack Compose: Modifier.sharedElement автоматично анимира background чрез Color transition. За карти анимирайте също elevation — от 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 за проверка на snapshot в containerView. За мониторинг на FPS използвайте Profile GPU Rendering (Android) или Core Animation instrument (iOS).

Резюме

  • Shared Element Transition — анимация на общ елемент между екрани със запазване на визуален контекст.
  • Android: FragmentTransaction.addSharedElement или ActivityOptions.makeSceneTransitionAnimation.
  • iOS: персонализирана имплементация чрез UIViewControllerAnimatedTransitioning + snapshot + PropertyAnimator.
  • Jetpack Compose: Modifier.sharedElement от SharedTransitionScope (Compose 1.7+).
  • Максимум 3 споделени елемента на преход за избягване на спад на FPS.
  • За ImageView с различен scaleType използвайте ChangeImageTransform (Android) или snapshot (iOS).
  • Библиотека Hero (iOS) — решение на трета страна с hero.id, аналогично на transitionName в Android.

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също