Pinch-to-Zoom — същност, обработка на щипка в мобилни приложения

Автор: IT Sectr Публикувано: 2026-02-27 Време за четене: 8 мин
Pinch-to-Zoom (щипване с пръсти) — мултитъч жест за мащабиране, при който потребителят събира или разтваря два пръста на екрана за намаляване или увеличаване на съдържанието. Този жест стана де факто стандарт за преглед на изображения, карти, уеб страници и документи на мобилни устройства. Според Apple HIG и Google Material Design, Pinch-to-Zoom е основен жест, който потребителите очакват във всички приложения, работещи с визуално съдържание.

Основни точки

  • Pinch-to-Zoom — мултитъч жест с два пръста за мащабиране на съдържание на мобилни устройства.
  • В iOS обработката се извършва чрез UIPinchGestureRecognizer с непрекъснато проследяване .began → .changed → .ended.
  • В Android се използва ScaleGestureDetector (от Support Library) с обратно извикване onScale за актуализиране на мащаба чрез Matrix.
  • UIPinchGestureRecognizer има свойство scale (коефициент спрямо предишното състояние) и velocity (скорост).
  • За ограничаване на zoom-a се прилага clamp на коефициента scale между зададените стойности min/max (обикновено 0.5–3.0).

Какво е Pinch-to-Zoom?

Pinch-to-Zoom (щипване или стискане) — мултитъч жест, който използва два пръста за промяна на мащаба на показаното съдържание. При събиране на пръстите (pinch close) мащабът намалява, при разтваряне (pinch open) — се увеличава. Жестът е непрекъснат: системата проследява разстоянието между пръстите в реално време и генерира междинни събития, позволявайки на приложението плавно да актуализира мащаба.

Механиката на жеста се основава на изчисляване на разстоянието между две точки на докосване. iOS и Android използват Евклидово разстояние: sqrt((x2-x1)² + (y2-y1)²). Текущият scale се изчислява като отношение на текущото разстояние към началното. Ако началното разстояние е било 100px, а е станало 150px — scale = 1.5 (увеличение с 50%). iOS предава scale спрямо предишното състояние (делта), Android — натрупан scale чрез ScaleGestureDetector.

Според Google Material Design Guidelines, Pinch-to-Zoom е задължителен за приложения, показващи изображения, карти или документи. В IT Sectr ние имплементираме този жест с UIPinchGestureRecognizer и ScaleGestureDetector във всички проекти, работещи с визуално съдържание — от галерии до редактори на изображения.

Pinch-to-Zoom в iOS: UIPinchGestureRecognizer

UIPinchGestureRecognizer — непрекъснат подклас на UIGestureRecognizer за разпознаване на жест щипка. За разлика от дискретните разпознаватели (UITapGestureRecognizer), генерира последователност от събития: .began (пръстите докоснаха екрана), .changed (всяко движение на пръстите), .ended (пръстите се вдигнаха).

Ключови свойства: scale (CGFloat) — относителна промяна на разстоянието между пръстите от началото на жеста; velocity (CGFloat) — скорост на промяна на scale за секунда. Начален scale = 1.0. При разтваряне на пръстите scale расте (1.0 → 1.5 → 2.0), при събиране — намалява (1.0 → 0.8 → 0.5). Свойството velocity се използва за инерция: след .ended може да се анимира продължаване на мащабирането със затихване.

UIPinchGestureRecognizer поддържа свойството delegate за едновременна работа с други разпознаватели (напр. pan + pinch на карта). Методът gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) връща true, и двата жеста работят паралелно. Apple препоръчва добавяне на UIPinchGestureRecognizer към контейнерен view, а не към отделни елементи — това опростява изчисляването на pivot точката на мащабиране.

Pinch-to-Zoom в Android: ScaleGestureDetector

ScaleGestureDetector — клас от Android Support Library (android.view), предназначен за разпознаване на мултитъч мащабиране. За разлика от GestureDetector.onFling, ScaleGestureDetector е специално проектиран за двупръстов zoom и автоматично изчислява focusX, focusY (център между пръстите) и scaleFactor (делта на мащаба спрямо предишното събитие).

ScaleGestureDetector работи чрез интерфейса OnScaleGestureListener с три метода: onScaleBegin(detector) (еквивалент на .began), onScale(detector) (еквивалент на .changed, връща boolean — true, ако мащабът е приет), onScaleEnd(detector) (еквивалент на .ended). Свойства: scaleFactor — множител на мащаба спрямо предишното събитие (~1.01 при бавно разтваряне), focusX/focusY — координати на центъра между пръстите.

