Основни точки
Плъзгането е бързо плъзгащо движение на пръста по повърхността на екрана, завършващо с отделяне. Плъзгането може да бъде кратко (превъртане на елемент) или дълго (отваряне на перде), но ключовата характеристика е дискретността: системата го разпознава като едно събитие, а не като непрекъсната последователност от координати.
Мобилните ОС определят плъзгането по три параметъра: минимално разстояние (праг), минимална скорост (velocity) и посока. Ако пръстът се движи бавно или разстоянието е недостатъчно, системата интерпретира жеста като панорамиране (pan) или тап. Праговите стойности се различават между платформите: iOS използва константата UISwipeGestureRecognizer с фиксиран праг, Android — ViewConfiguration.getScaledTouchSlop(), връщащ ~8–16 dp в зависимост от плътността на екрана.
Според Android Developers Documentation, onFling (плъзгане) в GestureDetector се активира при скорост над 100 px/s и разстояние от 50px. В IT Sectr използваме плъзгане за навигация (галерии, картови интерфейси, onboarding) — този жест е интуитивен за потребители от всички възрасти.
Плъзгане и панорамиране (pan/drag) — различни типове жестове, въпреки че и двата използват движение на пръста по екрана. Плъзгането е дискретно и означава „направи едно действие”, докато панорамирането е непрекъснато и означава „премествай обекта, докато пръстът е на екрана”.
| Параметър | Плъзгане | Панорамиране (Pan/Drag) |
|---|---|---|
| Тип | Дискретен | Непрекъснат |
| Събития | Едно (разпознато/неразпознато) | Множество (.began → .changed → .ended) |
| Скорост | Висока (бързо движение) | Всякаква (бавно движение) |
| Пример iOS | UISwipeGestureRecognizer | UIPanGestureRecognizer |
| Пример Android | GestureDetector.onFling() | GestureDetector.onScroll() |
| Сценарий | Изтриване на имейл, прелистване на страница | Преместване на карта, влачене на файл |
| Проследяване на координати | Не (само посока) | Да (постоянно актуализиране на координати) |
Изборът между плъзгане и панорамиране зависи от задачата. За действия с елемент (изтриване, архивиране, отваряне на меню) използвайте плъзгане. За директно манипулиране (преместване, мащабиране) — панорамиране. Смесването на жестове на един елемент изисква require(toFail:) или персонализирана логика.
UISwipeGestureRecognizer — подклас на UIGestureRecognizer, разпознаващ дискретно плъзгане в една от четирите посоки. За разлика от UIPanGestureRecognizer, не генерира междинни .changed събития: при разпознаване action се извиква точно веднъж.
Свойството direction (UISwipeGestureRecognizer.Direction) задава проследяваната посока — .left, .right, .up, .down. Може да се комбинира чрез маска: [.left, .right] за хоризонтални плъзгания. Свойството numberOfTouchesRequired определя броя пръсти — по подразбиране 1. UISwipeGestureRecognizer няма конфигурируем праг на разстояние — iOS използва вградена стойност от около 20px, достатъчна за надеждно разпознаване.
Тъй като UISwipeGestureRecogniser е дискретен разпознавател, не конфликтира с UIPanGestureRecognizer: при бавно движение ще се активира pan, при бързо — swipe. Въпреки това при съвместно съществуване с UITapGestureRecognizer на един и същ view може да се наложи require(toFail:) за предотвратяване на фалшиво разпознаване на тап.
В Android основният API за разпознаване на плъзгане е GestureDetector с колбек OnGestureListener, а именно методът onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY). GestureDetector анализира началното и крайното положение на пръста, скоростта на движение по осите и разстоянието, след което извиква onFling при превишаване на праговете.
Минималните прагове се настройват чрез ViewConfiguration: VelocityTracker.getMinVelocity() (~100 px/s) и ViewConfiguration.getScaledTouchSlop() (~8–16 dp). Персонализираният OnSwipeTouchListener е разпространен модел, опростяващ API: разработчикът не трябва ръчно да изчислява посока и скорост. Android Support Library не съдържа вграден „SwipeDetector”, така че персонализираната обвивка е стандартното решение.
За плъзгане в списъци Google препоръчва използването на ItemTouchHelper.SimpleCallback от RecyclerView, който абстрахира откриването на жест, анимацията и колбеците onSwiped(). В IT Sectr използваме персонализиран SwipeListener за ViewPager2 и картови интерфейси — това дава контрол над чувствителността на плъзгане без вреда за производителността.
За списъци (UITableView, RecyclerView) Apple и Google предоставят специализирани API, които заменят ръчното добавяне на UISwipeGestureRecognizer. Тези API поддържат бутони за действия, персонализирана анимация и жестове „плъзни за изтриване” — стандартни модели на мобилни приложения.
В iOS (11+) се използва UISwipeActionsConfiguration: методът tableView(_:leadingSwipeActionsConfigurationForRowAt:) връща масив от UIContextualAction — бутони със заглавие, стил (destructive, normal) и манипулатор. Например, „Изтрий” — .destructive с червен фон, „Архив” — .normal със сив. Системата сама анимира появата на бутоните и извиква completion блока след натискане.
В Android ItemTouchHelper.SimpleCallback се прикрепя към RecyclerView чрез ItemTouchHelper.attachToRecyclerView(). Параметри — флагове за посока (start, end, up, down) и колбек onSwiped(viewHolder, direction). Вътре в onSwiped се извиква adapter.notifyItemRemoved(position) за изтриване на елемента с анимация. ItemTouchHelper автоматично обработва жестове, конфликти с превъртане (scroll) и връщане на елемента, ако плъзгането не е завършено. Според Google I/O 2024, ItemTouchHelper се използва в 70% от Android приложенията с действия за плъзгане в списъци.
Добавя UISwipeGestureRecognizer към view за връщане към предишния екран. Кодът показва минималната конфигурация на разпознавателя с посока .left.
import UIKit
class CardViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let swipeLeft = UISwipeGestureRecognizer(
target: self,
action: #selector(handleSwipeLeft(_:))
)
swipeLeft.direction = .left
swipeLeft.numberOfTouchesRequired = 1
view.addGestureRecognizer(swipeLeft)
}
@objc private func handleSwipeLeft(_: UISwipeGestureRecognizer) {
guard let nav = navigationController else { return }
if nav.viewControllers.count > 1 {
nav.popViewController(animated: true)
} else {
nav.dismiss(animated: true)
}
}
}
Разпознавателят се свързва с root-view на контролера. Проверката navigationController.viewControllers.count гарантира, че pop се изпълнява само при наличие на предишен екран в стека. За модални контролери се използва dismiss. Кодът не изисква проверка на state — UISwipeGestureRecognizer е дискретен и извиква action само при успешно разпознаване.
Реализира проста обвивка на GestureDetector, която определя посоката на плъзгане и извиква колбеците onSwipeLeft/Right.
import android.content.Context
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
open class OnSwipeTouchListener(context: Context) : View.OnTouchListener {
private val gestureDetector = GestureDetector(context, GestureListener())
override fun onTouch(v: View, event: MotionEvent): Boolean {
return gestureDetector.onTouchEvent(event)
}
private inner class GestureListener : GestureDetector.SimpleOnGestureListener() {
companion object {
private const val SWIPE_THRESHOLD = 100
private const val SWIPE_VELOCITY_THRESHOLD = 100
}
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
val diffX = e2.x - e1!!.x
val diffY = e2.y - e1.y
if (Math.abs(diffX) > Math.abs(diffY)) {
if (Math.abs(diffX) > SWIPE_THRESHOLD
&& Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD
) {
if (diffX > 0) onSwipeRight()
else onSwipeLeft()
return true
}
} else {
if (Math.abs(diffY) > SWIPE_THRESHOLD
&& Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD
) {
if (diffY > 0) onSwipeDown()
else onSwipeUp()
return true
}
}
return false
}
}
open fun onSwipeLeft() {}
open fun onSwipeRight() {}
open fun onSwipeUp() {}
open fun onSwipeDown() {}
}
OnSwipeTouchListener разширява SimpleOnGestureListener, имплементирайки onFling. diffX/diffY определят посоката, SWIPE_THRESHOLD (100px) и SWIPE_VELOCITY_THRESHOLD (100 px/s) — прагове. Употреба: view.setOnTouchListener(OnSwipeTouchListener(context).apply { onSwipeLeft = { dismiss() } }).
Добавя бутон за изтриване при плъзгане наляво по клетка на таблица. Използва вградения API на iOS 11+ без ръчно разпознаване на жестове.
func tableView(
_ tableView: UITableView,
trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath
) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(
style: .destructive,
title: "Изтрий"
) { _, _, completionHandler in
items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .automatic)
completionHandler(true)
}
deleteAction.backgroundColor = .systemRed
let config = UISwipeActionsConfiguration(actions: [deleteAction])
config.performsFirstActionWithFullSwipe = true
return config
}
Методът връща UISwipeActionsConfiguration с масив от UIContextualAction. style: .destructive автоматично задава червен фон. performsFirstActionWithFullSwipe = true позволява изпълнение на действието с пълно плъзгане без допълнително натискане. В IT Sectr използваме този API за всички списъци с действия — стандартизиран е, поддържа VoiceOver и не изисква персонализирани GestureRecognizer.
Често задавани въпроси
Плъзгането е дискретен жест с бързо движение и отделяне на пръста, панорамирането е непрекъснато влачене с проследяване на координати. UISwipeGestureRecognizer се активира веднъж, UIPanGestureRecognizer генерира .began → .changed → .ended. Изборът зависи от сценария: плъзгане — за действия (изтриване, прелистване), pan — за преместване (карта, слайдър).
Логиката на жеста е идентична, но API се различава. iOS използва UISwipeGestureRecognizer с direction. Android използва GestureDetector.onFling() с персонализирани прагове. Резултатът — еднакъв UX с различна реализация. Крос-платформените рамки (Flutter, React Native) абстрахират разликата чрез единен API за плъзгане.
В iOS (11+) използвайте UISwipeActionsConfiguration в UITableViewDelegate. В Android — ItemTouchHelper.SimpleCallback за RecyclerView. Тези API заместват ръчните GestureRecognizer, поддържат бутони за действия и анимация, правилно обработват конфликта с превъртане и се адаптират към платформените HIG.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също