Реализация Shared Element Transition в Android-приложении

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.

Разработка и поддержка любых видов мобильных приложений:

Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

Это лишь некоторые из типы мобильных приложений, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента.

Услуги, которые мы предлагаем
Показано 1 из 1Все 1735 услуг
Реализация Shared Element Transition в Android-приложении
Средний
от 1 дня до 3 дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    792
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    671
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1097
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    969
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    914
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    495

Реализация Shared Element Transition в Android-приложении

Shared Element Transition в Android — механизм, при котором View с одного экрана визуально «переезжает» на другой. Список новостей, карточка с изображением — пользователь нажимает, и изображение плавно расширяется до полноэкранного детального вида. Не исчезает и появляется, а перемещается с анимацией формы, размера и позиции.

Доступно через Fragment Transitions API (традиционный подход) и через Compose SharedTransitionLayout (современный).

Fragment Transitions: Activity и Fragment

Между Activity:

// Из ListActivity при клике на элемент:
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("productId", product.id)

val options = ActivityOptionsCompat.makeSceneTransitionAnimation(
    this,
    imageView,                    // view на текущем экране
    "product_image_transition"    // transitionName — совпадает на обоих экранах
)
startActivity(intent, options.toBundle())
// В DetailActivity:
ViewCompat.setTransitionName(detailImageView, "product_image_transition")
// postponeEnterTransition() + startPostponedEnterTransition() если изображение загружается асинхронно

Если изображение загружается через Glide или Coil — нужен postponeEnterTransition() до начала загрузки и startPostponedEnterTransition() в callback загрузки. Иначе анимация стартует до появления изображения, и transition работает с placeholder.

// В DetailActivity.onCreate():
postponeEnterTransition()

Glide.with(this)
    .load(imageUrl)
    .listener(object : RequestListener<Drawable> {
        override fun onResourceReady(...): Boolean {
            startPostponedEnterTransition()
            return false
        }
        override fun onLoadFailed(...): Boolean {
            startPostponedEnterTransition() // обязательно и при ошибке
            return false
        }
    })
    .into(detailImageView)

Между Fragment через Navigation Component:

// В ListFragment:
val extras = FragmentNavigatorExtras(
    imageView to "product_image_transition"
)
findNavController().navigate(
    R.id.action_list_to_detail,
    bundleOf("productId" to product.id),
    null,
    extras
)

// В DetailFragment.onCreate():
sharedElementEnterTransition = TransitionInflater.from(requireContext())
    .inflateTransition(android.R.transition.move)
postponeEnterTransition()

android.R.transition.move — стандартный transition, включает изменение bounds, translation и clip bounds. Для кастомного поведения — TransitionSet с ChangeBounds, ChangeImageTransform, ChangeClipBounds.

Возврат назад: Shared Element автоматически анимирует return transition при popBackStack() или кнопке назад. sharedElementReturnTransition можно настроить отдельно — например, другой easing для обратного пути.

Jetpack Compose: SharedTransitionLayout

Compose 1.7+ (stable) принёс SharedTransitionLayout и SharedTransitionScope:

SharedTransitionLayout {
    NavHost(navController, startDestination = "list") {
        composable("list") {
            AnimatedVisibility(visible = true) {
                ProductList(
                    onProductClick = { product ->
                        navController.navigate("detail/${product.id}")
                    },
                    sharedTransitionScope = this@SharedTransitionLayout,
                    animatedVisibilityScope = this@AnimatedVisibility
                )
            }
        }
        composable("detail/{id}") { backStackEntry ->
            AnimatedVisibility(visible = true) {
                ProductDetail(
                    productId = backStackEntry.arguments?.getString("id"),
                    sharedTransitionScope = this@SharedTransitionLayout,
                    animatedVisibilityScope = this@AnimatedVisibility
                )
            }
        }
    }
}

// В ProductList:
@Composable
fun ProductCard(
    product: Product,
    sharedTransitionScope: SharedTransitionScope,
    animatedVisibilityScope: AnimatedVisibilityScope,
) {
    with(sharedTransitionScope) {
        AsyncImage(
            model = product.imageUrl,
            modifier = Modifier.sharedElement(
                rememberSharedContentState(key = "product-image-${product.id}"),
                animatedVisibilityScope
            )
        )
    }
}

Ключ (key) должен совпадать на обоих экранах. sharedElement обрабатывает геометрический переход, sharedBounds — когда контейнер тоже должен плавно изменить размер.

Типичные ошибки

transitionName задан только на одном экране — transition не работает молча. Всегда проверяй оба направления.

RecyclerView с множеством элементов: setTransitionName нужно вызывать в onBindViewHolder с уникальным именем для каждого item (например, "product_image_${product.id}"). Если использовать одинаковое имя — Android не знает, какой именно view анимировать.

Shared Element поверх системной навигации (edge-to-edge): если view находится близко к краю и content за navigation bar — WindowInsetsCompat может смещать layout, и финальная позиция transition отличается от реальной. Решается через правильное применение insets через ViewCompat.setOnApplyWindowInsetsListener.

Сроки

Shared Element Transition между двумя экранами (изображение + заголовок): 1 день. С поддержкой асинхронной загрузки изображений, кастомным transition set и обратной анимацией: 1–2 дня. Стоимость рассчитывается индивидуально.