За прилагане на мащаба към View обикновено се използва Matrix: view.imageMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY). Matrix поддържа натрупване на трансформации — всяко извикване на postScale добавя нова трансформация към съществуващата. Важно е да се помни: ScaleGestureDetector не ограничава минималния/максималния мащаб — това е задача на разработчика. В IT Sectr ние прилагаме ScaleGestureDetector с персонализирани граници на scale (0.5–3.0) и анимация на връщане при превишаване.

Ограничение на zoom и pivot точка

При имплементация на Pinch-to-Zoom е критично важно да ограничите минималния и максималния мащаб — без това потребителят може да увеличи изображението до безкрайност или да го намали до невидимост. Стандартни стойности: min = 0.5 (50% от оригиналния размер), max = 3.0 (300%). В iOS ограничението се извършва чрез clamp: newScale = min(max(currentScale * gesture.scale, minScale), maxScale). В Android — чрез проверка след postScale.

Pivot точката — център между пръстите, спрямо който се извършва мащабирането. iOS автоматично изчислява центъра на две докосвания и го предава чрез location(in:) — разработчикът не трябва да изчислява pivot ръчно. Android предоставя focusX/focusY в ScaleGestureDetector, но при прилагане чрез Matrix pivot се предава като трети и четвърти аргумент в postScale(scaleFactor, scaleFactor, focusX, focusY). Без pivot точка мащабирането ще се извършва спрямо горния ляв ъгъл (0,0), което води до изместване на съдържанието при zoom.

По опит на IT Sectr, правилното изчисляване на pivot точката е най-честият източник на грешки при внедряване на Pinch-to-Zoom. В 90% от случаите е достатъчно да използвате вградените координати на GestureRecognizer и ScaleGestureDetector, без да изчислявате pivot ръчно. Ако съдържанието се измества при мащабиране — проверете дали предавате focusX/focusY на matrix.postScale().

Инерция след zoom

За плавно потребителско изживяване след завършване на жеста може да се добави инерция: в iOS — използвайте velocity от UIPinchGestureRecognizer за UIView.animate с spring параметри; в Android — OverScroller или ValueAnimator. Инерцията не трябва да излиза извън границите на min/max scale — крайната стойност се отрязва с spring анимация на връщане при превишаване.

Примери на код в Swift и Kotlin

Пример 1: Pinch-to-Zoom на iOS (Swift)

Добавя UIPinchGestureRecognizer към UIImageView с ограничение на мащаба от 0.5 до 3.0. Показва минимална имплементация със запазване на натрупания мащаб.

swift
import UIKit

class ZoomableImageViewController: UIViewController {

    @IBOutlet private var imageView: UIImageView!
    private var currentScale: CGFloat = 1.0
    private let minScale: CGFloat = 0.5
    private let maxScale: CGFloat = 3.0

    override func viewDidLoad() {
        super.viewDidLoad()

        let pinch = UIPinchGestureRecognizer(
            target: self,
            action: #selector(handlePinch(_:))
        )
        imageView.addGestureRecognizer(pinch)
        imageView.isUserInteractionEnabled = true
    }

    @objc private func handlePinch(_: UIPinchGestureRecognizer) {
        if gestureRecognizer.state == .began
            || gestureRecognizer.state == .changed {

            let newScale = currentScale * gestureRecognizer.scale
            imageView.transform = CGAffineTransform(
                scaleX: min(max(newScale, minScale), maxScale),
                y: min(max(newScale, minScale), maxScale)
            )
        }
        if gestureRecognizer.state == .ended {
            currentScale = min(max(
                currentScale * gestureRecognizer.scale,
                minScale
            ), maxScale)
            gestureRecognizer.scale = 1.0
        }
    }
}

Логика: в .began/.changed мащабът се прилага чрез CGAffineTransform с clamp. В .ended scale се нулира до 1.0, а натрупаната стойност се запазва в currentScale. Това предотвратява „дрейф" на мащаба при многократни жестове — всеки нов жест започва от текущото състояние. За изображения вместо CGAffineTransform се препоръчва UIScrollView с minimumZoomScale/maximumZoomScale.

Пример 2: Pinch-to-Zoom на Android (Kotlin)

