Впровадження Hero-анімацій переходів між екранами в мобільних додатках
Hero-перехід — коли елемент з одного екрана «летить» на інший екран, стаючи його частиною. Карточка товару розкривається в повноекранний детальний вигляд, аватар зі списку плавно переміщується в шапку профілю. Користувачі розуміють просторову зв'язок між екранами без пояснень.
Складність не самої анімації—це правильна синхронізація lifecycle двох екранів, щоб елемент виглядав як той, що безперервно рухається, а не зникає на одному екрані й з'являється на іншому.
Flutter: Hero Widget
Flutter реалізує hero-переходи нативно через віджет Hero:
// Екран зі списком
Hero(
tag: 'product-image-${product.id}', // унікальний тег
child: CachedNetworkImage(
imageUrl: product.imageUrl,
fit: BoxFit.cover,
),
)
// Екран деталей
Hero(
tag: 'product-image-${product.id}',
child: CachedNetworkImage(
imageUrl: product.imageUrl,
fit: BoxFit.contain,
),
)
Navigator.push з будь-яким PageRoute автоматично запускає hero-анімацію між Hero віджетами з однаковим tag. Тривалість — 300 мс за замовчуванням, контролюється через transitionDuration у MaterialPageRoute або кастомному PageRoute.
Проблема: якщо зображення в списку обрізується ClipRRect, а на екрані деталей — ні, форма стрибає під час переходу. Рішення: використовуйте Hero з flightShuttleBuilder для кастомного віджета під час польоту:
Hero(
tag: 'product-image-${product.id}',
flightShuttleBuilder: (_, animation, __, fromCtx, toCtx) {
return AnimatedBuilder(
animation: animation,
builder: (_, child) => ClipRRect(
borderRadius: BorderRadius.lerp(
BorderRadius.circular(12),
BorderRadius.zero,
animation.value,
)!,
child: child,
),
child: CachedNetworkImage(imageUrl: product.imageUrl, fit: BoxFit.cover),
);
},
child: ...,
)
Це анімує borderRadius від 12 (карточка) до 0 (повний екран) під час переходу.
React Native: Shared Element Transition
React Native не має нативного hero-переходу. Використовуйте react-native-shared-element (реальний нативний рівень) або react-navigation v6 з createSharedElementStackNavigator з react-navigation-shared-element:
// Список товарів
<SharedElement id={`product.${item.id}.image`}>
<Image source={{ uri: item.imageUrl }} style={styles.thumbnail} />
</SharedElement>
// Екран деталей
<SharedElement id={`product.${item.id}.image`}>
<Image source={{ uri: item.imageUrl }} style={styles.fullImage} />
</SharedElement>
Конфігурація навігатора:
const Stack = createSharedElementStackNavigator();
<Stack.Screen
name="ProductDetail"
component={ProductDetailScreen}
sharedElements={(route) => [
{ id: `product.${route.params.product.id}.image`, animation: 'move' },
{ id: `product.${route.params.product.id}.title`, animation: 'fade' },
]}
/>
animation: 'move' переміщує елемент. 'fade' cross-fade на місці. 'fade-in' з'являється в новому місці. Для зображень—'move', для тексту—'fade' (масштабування тексту виглядає деграціозно).
iOS (SwiftUI): matchedGeometryEffect
@Namespace private var heroNamespace
// У списку
Image(product.imageName)
.matchedGeometryEffect(id: "product-image-\(product.id)", in: heroNamespace)
.frame(width: 80, height: 80)
// У детальному виді (умовний рендеринг)
if showDetail {
Image(product.imageName)
.matchedGeometryEffect(id: "product-image-\(product.id)", in: heroNamespace)
.frame(width: UIScreen.main.bounds.width, height: 300)
}
matchedGeometryEffect працює в одній View-ієрархії. Для модальних екранів (sheet, fullScreenCover) це складніше й вимагає кастомних переходів через AnyTransition з GeometryEffect. Для деталей див. послугу Matched Geometry Effect.
Поширені проблеми
Мерцання на початку переходу: елемент миттєво зникає з вихідного екрана при запуску анімації. У Flutter Hero приховує оригінал через Opacity за замовчуванням—це нормально. У React Native SharedElement іноді показує обидва елементи одночасно. Виправляється через useNativeDriver: true та перевірку версій бібліотеки.
Різний вміст: зображення в списку—кешована мініатюра, на екрані деталей—оригінал високої роздільної здатності. Поки завантажується оригінал, hero повинен показувати мініатюру. У Flutter Hero завжди використовує віджет з вихідного екрана під час польоту, потім перемикається. Переконайтеся, що CachedNetworkImage на обох екранах використовує той же URL для узгодженого розміру, або явно встановіть memCacheWidth.
Час розробки
Hero-перехід для одного типу елемента (зображення чи карточка) на одній платформі займає 1 день. Кілька типів hero-елементів (зображення + текст + іконка) з кастомною анімацією форми займає 2–3 дні. Вартість розраховується індивідуально.







