Реалізація спливаючих вікон нагород (Gamification) у мобільних додатках
Спливаюче вікно нагороди — це кульмінаційний момент у потоці взаємодії користувача. Користувач завершив квест, здійснив покупку, досяг рівня — і додаток повинен відповісти по-гідному. Нудний AlertDialog гублить момент. Правильно реалізоване спливаюче вікно з анімацією, частинками та haptic feedback перетворює технічну подію на справжній тріумф.
Анатомія спливаючого вікна нагороди
Типова структура за шарами:
-
Фон (Backdrop): затемнення з ефектом розмиття (
UIBlurEffect/RenderEffect.createBlurEffect) - Карта (Card): з'явлення знизу або масштабування з пружинною анімацією
- Іконка нагороди: масштабування + обертання з bounce-ефектом
-
Вибух частинок: конфеті, зірочки, монети —
CAEmitterLayerабо Lottie - Розкриття тексту: лічильник очок, анімація від попереднього до нового значення
- Кнопка CTA: з'являється з затримкою після основної анімації
Вся послідовність займає 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}') всередину.
Вибух частинок
Для однократного вибуху конфеті при з'явленні спливаючого вікна — CAEmitterLayer з birthRate = 300 на перші 0,1 секунди, потім birthRate = 0. Це створює ефект "вибуху" без безперервного потоку частинок.
emitterLayer.birthRate = 1 // увімкнено
DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) {
emitterLayer.birthRate = 0 // вимкнено — частинки летять до кінця свого часу життя
}
Lottie-конфеті — простіший підхід: завантажити готовий .json файл та відтворити його один раз над спливаючим вікном.
Послідовність анімацій
Все повинно з'являтися не одночасно — розтягнення створює ефект "розгортання" нагороди:
| Час | Подія |
|---|---|
| 0ms | Фон з'являється, розмиття зростає |
| 150ms | Карта влітає знизу (spring) |
| 300ms | Вибух частинок |
| 350ms | Іконка масштабується з bounce |
| 500ms | Haptic feedback (успіх) |
| 600ms | Текст нагороди fade in |
| 800ms | Лічильник починає рахувати |
| 1200ms | Кнопка CTA з'являється |
На iOS — використовуйте DispatchQueue.main.asyncAfter для кожного етапу або UIViewPropertyAnimator з addAnimations(afterDelay:). У Flutter — використовуйте AnimationController + Interval curves для кожного елемента.
Анімація закриття
Закриття повинно бути швидким: карта виходить вниз за 0,25s з ease-in. Фон зникає одночасно. Без тривалого затухання — користувачі хочуть продовжити.
Тривалість: 1 день для стандартного спливаючого вікна нагороди з анімаціями та haptic feedback на одній платформі.







