Анімація перетягування (Drag & Reorder) у мобільних додатках

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

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

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

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

Послуги, які ми пропонуємо
Показано 1 з 1Усі 1734 послуг
Анімація перетягування (Drag & Reorder) у мобільних додатках
Середній
від 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

Реалізація анімації перетягування (Drag & Reorder) у мобільних додатках

Користувач довго утримує елемент списку — той піднімається, масштабується, а сусідні елементи плавно розсуваються, показуючи місце для вставки. Це анімація Drag & Reorder. Без неї не обходиться жоден додаток із сортованими списками: від задачників до інтернет-магазинів. Але реалізація на різних платформах має особливості, які можуть зламати UX. Особливо часті проблеми на проектах з високим навантаженням: перетягування 1000+ елементів потребує тротлінгу анімацій. Наші рішення перевірені в production.

Ми впровадили Drag & Reorder у 15+ проектах за 5 років досвіду. Ось ключові проблеми: відсутність стабільних ключів призводить до «стрибків» при реордерингу; мутація списку без повідомлення фреймворку викликає затримки оновлення; ігнорування оптимістичного оновлення загрожує втратою нового порядку при збої мережі. Це дозволяє заощадити до 30% часу розробки порівняно з кастомною реалізацією. Зниження витрат на підтримку на 20% при використанні готових API. Розберемо, як уникнути цих помилок.

Чому стабільні ключі критичні для анімації?

Кожен елемент списку повинен мати унікальний і стабільний ідентифікатор. Без цього фреймворк не може коректно відстежувати переміщення та анімувати розсування. Наприклад, у SwiftUI використовуйте протокол Identifiable, у Compose — key параметр у .items(), у Flutter — ValueKey при побудові елементів. Якщо ключ змінюється при реордерингу (наприклад, на основі позиції), анімація ламається.

Як вибрати між вбудованими API та кастомною реалізацією?

Вбудовані API (iOS UIKit, SwiftUI .onMove, Flutter ReorderableListView) покривають 80% сценаріїв. Кастомна реалізація потрібна, якщо:

  • потрібна нестандартна анімація піднятого елемента (наприклад, підйом з тінню, зміна кольору);
  • список містить складні комірки з вкладеними жестами;
  • необхідна інтеграція з drag-and-drop між різними списками або вкладками.

Ми рекомендуємо починати з вбудованих API та додавати кастомізацію лише за необхідності. Це прискорює розробку та спрощує тестування.

Як впровадити Drag & Reorder за 4 кроки?

  1. Вибір API. iOS (UIKit) — вбудовані UICollectionViewDragDelegate/UICollectionViewDropDelegate. Android Compose — бібліотека reorderable. Flutter — ReorderableListView.
  2. Налаштування анімації. Піднятий елемент масштабуємо та піднімаємо за допомогою shadow. На iOS це відбувається автоматично; на Android використовуємо animateFloatAsState, на Flutter — proxyDecorator.
  3. Збереження порядку. Після завершення перетягування відправляємо новий масив на backend. Застосовуємо оптимістичне оновлення: UI змінюється одразу, при помилці мережі — відкат.
  4. Тестування. Перевіряємо на пристроях з різними версіями ОС, особливо на старих iOS (12 і вище) та Android (API 26+).

Як реалізувати Drag & Reorder на iOS (UIKit)?

Згідно з Apple Human Interface Guidelines, анімація перетягування має бути плавною та передбачуваною. Використовуємо UICollectionView з делегатами:

collectionView.dragDelegate = self
collectionView.dropDelegate = self
collectionView.dragInteractionEnabled = true

// UICollectionViewDragDelegate
func collectionView(_ collectionView: UICollectionView,
                    itemsForBeginning session: UIDragSession,
                    at indexPath: IndexPath) -> [UIDragItem] {
    let item = items[indexPath.item]
    let itemProvider = NSItemProvider(object: item.id as NSString)
    let dragItem = UIDragItem(itemProvider: itemProvider)
    dragItem.localObject = item
    return [dragItem]
}

// UICollectionViewDropDelegate
func collectionView(_ collectionView: UICollectionView,
                    performDropWith coordinator: UICollectionViewDropCoordinator) {
    guard let destinationIndexPath = coordinator.destinationIndexPath,
          let item = coordinator.items.first,
          let sourceIndexPath = item.sourceIndexPath else { return }

    collectionView.performBatchUpdates {
        items.move(fromOffsets: IndexSet(integer: sourceIndexPath.item),
                   toOffset: destinationIndexPath.item)
        collectionView.moveItem(at: sourceIndexPath, to: destinationIndexPath)
    }
    coordinator.drop(item.dragItem, toItemAt: destinationIndexPath)
}

UICollectionView автоматично анімує переміщення комірок. Для UITableView підхід аналогічний з UITableViewDragDelegate/UITableViewDropDelegate. У SwiftUI використовуйте .onMove — він додає drag handles, але без кастомізації зовнішнього вигляду.

Як анімувати перетягування в Android Compose?

У Compose вбудованого reorder немає, використовуємо бібліотеку sh.calvin.reorderable:reorderable:2.4.0:

val listState = rememberLazyListState()
var list by remember { mutableStateOf(items) }
val reorderState = rememberReorderableLazyListState(listState) { from, to ->
    list = list.toMutableList().apply { add(to.index, removeAt(from.index)) }
}

LazyColumn(
    state = listState,
    modifier = Modifier.reorderable(reorderState)
) {
    items(list, key = { it.id }) { item ->
        ReorderableItem(reorderState, key = item.id) { isDragging ->
            val elevation by animateDpAsState(if (isDragging) 8.dp else 0.dp)
            val scale by animateFloatAsState(if (isDragging) 1.05f else 1f)

            Card(
                modifier = Modifier
                    .fillMaxWidth()
                    .scale(scale)
                    .shadow(elevation)
            ) {
                Row {
                    Text(item.title, modifier = Modifier.weight(1f).padding(16.dp))
                    Icon(
                        Icons.Default.DragHandle,
                        contentDescription = null,
                        modifier = Modifier
                            .detectReorderAfterLongPress(reorderState)
                            .padding(16.dp)
                    )
                }
            }
        }
    }
}

isDragging керує анімацією піднятого елемента. Для кастомної анімації розсування сусідніх елементів на Compose 1.7+ використовуйте Modifier.animateItem().

Flutter: ReorderableListView з proxyDecorator

ReorderableListView.builder(
  itemCount: items.length,
  itemBuilder: (context, index) {
    return ListTile(
      key: ValueKey(items[index].id),
      title: Text(items[index].title),
      trailing: ReorderableDragStartListener(
        index: index,
        child: const Icon(Icons.drag_handle),
      ),
    );
  },
  onReorder: (oldIndex, newIndex) {
    setState(() {
      if (newIndex > oldIndex) newIndex--;
      final item = items.removeAt(oldIndex);
      items.insert(newIndex, item);
    });
  },
  proxyDecorator: (child, index, animation) {
    return AnimatedBuilder(
      animation: animation,
      builder: (context, child) {
        final scale = Tween<double>(begin: 1.0, end: 1.05)
            .evaluate(CurvedAnimation(parent: animation, curve: Curves.easeOut));
        return Transform.scale(scale: scale, child: child);
      },
      child: child,
    );
  },
)

proxyDecorator дозволяє підняти перетягуваний елемент без зміни оригінального віджету.

Порівняння платформ: що вибрати?

Платформа Вбудована підтримка Час реалізації (простий) Час реалізації (кастомний)
iOS (UIKit) Так (з iOS 11) 2-3 години 1 день
SwiftUI Так (.onMove) 1 година 0,5 дня
Android Compose Ні (потрібна бібліотека) 3-4 години 1 день
Flutter Так (ReorderableListView) 2 години 0,5 дня

iOS вирішується вдвічі швидше за Android Compose, якщо використовувати вбудовані API. Flutter — золота середина за швидкістю та гнучкістю.

Порівняння анімаційних можливостей

Платформа Підняття елемента Розсування сусідів Кастомізація
iOS UIKit Автоматично (scale + shadow) Автоматично Обмежена
SwiftUI Через .onMove з dragPreview Через withAnimation Середня
Android Compose Ручна (animateFloatAsState) Через animateItem() Висока
Flutter Через proxyDecorator Автоматично Висока

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

При замовленні реалізації Drag & Reorder під ключ ми надаємо:

  • Аналіз поточного списку та вибір оптимального API для вашої платформи
  • Реалізацію анімації підняття та розсування елементів
  • Інтеграцію з backend для збереження нового порядку (включаючи оптимістичне оновлення)
  • Тестування на реальних пристроях з різними версіями ОС
  • Документацію щодо впровадження та підтримки

Наші інженери з 5-річним досвідом гарантують якісну реалізацію. Замовте впровадження анімації перетягування та отримайте консультацію щодо вашого проекту.

Типові помилки

  • Відсутність стабільних ключів — елементи без key призводять до того, що фреймворк не може коректно анімувати переміщення. Завжди використовуйте key: ValueKey(item.id) (Flutter), id у .items(key=...) (Compose) або реалізуйте Hashable (SwiftUI).
  • Мутація списку без повідомлення — у Compose обов'язково використовуйте mutableStateOf та створюйте новий список при мутації. У Flutter — setState. В iOS — performBatchUpdates з move.
  • Ігнорування збереження порядку — після onReorder одразу відправляйте новий порядок на backend. Використовуйте оптимістичне оновлення: UI оновлюється миттєво, при помилці мережі — відкат.

Докладніше про драг-енд-дроп на iOS читайте в офіційній документації Apple. Для Android Compose — Android Developers.

Строки та вартість

Drag & Reorder для простого списку (використовуємо вбудовані API) — півдня. Кастомний drag з анімованим проксівидом, розсуванням сусідніх елементів та інтеграцією зі сховищем — 1-2 дні. Вартість розраховується індивідуально. Наші сертифіковані інженери гарантують якість — понад 5 років досвіду. Отримайте консультацію або замовте реалізацію вже сьогодні.

Анімації в мобільних додатках: 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 використовується як базова специфікація.