Pinch-to-Zoom — ουσία, επεξεργασία τσιμπήματος σε εφαρμογές κινητών

Συγγραφέας: IT Sectr Δημοσιεύτηκε: 2026-02-27 Χρόνος ανάγνωσης: 8 λεπ
Pinch-to-Zoom (τσίμπημα με τα δάχτυλα) — μια χειρονομία κλιμάκωσης πολλαπλής αφής κατά την οποία ο χρήστης ενώνει ή ανοίγει δύο δάχτυλα στην οθόνη για μείωση ή αύξηση του περιεχομένου. Αυτή η χειρονομία έχει γίνει το de facto πρότυπο για την προβολή εικόνων, χαρτών, ιστοσελίδων και εγγράφων σε κινητές συσκευές. Σύμφωνα με το Apple HIG και το Google Material Design, το Pinch-to-Zoom είναι η βασική χειρονομία που οι χρήστες αναμένουν σε όλες τις εφαρμογές που λειτουργούν με οπτικό περιεχόμενο.

Κύρια σημεία

  • Pinch-to-Zoom — μια χειρονομία πολλαπλής αφής με δύο δάχτυλα για κλιμάκωση περιεχομένου σε κινητές συσκευές.
  • Στο iOS, η επεξεργασία γίνεται μέσω UIPinchGestureRecognizer με συνεχή παρακολούθηση .began → .changed → .ended.
  • Στο Android, χρησιμοποιείται ScaleGestureDetector (από το Support Library) με callback onScale για ενημέρωση κλίμακας μέσω Matrix.
  • Το UIPinchGestureRecognizer έχει την ιδιότητα scale (συντελεστής σε σχέση με προηγούμενη κατάσταση) και velocity (ταχύτητα).
  • Για τον περιορισμό του zoom, εφαρμόζεται 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)²). Η τρέχουσα κλίμακα υπολογίζεται ως ο λόγος της τρέχουσας απόστασης προς την αρχική. Αν η αρχική απόσταση ήταν 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 με προσαρμοσμένα όρια κλίμακας (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 και προσθέτει εφέ αναπήδησης κατά την υπέρβαση ορίων. Ο κώδικας περιορίζεται στη ρύθμιση min/maxZoomScale και στην επιστροφή viewForZooming. Στην IT Sectr, χρησιμοποιούμε UIScrollView για όλες τις εικόνες πλήρους οθόνης — αυτό μειώνει τον όγκο κώδικα κατά 5 φορές σε σύγκριση με τη χειροκίνητη υλοποίηση μέσω UIPinchGestureRecognizer.

Συχνές ερωτήσεις

Σε τι διαφέρει το pinch από το τσίμπημα στο trackpad;

Στην οθόνη αφής, το pinch χρησιμοποιεί δύο δάχτυλα με παρακολούθηση της απόστασης μεταξύ τους. Στο trackpad MacBook, η κλιμάκωση υλοποιείται μέσω UIPinchGestureRecognizer με τις ίδιες καταστάσεις (.began → .changed → .ended), αλλά η πηγή αφής είναι το trackpad με 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. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.

Συζήτηση έργου

Διαβάστε επίσης