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 shared елемента по прелазу) смањује ефекат и може изазвати пад 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. Ако shared елемент нестаје или се појављује на једном од екрана — систем аутоматски примењује Fade.

Shared Element Transition у iOS (Swift)

На iOS-у не постоји уграђени аналог Android-овог sharedElementEnterTransition — shared-анимације се имплементирају ручно кроз 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 — пожељан начин за shared-анимације на 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+). Сваки shared елемент се означава кроз 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 — контејнер за shared елементе
SharedTransitionLayout {
    val sharedTransitionState = remember { SharedTransitionState() }

    // Екран А: листа
    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)
            }
        }
    }

    // Екран Б: детаљи (приказује се при избору елемента)
    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 доступан у експерименталном режиму. Замењује ручно управљање shared-анимацијама у NavHost-у. Кључне могућности: boundsTransform за прилагођену криву анимације, clipToBounds за заобљене углове, zIndex за редослед слојева. За анимацију слика са различитим contentScale користите Modifier.sharedElement са wrapper-ом SharedContentKey. За подршку старијим верзијама Compose-а (< 1.7) користите библиотеку SharedTransitionScope од Google-а.

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

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

ПроблемУзрокРешење
Трзање анимацијеРазличит layout пре и после прелаза / тежак snapshotКористите Placeholder: прикажите мали преглед током учитавања
Shared елемент се не анимираtransitionName се не поклапа на оба екранаПроверите тачно поклапање transitionName у XML/коду
FPS < 40 на Android-уShared елемент садржи сложену хијерархију 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 shared елемента по прелазу — сваки додатни елемент повећава оптерећење рендеровања. За листе (RecyclerView/UICollectionView) радите shared елемент на кликнути item, а не на целу листу. За картице са заобљеним угловима користите clipToOutline на Android-у или cornerRadius у snapshot-у на iOS-у. Ако се shared елемент на другом екрану налази у ScrollView-у, сачекајте завршетак layout-а (view.doOnPreDraw) пре почетка анимације.

Често постављана питања

Колико shared елемената се може анимирати у једном прелазу?

Препоручује се не више од 3 shared елемента по прелазу. Сваки додатни елемент повећава оптерећење рендеровања и може изазвати пад 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 за пренос shared елемената: 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-у ће се појавити поруке о хватању и анимацији shared елемената. Укључите «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 shared елемента по прелазу да би се избегао пад FPS-а.
  • За ImageView са различитим scaleType користите ChangeImageTransform (Android) или snapshot (iOS).
  • Библиотека Hero (iOS) — решење треће стране са hero.id, аналогно transitionName у Android-у.

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође