Реализация всплывающих наград (Reward Popup) в мобильном приложении
Reward popup — это кульминационный момент в пользовательском потоке. Пользователь завершил квест, совершил покупку, достиг уровня — и приложение должно ответить. Скучный AlertDialog убивает момент. Правильно реализованный попап с анимацией, частицами и haptic feedback превращает техническое событие в «вау».
Анатомия reward popup
Типичная структура по слоям:
-
Backdrop: затемнение фона с blur-эффектом (
UIBlurEffect/RenderEffect.createBlurEffect) - Card: появление снизу или масштабом с пружинной анимацией
- Reward icon: scale + rotation с bounce
-
Particle burst: конфетти, звёздочки, монеты —
CAEmitterLayerили Lottie - Text reveal: счётчик баллов, анимирующийся от предыдущего значения к новому
- CTA button: появляется с задержкой после основной анимации
Весь пайплайн занимает 1.2–2 секунды. Длиннее — пользователь начинает тапать «закрыть».
Анимация числового счётчика
Самодетальнейший элемент — анимированный счётчик. +500 монет должен считаться, а не просто появляться.
class CountingLabel: UILabel {
func countFrom(_ start: Int, to end: Int, duration: TimeInterval) {
let displayLink = CADisplayLink(target: self, selector: #selector(update))
self.start = start; self.end = end
self.startTime = CACurrentMediaTime()
self.duration = duration
displayLink.add(to: .main, forMode: .common)
}
@objc func update(link: CADisplayLink) {
let elapsed = CACurrentMediaTime() - startTime
let progress = min(elapsed / duration, 1.0)
let eased = 1 - pow(1 - progress, 3) // ease-out cubic
currentValue = Int(Double(start) + Double(end - start) * eased)
text = "+\(currentValue)"
if progress >= 1 { link.invalidate() }
}
}
В Flutter — TweenAnimationBuilder<int> с curve: Curves.easeOut и Text('${value}') внутри.
Particle burst
Для однократного burst конфетти при появлении попапа — CAEmitterLayer с birthRate = 300 на первые 0.1 секунды, затем birthRate = 0. Это даёт эффект «взрыва» без бесконечного потока.
emitterLayer.birthRate = 1 // включено
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
emitterLayer.birthRate = 0 // выключено — частицы летят до конца lifetime
}
Lottie-конфетти — более простой путь: готовый .json файл воспроизводится однократно поверх попапа.
Последовательность анимаций
Всё должно появляться не одновременно — stagger создаёт ощущение «разворачивания» награды:
| Время | Событие |
|---|---|
| 0ms | Backdrop появляется, blur нарастает |
| 150ms | Card влетает снизу (spring) |
| 300ms | Particle burst |
| 350ms | Icon scale up с bounce |
| 500ms | Haptic feedback (success) |
| 600ms | Текст наград fade in |
| 800ms | Счётчик начинает считать |
| 1200ms | CTA кнопка появляется |
На iOS — DispatchQueue.main.asyncAfter для каждого этапа или UIViewPropertyAnimator с addAnimations(afterDelay:). В Flutter — AnimationController + Interval curves для каждого элемента.
Dismiss анимация
Закрытие должно быть быстрым: Card уходит вниз за 0.25s с ease-in. Backdrop исчезает одновременно. Никакого затяжного исчезновения — пользователь уже хочет продолжить.
Срок: 1 день для стандартного reward popup с анимациями и haptic на одной платформе.







