Mga pangunahing puntos
Pinch-to-Zoom (kurot o pisil) — isang multi-touch na kilos na gumagamit ng dalawang daliri upang baguhin ang scale ng ipinapakitang nilalaman. Kapag pinagdikit ang mga daliri (pinch close) bumababa ang scale, kapag pinalayo (pinch open) — tumataas. Ang kilos ay tuluy-tuloy: sinusubaybayan ng system ang distansya sa pagitan ng mga daliri sa real-time at bumubuo ng mga intermediate na kaganapan, na nagpapahintulot sa app na maayos na i-update ang scale.
Ang mekanika ng kilos ay batay sa pagkalkula ng distansya sa pagitan ng dalawang punto ng pagpindot. Ang iOS at Android ay gumagamit ng Euclidean na distansya: sqrt((x2-x1)² + (y2-y1)²). Ang kasalukuyang scale ay kinakalkula bilang ratio ng kasalukuyang distansya sa paunang distansya. Kung ang paunang distansya ay 100px at naging 150px — scale = 1.5 (50% na pagtaas). Ang iOS ay nagpapadala ng scale kumpara sa dating estado (delta), Android — naipon na scale sa pamamagitan ng ScaleGestureDetector.
Ayon sa Google Material Design Guidelines, ang Pinch-to-Zoom ay kinakailangan para sa mga app na nagpapakita ng mga larawan, mapa, o dokumento. Sa IT Sectr, ipinapatupad namin ang kilos na ito gamit ang UIPinchGestureRecognizer at ScaleGestureDetector sa lahat ng proyektong gumagamit ng visual na nilalaman — mula sa mga gallery hanggang sa mga editor ng larawan.
UIPinchGestureRecognizer — isang tuluy-tuloy na subclass ng UIGestureRecognizer para sa pagkilala ng kilos na kurot. Hindi tulad ng mga discrete recognizer (UITapGestureRecognizer), bumubuo ito ng pagkakasunod-sunod ng mga kaganapan: .began (dumikit ang mga daliri sa screen), .changed (bawat galaw ng mga daliri), .ended (inangat ang mga daliri).
Mga pangunahing property: scale (CGFloat) — relatibong pagbabago ng distansya sa pagitan ng mga daliri mula sa simula ng kilos; velocity (CGFloat) — bilis ng pagbabago ng scale bawat segundo. Paunang scale = 1.0. Kapag pinalalayo ang mga daliri, tumataas ang scale (1.0 → 1.5 → 2.0), kapag pinagdikit — bumababa (1.0 → 0.8 → 0.5). Ang property na velocity ay ginagamit para sa inertia: pagkatapos ng .ended, maaaring i-animate ang pagpapatuloy ng pag-scale na may pagkupas.
Sinusuportahan ng UIPinchGestureRecognizer ang property na delegate para sa sabay-sabay na paggana sa iba pang recognizer (hal., pan + pinch sa mapa). Ang pamamaraang gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) ay nagbabalik ng true, at parehong kilos ay gumagana nang magkatulad. Inirerekomenda ng Apple na idagdag ang UIPinchGestureRecognizer sa isang container view, hindi sa mga indibidwal na elemento — pinapasimple nito ang pagkalkula ng pivot point ng pag-scale.
ScaleGestureDetector — isang klase mula sa Android Support Library (android.view), na idinisenyo para sa pagkilala ng multi-touch na pag-scale. Hindi tulad ng GestureDetector.onFling, ang ScaleGestureDetector ay espesyal na idinisenyo para sa dalawang-daliring zoom at awtomatikong kinakalkula ang focusX, focusY (gitna sa pagitan ng mga daliri) at scaleFactor (delta ng scale mula sa nakaraang kaganapan).
Ang ScaleGestureDetector ay gumagana sa pamamagitan ng interface na OnScaleGestureListener na may tatlong pamamaraan: onScaleBegin(detector) (katumbas ng .began), onScale(detector) (katumbas ng .changed, nagbabalik ng boolean — true kung tinanggap ang scale), onScaleEnd(detector) (katumbas ng .ended). Mga property: scaleFactor — multiplier ng scale kumpara sa nakaraang kaganapan (~1.01 sa mabagal na pagpalayo), focusX/focusY — mga coordinate ng gitna sa pagitan ng mga daliri.
Para sa paglalapat ng scale sa View, karaniwang ginagamit ang Matrix: view.imageMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY). Sinusuportahan ng Matrix ang akumulasyon ng mga transformasyon — bawat tawag sa postScale ay nagdaragdag ng bagong pagbabago sa umiiral na. Mahalagang tandaan: Hindi nililimitahan ng ScaleGestureDetector ang minimum/maximum na scale — ito ay gawain ng developer. Sa IT Sectr, inilalapat namin ang ScaleGestureDetector na may custom na mga hangganan ng scale (0.5–3.0) at animation ng pagbabalik kapag lumampas.
Sa pagpapatupad ng Pinch-to-Zoom, kritikal na limitahan ang minimum at maximum na scale — kung wala ito, maaaring palakihin ng user ang larawan nang walang hanggan o paliitin ito hanggang sa hindi makita. Mga karaniwang halaga: min = 0.5 (50% ng orihinal na laki), max = 3.0 (300%). Sa iOS, ang limitasyon ay ginagawa sa pamamagitan ng clamp: newScale = min(max(currentScale * gesture.scale, minScale), maxScale). Sa Android — sa pamamagitan ng pagsusuri pagkatapos ng postScale.
Ang pivot point — gitna sa pagitan ng mga daliri kung saan isinasagawa ang pag-scale. Awtomatikong kinakalkula ng iOS ang sentro ng dalawang pagpindot at ipinapadala ito sa pamamagitan ng location(in:) — hindi kailangang kalkulahin ng developer ang pivot nang manu-mano. Ang Android ay nagbibigay ng focusX/focusY sa ScaleGestureDetector, ngunit kapag inilapat sa pamamagitan ng Matrix, ang pivot ay ipinadala bilang ikatlo at ikaapat na argumento sa postScale(scaleFactor, scaleFactor, focusX, focusY). Kung walang pivot point, ang pag-scale ay isasagawa kumpara sa kaliwang sulok sa itaas (0,0), na nagdudulot ng paglipat ng nilalaman sa zoom.
Ayon sa karanasan ng IT Sectr, ang tamang pagkalkula ng pivot point — ang pinakakaraniwang pinagmumulan ng mga bug sa pagpapatupad ng Pinch-to-Zoom. Sa 90% ng mga kaso, sapat na gamitin ang built-in na mga coordinate ng GestureRecognizer at ScaleGestureDetector, nang hindi kinakalkula nang manu-mano ang pivot. Kung lumilipat ang nilalaman sa pag-scale — suriin kung ipinapadala mo ang focusX/focusY sa matrix.postScale().
Para sa maayos na UX pagkatapos ng kilos, maaaring magdagdag ng inertia: sa iOS — gamitin ang velocity mula sa UIPinchGestureRecognizer para sa UIView.animate na may spring parameter; sa Android — OverScroller o ValueAnimator. Ang inertia ay hindi dapat lumampas sa mga hangganan ng min/max scale — ang huling halaga ay pinuputol na may spring na animation ng pagbabalik kapag lumampas.
Nagdaragdag ng UIPinchGestureRecognizer sa UIImageView na may limitasyon ng scale mula 0.5 hanggang 3.0. Ipinapakita ang minimal na implementasyon na may pagpapanatili ng naipong scale.
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
}
}
}
Logic: sa .began/.changed, ang scale ay inilalapat sa pamamagitan ng CGAffineTransform na may clamp. Sa .ended, ang scale ay nireret sa 1.0 at ang naipong halaga ay nai-save sa currentScale. Pinipigilan nito ang "drift" ng scale sa maraming kilos — bawat bagong kilos ay nagsisimula mula sa kasalukuyang estado. Para sa mga larawan, sa halip na CGAffineTransform, inirerekomenda ang UIScrollView na may minimumZoomScale/maximumZoomScale.
Ipinapatupad ang pag-scale ng ImageView sa pamamagitan ng ScaleGestureDetector na may Matrix. Ipinapakita ang tamang pagproseso ng pivot point at limitasyon ng scale.
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
}
}
Ang custom na ImageView ay nag-o-override ng onTouchEvent, na nagpapasa ng mga kaganapan sa ScaleGestureDetector. Ang OnScaleListener.onScale() ay tinatawag sa bawat galaw ng mga daliri. detector.scaleFactor — delta (1.01 sa mabagal na pagpalayo). Ang currentScale ay naiipon na may coerceIn para sa clamp. focusX/focusY — gitna sa pagitan ng mga daliri, ipinapasa sa postScale para sa tamang pivot na pag-scale.
Ang UIScrollView ay may built-in na suporta para sa Pinch-to-Zoom sa pamamagitan ng delegate. Ito ang mas gustong paraan para sa mga larawan at dokumento — awtomatikong pinamamahalaan ng ScrollView ang scale, inertia, at pag-scroll.
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
}
}
Ang UIScrollView ay nakapag-iisa na lumilikha ng UIPinchGestureRecognizer, namamahala ng scale sa pamamagitan ng CALayer transformation, at nagdaragdag ng bounce effect kapag lumampas sa mga hangganan. Ang code ay nababawasan sa pag-set ng min/maxZoomScale at pagbabalik ng viewForZooming. Sa IT Sectr, ginagamit namin ang UIScrollView para sa lahat ng full-screen na larawan — binabawasan nito ang dami ng code ng 5 beses kumpara sa manu-manong implementasyon sa pamamagitan ng UIPinchGestureRecognizer.
Mga madalas itanong
Sa touch screen, ang pinch ay gumagamit ng dalawang daliri na may pagsubaybay sa distansya sa pagitan nila. Sa MacBook trackpad, ang pag-scale ay ipinatupad sa pamamagitan ng UIPinchGestureRecognizer na may parehong mga estado (.began → .changed → .ended), ngunit ang pinagmulan ng mga pagpindot ay ang trackpad na may Force Touch, hindi isang capacitive screen. Ang API ay pareho, ang mekanika ay magkapareho.
Sa iOS: pagkatapos makatanggap ng scale mula sa UIPinchGestureRecognizer, i-multiply sa currentScale at ilapat ang clamp(min: 0.5, max: 3.0). Sa Android: sa pamamagitan ng coerceIn(minScale, maxScale) bago ang matrix.postScale(). Sa UIScrollView — sa pamamagitan ng mga property na minimumZoomScale at maximumZoomScale. Kung walang limitasyon, ang scale ay maaaring pumunta sa infinity o zero.
Hindi, ang pinch ay nangangailangan ng hindi bababa sa dalawang daliri. Para sa isang-daliring zoom (double-tap + pag-drag pataas/pababa), kinakailangan ang custom na implementasyon sa pamamagitan ng UIPanGestureRecognizer o GestureDetector.onScroll. Ito ay ibang kilos na may ibang lohika — karaniwang ginagamit para sa zoom sa camera o mapa.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.