Əsas məqamlar
Pinch-to-Zoom (çimdik və ya sıxma) — göstərilən məzmunun miqyasını dəyişmək üçün iki barmaqdan istifadə edən multi-touch jestdir. Barmaqları yaxınlaşdırdıqda (pinch close) miqyas azalır, aralandıqda (pinch open) — böyüyür. Jest fasiləsizdir: sistem barmaqlar arasındakı məsafəni real vaxtda izləyir və aralıq hadisələr yaradaraq tətbiqə miqyası hamar şəkildə yeniləməyə imkan verir.
Jestin mexanikası iki toxunma nöqtəsi arasındakı məsafənin hesablanmasına əsaslanır. iOS və Android Evklid məsafəsindən istifadə edir: sqrt((x2-x1)² + (y2-y1)²). Cari scale cari məsafənin ilkin məsafəyə nisbəti kimi hesablanır. İlkin məsafə 100px idisə və 150px oldusa — scale = 1.5 (50% artım). iOS scale-i əvvəlki vəziyyətə nisbətən (delta), Android isə ScaleGestureDetector vasitəsilə yığılmış scale-i ötürür.
Google Material Design Guidelines-ə görə, Pinch-to-Zoom şəkillər, xəritələr və ya sənədlər göstərən tətbiqlər üçün məcburidir. IT Sectr-də bu jesti UIPinchGestureRecognizer və ScaleGestureDetector ilə vizual məzmunla işləyən bütün layihələrdə — qalereyalardan tutmuş şəkil redaktorlarına qədər tətbiq edirik.
UIPinchGestureRecognizer — çimdik jestini tanımaq üçün UIGestureRecognizer-in fasiləsiz alt sinfi. Diskret tanıyıcılardan (UITapGestureRecognizer) fərqli olaraq, hadisələr ardıcıllığı yaradır: .began (barmaqlar ekrana toxundu), .changed (barmaqların hər hərəkəti), .ended (barmaqlar ayrıldı).
Əsas xassələr: scale (CGFloat) — jestin əvvəlindən barmaqlar arasındakı məsafənin nisbi dəyişməsi; velocity (CGFloat) — saniyədə scale dəyişmə sürəti. İlkin scale = 1.0. Barmaqlar aralandıqda scale artır (1.0 → 1.5 → 2.0), yaxınlaşdıqda — azalır (1.0 → 0.8 → 0.5). Velocity xassəsi inersiya üçün istifadə olunur: .ended-dən sonra sönümlə miqyaslandırmanın davamını animasiya etmək olar.
UIPinchGestureRecognizer digər tanıyıcılarla (məsələn, xəritədə pan + pinch) eyni vaxtda işləmək üçün delegate xassəsini dəstəkləyir. gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) metodu true qaytarır və hər iki jest paralel işləyir. Apple UIPinchGestureRecognizer-in ayrı-ayrı elementlərə deyil, konteyner view-ə əlavə edilməsini tövsiyə edir — bu miqyaslandırmanın pivot nöqtəsinin hesablanmasını asanlaşdırır.
ScaleGestureDetector — multi-touch miqyaslandırmanı tanımaq üçün Android Support Library-dən (android.view) sinif. GestureDetector.onFling-dən fərqli olaraq, ScaleGestureDetector xüsusi olaraq iki barmaqlı zoom üçün nəzərdə tutulub və avtomatik olaraq focusX, focusY (barmaqlar arasında mərkəz) və scaleFactor (əvvəlki hadisədən miqyas deltası) hesablayır.
ScaleGestureDetector üç metodlu OnScaleGestureListener interfeysi vasitəsilə işləyir: onScaleBegin(detector) (.began analoqu), onScale(detector) (.changed analoqu, boolean qaytarır — true, miqyas qəbul edilibsə), onScaleEnd(detector) (.ended analoqu). Xassələr: scaleFactor — əvvəlki hadisəyə nisbətən miqyas çarpanı (~1.01 yavaş aralamada), focusX/focusY — barmaqlar arasında mərkəzin koordinatları.
View-ə miqyas tətbiq etmək üçün adətən Matrix istifadə olunur: view.imageMatrix.postScale(scaleFactor, scaleFactor, focusX, focusY). Matrix transformasiyaların yığılmasını dəstəkləyir — hər postScale çağırışı mövcud çevrilməyə yeni çevrilmə əlavə edir. Yadda saxlamaq vacibdir: ScaleGestureDetector minimal/maksimal miqyası məhdudlaşdırmır — bu tərtibatçının vəzifəsidir. IT Sectr-də biz ScaleGestureDetector-u fərdi scale sərhədləri (0.5–3.0) və aşma halında qayıdış animasiyası ilə tətbiq edirik.
Pinch-to-Zoom-un tətbiqində minimal və maksimal miqyası məhdudlaşdırmaq kritik əhəmiyyət daşıyır — bu olmadan istifadəçi şəkli sonsuza qədər böyüdə və ya görünməz hala gələnə qədər kiçildə bilər. Standart dəyərlər: min = 0.5 (orijinal ölçünün 50%-i), max = 3.0 (300%). iOS-da məhdudiyyət clamp vasitəsilə aparılır: newScale = min(max(currentScale * gesture.scale, minScale), maxScale). Android-də — postScale-dən sonra yoxlama ilə.
Pivot nöqtəsi — miqyaslandırmanın aparıldığı barmaqlar arasındakı mərkəz. iOS avtomatik olaraq iki toxunmanın mərkəzini hesablayır və onu location(in:) vasitəsilə ötürür — tərtibatçı pivotu əl ilə hesablamalı deyil. Android ScaleGestureDetector-da focusX/focusY təqdim edir, lakin Matrix vasitəsilə tətbiq edərkən pivot postScale(scaleFactor, scaleFactor, focusX, focusY)-də üçüncü və dördüncü arqument kimi ötürülür. Pivot nöqtəsi olmadan miqyaslandırma yuxarı sol küncə (0,0) nisbətən aparılacaq, bu da zoom zamanı məzmunun yerdəyişməsinə səbəb olur.
IT Sectr təcrübəsinə görə, pivot nöqtəsinin düzgün hesablanması Pinch-to-Zoom-un tətbiqində ən çox rast gəlinən səhv mənbəyidir. 90% hallarda GestureRecognizer və ScaleGestureDetector-un daxili koordinatlarından istifadə etmək kifayətdir, pivotu əl ilə hesablamadan. Əgər məzmun miqyaslandırma zamanı yerdəyişirsə — focusX/focusY-ni matrix.postScale()-ə ötürdüyünüzü yoxlayın.
Hamar UX üçün jest bitdikdən sonra inersiya əlavə etmək olar: iOS-da — UIPinchGestureRecognizer-dən velocity-ni spring parametrləri ilə UIView.animate üçün istifadə edin; Android-də — OverScroller və ya ValueAnimator. İnersiya min/max scale sərhədlərindən kənara çıxmamalıdır — son qiymət aşma zamanı spring qayıdış animasiyası ilə kəsilir.
UIImageView-ə miqyas məhdudiyyəti 0.5-dən 3.0-dək olan UIPinchGestureRecognizer əlavə edir. Yığılmış miqyasın saxlanması ilə minimal tətbiqi göstərir.
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
}
}
}
Məntiq: .began/.changed-də miqyas CGAffineTransform ilə clamp tətbiq edilir. .ended-də scale 1.0-a sıfırlanır və yığılmış dəyər currentScale-də saxlanılır. Bu çoxsaylı jestlər zamanı miqyasın „sürüşməsinin" qarşısını alır — hər yeni jest cari vəziyyətdən başlayır. Şəkillər üçün CGAffineTransform əvəzinə minimumZoomScale/maximumZoomScale ilə UIScrollView tövsiyə olunur.
Matrix ilə ScaleGestureDetector vasitəsilə ImageView-in miqyaslandırılmasını tətbiq edir. Pivot nöqtəsinin düzgün işlənməsini və miqyas məhdudiyyətini göstərir.
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
}
}
Fərdi ImageView onTouchEvent-i ləğv edərək hadisələri ScaleGestureDetector-a ötürür. OnScaleListener.onScale() barmaqların hər hərəkətində çağırılır. detector.scaleFactor — delta (yavaş aralamada 1.01). currentScale clamp üçün coerceIn ilə yığılır. focusX/focusY — barmaqlar arasında mərkəz, düzgün pivot miqyaslandırması üçün postScale-ə ötürülür.
UIScrollView-in delegate vasitəsilə daxili Pinch-to-Zoom dəstəyi var. Bu şəkillər və sənədlər üçün üstünlük verilən üsuldur — ScrollView miqyası, inersiyanı və sürüşdürməni avtomatik idarə edir.
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 müstəqil olaraq UIPinchGestureRecognizer yaradır, CALayer transformasiyası vasitəsilə miqyası idarə edir və sərhədləri aşdıqda bounce effekti əlavə edir. Kod min/maxZoomScale-in qurulmasına və viewForZooming-in qaytarılmasına qədər azalır. IT Sectr-də bütün tam ekran şəkilləri üçün UIScrollView istifadə edirik — bu, UIPinchGestureRecognizer ilə əl ilə tətbiq ilə müqayisədə kodu 5 dəfə azaldır.
Tez-tez verilən suallar
Sensor ekranda pinch aralarındakı məsafəni izləməklə iki barmaqdan istifadə edir. MacBook trekpaddında miqyaslandırma eyni hallarla (.began → .changed → .ended) UIPinchGestureRecognizer vasitəsilə həyata keçirilir, lakin toxunma mənbəyi tutumlu ekran deyil, Force Touch ilə trekpaddır. API eynidir, mexanika eynidir.
iOS-da: UIPinchGestureRecognizer-dən scale aldıqdan sonra currentScale-ə vurun və clamp(min: 0.5, max: 3.0) tətbiq edin. Android-də: matrix.postScale() qabağı coerceIn(minScale, maxScale) vasitəsilə. UIScrollView-də — minimumZoomScale və maximumZoomScale xassələri ilə. Məhdudiyyət olmadan miqyas sonsuzluğa və ya sıfıra gedə bilər.
Xeyr, pinch ən azı iki barmaq tələb edir. Bir barmaqlı zoom (double-tap + yuxarı/aşağı sürüşdürmə) üçün UIPanGestureRecognizer və ya GestureDetector.onScroll vasitəsilə fərdi tətbiq tələb olunur. Bu fərqli məntiqə malik başqa bir jestdir — adətən kamerada və ya xəritələrdə zoom üçün istifadə olunur.
Nəticə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun