Pontos principais
Pinch-to-Zoom (pinça) é um gesto multitoque que usa dois dedos para alterar a escala do conteúdo exibido. Ao juntar os dedos (pinch close), a escala diminui; ao afastá-los (pinch open), aumenta. O gesto é contínuo: o sistema rastreia a distância entre os dedos em tempo real e gera eventos intermediários, permitindo que o aplicativo atualize suavemente a escala.
A mecânica do gesto baseia-se no cálculo da distância entre dois pontos de toque. iOS e Android usam a distância euclidiana: sqrt((x2−x1)² + (y2−y1)²). A escala atual é calculada como a razão entre a distância atual e a distância inicial. Se a distância inicial era 100px e se torna 150px — scale = 1.5 (aumento de 50%). O iOS passa a escala relativa ao estado anterior (delta), o Android passa a escala acumulada através do ScaleGestureDetector.
De acordo com as Google Material Design Guidelines, o Pinch-to-Zoom é obrigatório para aplicativos que exibem imagens, mapas ou documentos. Na IT Sectr, implementamos este gesto com UIPinchGestureRecognizer e ScaleGestureDetector em todos os projetos que trabalham com conteúdo visual — de galerias a editores de imagem.
UIPinchGestureRecognizer é uma subclasse contínua de UIGestureRecognizer para reconhecer o gesto de pinça. Ao contrário dos reconhecedores discretos (UITapGestureRecognizer), ele gera uma sequência de eventos: .began (dedos tocam a tela), .changed (cada movimento dos dedos), .ended (dedos são levantados).
Propriedades principais: scale (CGFloat) — mudança relativa na distância entre os dedos desde o início do gesto; velocity (CGFloat) — taxa de mudança da escala por segundo. Escala inicial = 1.0. Ao afastar os dedos, a escala aumenta (1.0 → 1.5 → 2.0); ao juntá-los, diminui (1.0 → 0.8 → 0.5). A propriedade velocity é usada para inércia: após .ended, você pode animar a continuação da escala com desaceleração.
UIPinchGestureRecognizer suporta a propriedade delegate para reconhecimento simultâneo com outros reconhecedores (por exemplo, pan + pinch em um mapa). O método gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) retorna true, e ambos os gestos funcionam em paralelo. A Apple recomenda adicionar UIPinchGestureRecognizer à view contêiner em vez de elementos individuais — isso simplifica o cálculo do ponto pivô da escala.
ScaleGestureDetector é uma classe da Android Support Library (android.view) projetada para reconhecer escala multitoque. Ao contrário do GestureDetector.onFling, o ScaleGestureDetector é especificamente construído para zoom com dois dedos e calcula automaticamente focusX, focusY (o ponto central entre os dedos) e scaleFactor (delta de escala do evento anterior).
ScaleGestureDetector funciona através da interface OnScaleGestureListener com três métodos: onScaleBegin(detector) (análogo a .began), onScale(detector) (análogo a .changed, retorna boolean — true se a escala for aceita), onScaleEnd(detector) (análogo a .ended). Propriedades: scaleFactor — multiplicador de escala relativo ao evento anterior (~1.01 para afastamento lento), focusX/focusY — coordenadas do ponto central entre os dedos.
Para aplicar escala a uma View, geralmente se usa Matrix: view.imageMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY). Matrix suporta acúmulo de transformações — cada chamada postScale adiciona uma nova transformação à existente. É importante lembrar: ScaleGestureDetector não limita a escala mínima/máxima — essa é responsabilidade do desenvolvedor. Na IT Sectr, usamos ScaleGestureDetector com limites de escala personalizados (0.5–3.0) e animação de retorno ao exceder os limites.
Ao implementar Pinch-to-Zoom, é criticamente importante limitar a escala mínima e máxima — sem isso, o usuário pode ampliar até o infinito ou reduzir até a invisibilidade. Valores padrão: min = 0.5 (50% do tamanho original), max = 3.0 (300%). No iOS, a limitação é feita via clamp: newScale = min(max(currentScale * gesture.scale, minScale), maxScale). No Android — através de uma verificação após postScale.
O ponto pivô é o centro entre os dedos, em relação ao qual a escala é realizada. O iOS calcula automaticamente o centróide dos dois toques e o passa através de location(in:) — o desenvolvedor não precisa calcular o pivô manualmente. O Android fornece focusX/focusY no ScaleGestureDetector, mas ao aplicar via Matrix, o pivô é passado como terceiro e quarto argumentos em postScale(scaleFactor, scaleFactor, focusX, focusY). Sem um ponto pivô, a escala será realizada em relação ao canto superior esquerdo (0,0), o que faz o conteúdo se deslocar durante o zoom.
Com base na experiência da IT Sectr, o cálculo correto do ponto pivô é a fonte mais comum de bugs ao implementar Pinch-to-Zoom. Em 90% dos casos, usar as coordenadas integradas do GestureRecognizer e ScaleGestureDetector é suficiente — não é necessário calcular o pivô manualmente. Se o conteúdo se deslocar durante a escala, verifique se você está passando focusX/focusY para matrix.postScale().
Para uma UX suave após o término do gesto, você pode adicionar inércia: no iOS — use velocity do UIPinchGestureRecognizer com UIView.animate e parâmetros spring; no Android — OverScroller ou ValueAnimator. A inércia não deve ultrapassar os limites de escala mín/máx — o valor final é limitado com uma animação de retorno spring ao exceder o limite.
Adiciona UIPinchGestureRecognizer a UIImageView com limitação de escala de 0.5 a 3.0. Mostra uma implementação mínima com preservação da escala acumulada.
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
}
}
}
Lógica: em .began/.changed, a escala é aplicada via CGAffineTransform com clamp. Em .ended, a escala é redefinida para 1.0 e o valor acumulado é armazenado em currentScale. Isso evita a deriva da escala durante gestos repetidos — cada novo gesto começa do estado atual. Para imagens, recomenda-se UIScrollView com minimumZoomScale/maximumZoomScale em vez de CGAffineTransform.
Implementa a escala de ImageView via ScaleGestureDetector com Matrix. Mostra o manuseio correto do ponto pivô e a limitação da escala.
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
}
}
O ImageView personalizado sobrescreve onTouchEvent, passando eventos para ScaleGestureDetector. OnScaleListener.onScale() é chamado em cada movimento dos dedos. detector.scaleFactor é o delta (1.01 para afastamento lento). currentScale acumula com coerceIn para clamp. focusX/focusY — o ponto central entre os dedos, passado para postScale para escala correta do pivô.
UIScrollView possui suporte integrado a Pinch-to-Zoom através de seu delegate. Esta é a abordagem preferida para imagens e documentos — o ScrollView gerencia escala, inércia e rolagem automaticamente.
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 cria automaticamente um UIPinchGestureRecognizer, gerencia a escala através da transformação de CALayer e adiciona um efeito de ressalto ao ultrapassar os limites. O código se reduz a definir min/maxZoomScale e retornar viewForZooming. Na IT Sectr, usamos UIScrollView para todas as imagens em tela cheia — isso reduz o volume de código em 5 vezes em comparação com a implementação manual via UIPinchGestureRecognizer.
Perguntas frequentes
Em uma tela sensível ao toque, pinch usa dois dedos com rastreamento da distância entre eles. Em um trackpad de MacBook, o zoom é implementado através de UIPinchGestureRecognizer com os mesmos estados (.began → .changed → .ended), mas a fonte do toque é um trackpad com Force Touch, não uma tela capacitiva. A API é a mesma, a mecânica é idêntica.
No iOS: após obter a escala de UIPinchGestureRecognizer, multiplique por currentScale e aplique clamp(min: 0.5, max: 3.0). No Android: use coerceIn(minScale, maxScale) antes de matrix.postScale(). No UIScrollView — através das propriedades minimumZoomScale e maximumZoomScale. Sem limitação, a escala pode ir ao infinito ou zero.
Não, o pinch requer pelo menos dois dedos. Para zoom com um dedo (duplo toque + arrastar para cima/baixo), é necessária uma implementação personalizada via UIPanGestureRecognizer ou GestureDetector.onScroll. Este é um gesto diferente com lógica diferente — geralmente usado para zoom na câmera ou mapas.
Resumo
Vamos desenvolver um aplicativo móvel chave na mão
A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.
Leia também