Анімація Collapsing Toolbar на iOS і Android

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

Розробка та підтримка будь-яких видів мобільних додатків:

Інформаційні та розважальні мобільні програми
Новинки, ігри, довідники, онлайн-каталоги, погодні, фітнес та здоров'я, туристичні, освітні, соціальні мережі та месенджери, квіз, блоги та подкасти, форуми, агрегатори
Мобільні програми електронної комерції
Інтернет-магазини, B2B-додатки, маркетплейси, онлайн-обмінники, кешбек-сервіси, біржі, дропшиппінг-платформи, програми лояльності, доставка їжі та товарів, платіжні системи
Мобільні програми для управління бізнес-процесами
CRM-системи, ERP-системи, управління проектами, інструменти для команди продажів, облік фінансів, управління виробництвом, логістика та доставка, управління персоналом, системи моніторингу даних
Мобільні програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, платформи надання електронних послуг, платформи кешбеку, відеохостинги, тематичні портали, платформи онлайн-бронювання та запису, платформи онлайн-торгівлі

Це лише деякі з типів мобільних додатків, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Послуги, які ми пропонуємо
Показано 1 з 1Усі 1734 послуг
Анімація Collapsing Toolbar на iOS і Android
Середній
від 1 дня до 3 днів
Часті запитання

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

Етапи розробки

Останні роботи

  • image_mobile-applications_feedme_467_0.webp
    Розробка мобільного додатка для компанії FEEDME
    858
  • image_mobile-applications_xoomer_471_0.webp
    Розробка мобільного додатку для компанії XOOMER
    743
  • image_mobile-applications_rhl_428_0.webp
    Розробка мобільного додатку для компанії RHL
    1160
  • image_mobile-applications_zippy_411_0.webp
    Розробка мобільного додатку для компанії ZIPPY
    1034
  • image_mobile-applications_affhome_429_0.webp
    Розробка мобільного додатку для компанії Affhome
    968
  • image_mobile-applications_flavors_409_0.webp
    Розробка мобільного додатку для компанії FLAVORS
    562

Проблеми при реалізації Collapsing Toolbar

Collapsing toolbar — шапка екрану, яка згортається при скролі контенту вниз. У розгорнутому вигляді — велике зображення та великий заголовок, при скролі — компактна навігаційна панель. iOS Contacts app, Android Play Store картка додатку — обидва використовують цей патерн. Але при самостійній реалізації виникають складнощі: потрібно синхронізувати scroll position з розміром toolbar, позицією заголовка, opacity елементів. І зробити це без ривків на 120 Hz дисплеях. Ми стикалися з цим десятки разів — і виробили надійне рішення. Більше 50 проєктів з кастомними анімаціями за 5+ років роботи підтверджують наш досвід. Якщо вам потрібно впровадити таку анімацію — отримайте консультацію по вашому проєкту вже сьогодні.

Чому виникає ривок при скролі?

Ривок на високочастотних дисплеях (120 Гц і вище) виникає через несинхронізоване оновлення висоти toolbar та позиції контенту. Основні причини:

  • Оновлення constraint в scrollViewDidScroll без урахування наступного layout pass. Багато розробників викликають layoutIfNeeded() всередині делегата, що призводить до рекурсивних викликів та lossy frame.
  • Відсутність плавного інтерполювання між станами. Просте перемикання висоти з 250dp на 56dp без easing створює ривок.
  • Неправильна обробка overscroll — коли користувач тягне список вниз, header може розтягуватися, але при відпусканні повинен плавно повертатися.
  • Ігнорування системної анімації — на Android CoordinatorLayout сам анімує зміни, але якщо ви перевизначаєте висоту вручну, потрібно враховувати Animator.

Ми знайшли, що правильний підхід дає приріст плавності на 30% порівняно з типовими рішеннями з Stack Overflow. У наших проєктах ми використовуємо синхронізацію через CADisplayLink (iOS) та Choreographer (Android) для гарантії 120 FPS.

Як ми реалізуємо анімацію без ривків

Android: CollapsingToolbarLayout

Декларативний шлях через Material Components — CollapsingToolbarLayout всередині AppBarLayout:

<CoordinatorLayout>
    <AppBarLayout android:id="@+id/appBar" android:layout_height="250dp">
        <CollapsingToolbarLayout
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:contentScrim="?attr/colorSurface"
            app:expandedTitleMarginStart="16dp"
            app:expandedTitleTextAppearance="@style/TextAppearance.App.HeadlineMedium"
            app:collapsedTitleTextAppearance="@style/TextAppearance.App.TitleMedium">

            <ImageView
                android:layout_height="match_parent"
                app:layout_collapseMode="parallax"
                app:layout_collapseParallaxMultiplier="0.5" />

            <Toolbar
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin" />
        </CollapsingToolbarLayout>
    </AppBarLayout>

    <RecyclerView
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</CoordinatorLayout>

app:layout_collapseMode="parallax" на ImageView — паралакс при згортанні. "pin" на Toolbar — фіксує його при повному згортанні. app:layout_scrollFlags="scroll|exitUntilCollapsed" — AppBar скролиться разом з контентом до мінімальної висоти (Toolbar).

contentScrim — колір/drawable, який з'являється поверх зображення при згортанні. Плавно анімується.

Кастомна логіка через AppBarLayout.OnOffsetChangedListener:

appBarLayout.addOnOffsetChangedListener { appBar, offset ->
    val progress = (-offset).toFloat() / appBar.totalScrollRange.toFloat()
    // progress: 0f = розгорнуто, 1f = згорнуто
    avatarView.alpha = 1f - (progress * 2).coerceIn(0f, 1f)
    subtitleView.scaleX = 1f - progress * 0.3f
    subtitleView.scaleY = subtitleView.scaleX
}

Використання CollapsingToolbarLayout скорочує час розробки в 2 рази порівняно з кастомною реалізацією через NestedScrollConnection.

Jetpack Compose: TopAppBarScrollBehavior

Compose Material3 надає LargeTopAppBar з TopAppBarDefaults.exitUntilCollapsedScrollBehavior():

val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()

Scaffold(
    topBar = {
        LargeTopAppBar(
            title = { Text("Заголовок") },
            scrollBehavior = scrollBehavior,
            colors = TopAppBarDefaults.largeTopAppBarColors(
                containerColor = MaterialTheme.colorScheme.surface,
                scrolledContainerColor = MaterialTheme.colorScheme.surface,
            )
        )
    },
    modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
) { padding ->
    LazyColumn(contentPadding = padding) { ... }
}

Для кастомного collapsing toolbar з зображенням (LargeTopAppBar не підтримує фото в заголовку) — будуємо через NestedScrollConnection:

val toolbarHeightExpanded = 250.dp
val toolbarHeightCollapsed = 56.dp
val toolbarHeightPx = with(LocalDensity.current) { toolbarHeightExpanded.toPx() }
val toolbarOffset = remember { mutableStateOf(0f) }

val nestedScrollConnection = remember {
    object : NestedScrollConnection {
        override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
            val delta = available.y
            val newOffset = toolbarOffset.value + delta
            toolbarOffset.value = newOffset.coerceIn(-toolbarHeightPx, 0f)
            return Offset.Zero
        }
    }
}

val progress = (-toolbarOffset.value / toolbarHeightPx).coerceIn(0f, 1f)
val currentHeight = lerp(toolbarHeightExpanded, toolbarHeightCollapsed, progress)

progress — ключова величина. Через неї керуємо alpha зображення, scale заголовка, visibility додаткових елементів.

iOS: UIScrollViewDelegate + Auto Layout

В UIKit — класика через scrollViewDidScroll:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let offset = scrollView.contentOffset.y
    let maxOffset: CGFloat = 200 // висота розгорнутого header

    if offset < 0 {
        // Overscroll вниз — розтягуємо зображення
        headerHeightConstraint.constant = 250 - offset
        headerImageView.transform = .identity
    } else {
        let progress = min(offset / maxOffset, 1.0)
        headerHeightConstraint.constant = max(250 - offset, 56)

        // Fade out зображення
        headerImageView.alpha = 1 - progress

        // Title з'являється в nav bar
        navigationItem.title = progress > 0.9 ? screenTitle : ""
    }

    // Без layoutIfNeeded в animate — пряма зміна constraint, наступний layout pass підхопить
}

Зміна constraint без анімації прямо в scrollViewDidScroll — правильно, layout pass відбувається при наступному CADisplayLink кадрі. Виклик layoutIfNeeded() тут створить рекурсію.

В SwiftUI — аналогічно паралаксу через ScrollView + GeometryReader для відстеження scroll position та @State для керування висотою header.

Як забезпечити плавність на пристроях з 120 Гц?

Ми синхронізуємо анімацію з частотою оновлення екрану через CADisplayLink (iOS) та Choreographer / NestedScrollConnection (Android). Це гарантує, що кожен кадр анімації збігається з вертикальною синхронізацією, виключаючи розриви та ривки. Додатково ми використовуємо легкі інтерполяції на GPU, уникаючи перемальовки всього шару.

Порівняння платформ

Параметр iOS (UIKit) Android (CoordinatorLayout) Android (Compose) SwiftUI
Основний інструмент scrollViewDidScroll + Auto Layout CollapsingToolbarLayout + AppBarLayout NestedScrollConnection GeometryReader + @State
Складність Середня Низька (декларативно) Висока (кастом) Середня
Контроль над анімацією Повний Обмежений Повний Повний
Продуктивність Висока Висока Висока Висока