Имплементира мащабиране на ImageView чрез ScaleGestureDetector с Matrix. Показва коректна обработка на pivot точка и ограничение на мащаба.

kotlin
import android.content.Context
import android.graphics.Matrix
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.widget.ImageView

class PinchZoomImageView(context: Context) : ImageView(context) {

    private val matrix = Matrix()
    private var currentScale = 1f
    private val minScale = 0.5f
    private val maxScale = 3.0f

    private val scaleDetector =
        ScaleGestureDetector(context, ScaleListener())

    private inner class ScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener() {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            currentScale = (currentScale * detector.scaleFactor)
                .coerceIn(minScale, maxScale)
            matrix.postScale(
                currentScale,
                currentScale,
                detector.focusX,
                detector.focusY
            )
            imageMatrix = matrix
            return true
        }
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        scaleDetector.onTouchEvent(event)
        return true
    }
}

Персонализиран ImageView презаписва onTouchEvent, предавайки събития на ScaleGestureDetector. OnScaleListener.onScale() се извиква при всяко движение на пръстите. detector.scaleFactor — делта (1.01 при бавно разтваряне). currentScale се натрупва с coerceIn за clamp. focusX/focusY — център между пръстите, предаден на postScale за коректно pivot мащабиране.

Пример 3: UIScrollView с поддръжка на zoom (iOS)

UIScrollView има вградена поддръжка на Pinch-to-Zoom чрез делегат. Това е предпочитаният начин за изображения и документи — ScrollView управлява мащаба, инерцията и превъртането автоматично.

swift
class ScrollableImageViewController: UIViewController,
                                    UIScrollViewDelegate {

    @IBOutlet private var scrollView: UIScrollView!
    @IBOutlet private var imageView: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()
        scrollView.minimumZoomScale = 0.5
        scrollView.maximumZoomScale = 3.0
        scrollView.delegate = self
    }

    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return imageView
    }
}

UIScrollView самостоятелно създава UIPinchGestureRecognizer, управлява мащаба чрез CALayer трансформация и добавя bounce ефект при превишаване на границите. Кодът се свежда до задаване на min/maxZoomScale и връщане на viewForZooming. В IT Sectr използваме UIScrollView за всички изображения на цял екран — това намалява обема на кода 5 пъти в сравнение с ръчна имплементация чрез UIPinchGestureRecognizer.

Често задавани въпроси

С какво pinch се различава от щипване на тракпад?

На сензорния екран pinch използва два пръста с проследяване на разстоянието между тях. На тракпада на MacBook мащабирането е имплементирано чрез UIPinchGestureRecognizer със същите състояния (.began → .changed → .ended), но източникът на докосвания е тракпад с Force Touch, а не капацитивен екран. API е същият, механиката е идентична.

Как да ограничите минималния и максималния zoom?

В iOS: след получаване на scale от UIPinchGestureRecognizer умножете по currentScale и приложете clamp(min: 0.5, max: 3.0). В Android: чрез coerceIn(minScale, maxScale) преди matrix.postScale(). В UIScrollView — чрез свойствата minimumZoomScale и maximumZoomScale. Без ограничение мащабът може да отиде до безкрайност или нула.

Работи ли Pinch-to-Zoom с един пръст?

Не, pinch изисква минимум два пръста. За zoom с един пръст (double-tap + плъзгане нагоре/надолу) е необходима персонализирана имплементация чрез UIPanGestureRecognizer или GestureDetector.onScroll. Това е различен жест с различна логика — обикновено се използва за zoom в камера или карти.

Резюме

  • Pinch-to-Zoom — мултитъч жест с два пръста за мащабиране на съдържание, стандарт за изображения, карти и документи.
  • UIPinchGestureRecognizer в iOS — непрекъснат разпознавател с scale (делта) и velocity (скорост).
  • ScaleGestureDetector в Android — специализиран детектор с onScale(), focusX/focusY за pivot точка.
  • За UIScrollView zoom се вгражда чрез minimumZoomScale/maximumZoomScale — по-просто от ръчна имплементация.
  • Задължително ограничете мащаба (0.5–3.0) чрез clamp — без това съдържанието може да стане невидимо.
  • Pivot точката на мащабиране — център между пръстите; без нея съдържанието се измества при увеличаване.
  • В IT Sectr използваме UIScrollView за изображения и персонализиран ScaleGestureDetector за редактори — изборът зависи от сложността на трансформациите.

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също