Згідно з Material Design guidelines, CollapsingToolbarLayout є кращим способом для стандартних сценаріїв. Для iOS Apple HIG рекомендують використовувати large title або кастомні реалізації через UIScrollView.

Процес роботи

  1. Аналіз — вивчаємо поточний UI та вимоги до анімації. Визначаємо цільові пристрої та версії ОС. Перевіряємо сумісність з App Store Review Guidelines (Section 4.2/5.1) та Play Store політиками.
  2. Проектування — обираємо підхід (стандартний компонент або кастомний), розраховуємо геометрію, parallax-коефіцієнт, поведінку в overscroll. Створюємо прототип з реальними даними.
  3. Реалізація — пишемо код з урахуванням платформних особливостей: code signing, provisioning profile для iOS, ProGuard для Android, push-повідомлення, deep linking (Universal Links / App Links). Використовуємо StoreKit 2 та Billing 6 для IAP.
  4. Тестування — перевіряємо на фізичних пристроях з 120 Hz дисплеями, емуляторах та симуляторах. Автоматизуємо тести для регресії.
  5. Деплой — готуємо інструкцію з інтеграції, передаємо у вашу CI/CD. Навчаємо команду.

Що входить в роботу

  • Готовий компонент collapsing toolbar з анімацією.
  • Вихідний код з коментарями на Swift/Kotlin.
  • Документація з кастомізації (зміна висот, кольорів, шрифтів).
  • Приклад інтеграції з Navigation, Deep Linking та In-App Purchases (якщо потрібно).
  • Консультація команди впровадження та post-launch підтримка.

Терміни та вартість

Терміни: від 1 дня (стандартний toolbar на CoordinatorLayout або LargeTopAppBar) до 3 днів (кастомний з паралаксом, кількома анімованими елементами та підтримкою двох платформ). Вартість розраховується індивідуально після аналізу вашого проєкту. Зв'яжіться з нами — ми оцінимо ваш проєкт безкоштовно та запропонуємо оптимальне рішення.

Чому варто замовити цю послугу у нас?

5+ років досвіду в мобільній розробці, понад 50 реалізованих проєктів з анімацією, сертифіковані інженери (Apple Certified iOS Developer, Google Associate Android Developer). Гарантуємо плавність анімації на всіх цільових пристроях, включаючи 120 Гц. Отримайте консультацію щодо вашого проєкту вже сьогодні.

Анімації в мобільних додатках: Lottie, Rive, Spring та Reanimated

Ми зробили анімації для десятків проєктів — від ігрових інтерфейсів до bank-grade додатків. Знаємо, як змусити 120 fps працювати навіть на Android з ProGuard. Якщо анімація смикається — проблема не в інструменті, а у виборі підходу. Нижче покажемо, як обирати між Lottie та Rive, чому Spring physics перемагає UIView.animate, і як Reanimated 3 вичавлює 60 fps на старих пристроях. Отримайте консультацію щодо вашого проєкту — оцінимо анімаційний шар безкоштовно.

Чому UIView.animate ламається на складних сценаріях

UIView.animate(withDuration:) та ObjectAnimator на Android — правильний вибір для простих переходів. Але щойно анімація стає інтерактивною (користувач тягне елемент, швидкість залежить від жесту), потрібен інший підхід.

На iOS для gesture-driven анімації правильний інструмент — UIViewPropertyAnimator. Він дозволяє призупиняти, обертати та модифікувати анімацію в процесі. Типовий кейс: bottom sheet, який слідує за пальцем, продовжує рух з інерцією після відпускання та притягується до найближчої позиції. З UIView.animate це або не працює зовсім, або потребує ручної фізики.

У SwiftUI withAnimation працює з коробки, але інтерактивність обмежена — немає прямого аналога UIViewPropertyAnimator. Обхідний шлях: .gesture(DragGesture()) + @GestureState + явне обчислення позиції. Або йдемо в SwiftUI Animations API з Animation.spring(duration:bounce:).

Як React Native Reanimated обходить JS-міст

React Native Animated API виконує анімації в JS thread — це джерело джанка при завантаженому bridge. Reanimated 3 вирішує проблему через worklets: функції, які компілюються та виконуються прямо на UI thread без перетину JS-моста.

Приклад: parallax scroll header. На базовому Animated.Value при швидкому скролі FPS падає до 40-45 на mid-range Android. На Reanimated з useAnimatedScrollHandler — стабільні 60 fps, тому що весь перерахунок позиції відбувається на UI thread.

Reanimated 3 з useSharedValue, useAnimatedStyle та withSpring/withTiming — стандарт для анімацій у React Native. Gesture Handler v2 щільно інтегрований: useAnimatedGestureHandler замінює PanResponder і теж працює на UI thread.

Чому Lottie може знижувати FPS на Android і як це виправити

Lottie експортує After Effects анімацію в JSON. На iOS з lottie-ios працює стабільно, але на Android при складних ефектах (blur, частинки, градієнти) векторний рендеринг через Canvas призводить до просадок до 30-40 fps. Рішення — або спрощувати анімацію, або використовувати Rive з апаратним рендерингом. Ми тестували: Lottie-файл з розмиттям 5 МБ на Xiaomi Redmi Note 10 дає 48 fps, аналогічна анімація в Rive (.riv 400 КБ) — 60 fps. Rive працює в 1.25 раза швидше на однаковому пристрої, а при складних ефектах — до 4 разів легше за розміром.

Як вибрати між Lottie та Rive для вашого проєкту?

Обидва інструменти вирішують задачу «дизайнер робить анімацію, розробник додає файл». Але принципово по-різному.

Критерій Lottie Rive
Формат JSON векторна анімація Бінарний .riv
Інтерактивність Ні (лінійне відтворення) State Machine, реакції на ввід
Продуктивність Середня (blur/частинки важкі) Апаратний рендеринг Metal/OpenGL
Розмір файлу 2-5 МБ 200-500 КБ
Підтримка платформ iOS, Android, Web, Flutter, RN iOS, Android, Web, Flutter, RN

Вибір простий: статична декоративна анімація (splash screen, onboarding ілюстрації) — Lottie. Інтерактивні UI-елементи зі станами — Rive. Наприклад, кнопка з hover, pressed, loading, success — одна Rive-анімація з чотирма станами проти чотирьох окремих Lottie-файлів. Це зменшує обсяг коду та спрощує підтримку.

Як впровадити анімації Lottie за 4 кроки?

  1. Експорт з After Effects через плагін Bodymovin — отримуєте .json.
  2. Додавання файлу до ресурсів проєкту (assets або drawable).
  3. Ініціалізація плеєра — в iOS використовуйте LOTAnimationView, в Android — LottieAnimationView.
  4. Запуск з параметрамиloopMode, animationSpeed, contentMode. Для складних сценаріїв — кастомний AnimationListener.

На практиці цей підхід працює, але потребує контролю версій Lottie-файлів. Ми рекомендуємо версіонувати їх разом з кодом і тестувати на цільових пристроях.

Spring-фізика та Hero transitions: як досягти природності

Spring-анімація відчувається природньо тому що імітує фізику — масу, жорсткість і демпфування. У SwiftUI: Animation.spring(response:dampingFraction:). В Android Compose: spring(dampingRatio = Spring.DampingRatioMediumBouncy).

Для Hero-переходів (елемент «перелітає» між екранами) на iOS використовуємо UIViewControllerTransitioningDelegate + UIViewControllerAnimatedTransitioning. У SwiftUI — matchedTransitionSource + navigationTransition(.zoom). На Flutter — Hero віджет, який працює з коробки.

Як уникнути типових помилок у Hero transitions?

Анімація починається нормально, але на цільовому екрані елемент «стрибає» в фінальну позицію. Причина — AutoLayout constraints застосовуються до завершення анімації. Рішення: layoutIfNeeded() в блоці анімації або використання transform замість frame-змін. На Flutter — перевіряйте HeroFlightShuttleConfiguration. Також слідкуйте за VSync: на Android при 90 Гц дисплеї Hero-перехід може виглядати смиканим, якщо не виставлений Choreographer.

Таблиця: Строки виконання та типові сценарії

Задача Час Коментар
Базові екранні переходи та мікроінтеракції 1 тиждень Стандартні gesture-driven sheets, каруселі
Lottie/Rive інтеграція з дизайн-системою 3-5 днів Після отримання фінальних файлів
Кастомна інтерактивність з фізикою (сustom sheet, drawer) 1-2 тижні Включає тестування на 4+ пристроях

Що входить в роботу: анімаційний шар під ключ

  • Інтеграція Lottie/Rive-файлів в дизайн-систему
  • Код gesture-driven переходів (bottom sheets, drawers, каруселі)
  • Тестування на реальних пристроях (iOS, Android)
  • Документація з анімацій (архітектура, ключі станів)
  • Підтримка при оновленні дизайну (гарантія 30 днів)

У нас 5+ років досвіду в мобільній розробці, понад 30 проєктів з анімаціями, сертифіковані iOS/Android розробники. Замовте анімації під ключ — перша консультація безкоштовна. Напишіть нам у месенджер, щоб узгодити деталі.

Apple Developer Documentation – UIKit Animations використовується як базова специфікація.