Android?實(shí)現(xiàn)單指滑動(dòng)雙指縮放照片demo及過(guò)程解析
一、前景提示
最近接到一個(gè)查看大圖的需求,現(xiàn)在圖片展示還不夠大,要求還要能縮小能放大還能保存照片。直接開(kāi)始Google實(shí)現(xiàn)方式。
二、實(shí)現(xiàn)功能
根據(jù)查詢到的結(jié)果分為兩種,一個(gè)是使用手勢(shì)監(jiān)聽(tīng)來(lái)實(shí)現(xiàn),第二種監(jiān)聽(tīng)觸摸事件來(lái)實(shí)現(xiàn)
- 手勢(shì)監(jiān)聽(tīng)-- ScaleGestureDetector Google提供的手勢(shì)監(jiān)聽(tīng)類
- 觸摸事件--OnTouchListener 自己監(jiān)聽(tīng)觸摸事件自己實(shí)現(xiàn)放大縮小的邏輯
2.1 手勢(shì)監(jiān)聽(tīng)
先寫(xiě)布局文件
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <androidx.appcompat.widget.AppCompatImageView android:id="@+id/iv_example" android:layout_width="match_parent" android:layout_height="match_parent" android:text="Hello World!" android:scaleType="fitCenter" android:src="@drawable/muffin_7870491_1920" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
再去實(shí)現(xiàn)手勢(shì)監(jiān)聽(tīng)方法
class MainActivity : AppCompatActivity() { private lateinit var mScaleGestureDetector: ScaleGestureDetector private var mScaleFactor: Float = 1.0f private lateinit var mImageView: AppCompatImageView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) mImageView = findViewById(R.id.iv_example) mScaleGestureDetector = ScaleGestureDetector(this, ScaleGestureListener()) mImageView.setOnTouchListener { _, event -> mScaleGestureDetector.onTouchEvent(event) true } } private inner class ScaleGestureListener : ScaleGestureDetector.SimpleOnScaleGestureListener() { override fun onScale(detector: ScaleGestureDetector): Boolean { mScaleFactor *= detector.scaleFactor // 限制縮放因子在0.1到10.0 mScaleFactor = mScaleFactor.coerceIn(0.1f, 10.0f) mImageView.scaleX = mScaleFactor mImageView.scaleY = mScaleFactor return true } } }
代碼很簡(jiǎn)單直接使用ScaleGestureDetector去監(jiān)聽(tīng)觸摸事件,手勢(shì)本質(zhì)也是Google內(nèi)部監(jiān)聽(tīng)事件判斷再回調(diào)給我們使用。當(dāng)然我們這里不去查看源碼,只看實(shí)現(xiàn)過(guò)程。 在使用過(guò)程中發(fā)現(xiàn)這種縮放并不平滑,而且響應(yīng)有點(diǎn)慢,有延遲。猜想內(nèi)部是由很多其他的判斷吧。那我們只想簡(jiǎn)單一點(diǎn)怎么搞呢,那就是自己去判斷縮放,還有實(shí)現(xiàn)單指滑動(dòng)用手勢(shì)也不太好實(shí)現(xiàn)的樣子。所以我們?cè)囋嚨诙N方式實(shí)現(xiàn)也就是觸摸事件。
2.2 觸摸事件
首先我們實(shí)現(xiàn)一下縮放,我們還是沿用上次使用onTouchListener來(lái)處理我們的觸摸事件,布局文件中需要把imageView的縮放屬性改為矩陣 android:scaleType="matrix"
private var startMatrix = Matrix() mImageView.setOnTouchListener { _, event -> when(event.action and MotionEvent.ACTION_MASK) { MotionEvent.ACTION_POINTER_DOWN -> { // 記錄雙指按下的位置和距離 startDistance = getDistance(event) if (startDistance > 10f) { startMatrix.set(mImageView.imageMatrix) mode = 2 } return@setOnTouchListener true } } true }
沒(méi)有自己處理過(guò)觸摸事件的小伙伴可能會(huì)好奇MotionEvent.ACTION_MASK是什么,其實(shí)這個(gè)是為了處理多點(diǎn)觸摸事件加的一個(gè)flag和action做and操作,我們就能處理ACTION_POINTER_DOWN和ACTION_POINTER_UP這兩個(gè)多點(diǎn)觸摸事件。 看下代碼邏輯,我們先計(jì)算兩個(gè)手指的距離,如果距離大于10就證明是縮放操作,設(shè)置成我們自己定義的模式,再把imageView的矩陣保存,后續(xù)對(duì)照片移動(dòng),縮放都是通過(guò)變換矩陣來(lái)實(shí)現(xiàn)的。 至于計(jì)算兩個(gè)手指之間的距離用的勾股定理,來(lái)個(gè)示意圖,大家就明白了。
計(jì)算如下。
private fun getDistance(event: MotionEvent): Float { val dx = event.getX(0) - event.getX(1) val dy = event.getY(0) - event.getY(1) return sqrt(dx * dx + dy * dy) }
通過(guò)計(jì)算能得到直角邊和鄰邊,對(duì)他們使用勾股定理就能得到斜邊的值,也就是兩個(gè)手指之間的距離。 有做過(guò)觸摸事件監(jiān)聽(tīng)的同學(xué)就應(yīng)該知道,我們下一步要監(jiān)聽(tīng)移動(dòng)事件了也就是MotionEvent.ACTION_MOVE。
mImageView.setOnTouchListener { _, event -> when (event.action and MotionEvent.ACTION_MASK) { MotionEvent.ACTION_POINTER_DOWN -> { // 記錄雙指按下的位置和距離 startDistance = getDistance(event) if (startDistance > 10f) { startMatrix.set(mImageView.imageMatrix) mode = 2 } return@setOnTouchListener true } MotionEvent.ACTION_MOVE -> { if (mode == 2) { // 雙指縮放 val currentDistance = getDistance(event) if (currentDistance > 10f) { val scale = currentDistance / startDistance mImageView.imageMatrix = startMatrix.apply { postScale(scale, scale, getMidX(event), getMidY(event)) } } } return@setOnTouchListener true } MotionEvent.ACTION_POINTER_UP -> { mode = 0 return@setOnTouchListener true } else -> return@setOnTouchListener true } }
這里在move事件中我們也需要對(duì)手指之間的距離進(jìn)行計(jì)算,如果距離超過(guò)10,就開(kāi)始計(jì)算縮放倍數(shù),通過(guò)postScale進(jìn)行矩陣變換。 在MotionEvent.ACTION_POINTER_UP事件中對(duì)mode值進(jìn)行復(fù)位操作,畢竟還有個(gè)單指拖動(dòng)操作。 如果大家把上面的代碼運(yùn)行過(guò)就會(huì)發(fā)現(xiàn)怎么圖片沒(méi)有居中顯示,這是因?yàn)槲覀兊目s放屬性被改為矩陣也就是android:scaleType="matrix",那么想要圖片居中顯示怎么操作呢,只需要在觸摸時(shí)去改變縮放屬性,其他的時(shí)候不變即可。 我們把imageView恢復(fù)成android:scaleType="fitCenter",在onTouchListener中加入(放在when前即可)
mImageView.scaleType = ImageView.ScaleType.MATRIX
這樣一開(kāi)始就可以保持圖片在中央了。 這樣縮放功能實(shí)現(xiàn)了,下面實(shí)現(xiàn)單指拖動(dòng)功能,思路很簡(jiǎn)單記錄第一次按下的位置,在移動(dòng)過(guò)程中計(jì)算應(yīng)該需要偏移的距離,再記錄下當(dāng)前的位置,以便于下次計(jì)算。
private var lastX = 0f private var lastY = 0f mImageView.setOnTouchListener { _, event -> mImageView.scaleType = ImageView.ScaleType.MATRIX when (event.action and MotionEvent.ACTION_MASK) { MotionEvent.ACTION_DOWN -> { // 記錄單指按下的位置 lastX = event.x lastY = event.y mode = 1 startMatrix.set(mImageView.imageMatrix) return@setOnTouchListener true } MotionEvent.ACTION_POINTER_DOWN -> { // 記錄雙指按下的位置和距離 startDistance = getDistance(event) if (startDistance > 10f) { startMatrix.set(mImageView.imageMatrix) mode = 2 } return@setOnTouchListener true } MotionEvent.ACTION_MOVE -> { if (mode == 1) { // 單指拖動(dòng) val dx = event.x - lastX val dy = event.y - lastY mImageView.imageMatrix = startMatrix.apply { postTranslate(dx, dy) } lastX = event.x lastY = event.y } else if (mode == 2) { // 雙指縮放 val currentDistance = getDistance(event) if (currentDistance > 10f) { val scale = currentDistance / startDistance mImageView.imageMatrix = startMatrix.apply { postScale(scale, scale, getMidX(event), getMidY(event)) } } } return@setOnTouchListener true } MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> { mode = 0 return@setOnTouchListener true } else -> return@setOnTouchListener true } }
代碼實(shí)現(xiàn)和思路一樣,我們還需要在MotionEvent.ACTION_UP中復(fù)位模式,調(diào)用postTranslate進(jìn)行偏移。 這樣基本上功能我們都簡(jiǎn)單實(shí)現(xiàn)了。下面我們就需要優(yōu)化了代碼,如果各位跟著實(shí)現(xiàn)了,就會(huì)發(fā)現(xiàn)縮放倍數(shù)太大了導(dǎo)致輕輕動(dòng)一下就會(huì)放很大,還有別的都是需要我們優(yōu)化的。
三、功能優(yōu)化
3.1 優(yōu)化縮放倍數(shù)太大問(wèn)題
其實(shí)這個(gè)問(wèn)題和我們處理move事件有關(guān)系,熟悉Android事件機(jī)制都知道一個(gè)完整的事件流程就是down->move.....move->up。知道了這個(gè)之后,再仔細(xì)看我們的代碼
val currentDistance = getDistance(event) if (currentDistance > 10f) { val scale = currentDistance / startDistance mImageView.imageMatrix = startMatrix.apply { postScale(scale, scale, getMidX(event), getMidY(event)) } }
在move事件中我們這樣處理的,計(jì)算縮放倍數(shù)然后縮放,大體一看是沒(méi)有什么問(wèn)題的。但是,我們的move事件不止執(zhí)行一次,這就導(dǎo)致我們的縮放不止執(zhí)行一次,每次都是在原來(lái)的基礎(chǔ)上放大或者縮小。所以輕輕移動(dòng)倍數(shù)就會(huì)很多。 最簡(jiǎn)單的辦法就是我們記錄一下move過(guò)程中累計(jì)的倍數(shù),如果到達(dá)最大值或者最小值就不讓放大或者縮小了。代碼如下。
if (scale > 1.0f) { sumScale += scale } else { sumScale -= scale } if (sumScale >= maxScale || sumScale <= minScale) { return@setOnTouchListener true }
簡(jiǎn)單但是有效的方式。其中max和min,可以自己賦值。
3.2 保持原圖不縮小
實(shí)現(xiàn)起來(lái)也很簡(jiǎn)單,需要先定義一個(gè)變量記錄當(dāng)前縮放之后的倍數(shù)。大家測(cè)試就會(huì)發(fā)現(xiàn),如果是放大操作那么倍數(shù)就會(huì)大于1如果是縮小倍數(shù)就會(huì)比1 小。我們就可以利用這點(diǎn)來(lái)處理我們的邏輯。
private var lastScaleFactor = 1f if (scale * lastScaleFactor > 1.0f) { if (sumScale >= maxScale || sumScale <= minScale) { return@setOnTouchListener true } sumScale += scale mImageView.imageMatrix = startMatrix.apply { postScale(scale, scale, getMidX(event), getMidY(event)) lastScaleFactor *= scale } } else { sumScale -= scale }
demo在這里點(diǎn)我點(diǎn)我
tips:demo好像不是放大不是很順暢,但是在項(xiàng)目里用Gilde加載后很流暢,猜測(cè)是照片大小問(wèn)題。但是思路是一樣的問(wèn)題不大。
以上就是Android 實(shí)現(xiàn)單指滑動(dòng)雙指縮放照片demo及過(guò)程解析的詳細(xì)內(nèi)容,更多關(guān)于Android單指滑動(dòng)雙指縮放的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android開(kāi)發(fā)獲取短信的內(nèi)容并截取短信
本文給大家介紹android開(kāi)發(fā)獲取短信內(nèi)容并截取短息的相關(guān)內(nèi)容,本文代碼簡(jiǎn)單易懂,感興趣的朋友一起學(xué)習(xí)吧2015-12-12強(qiáng)制去除Unity自動(dòng)添加的Android隱私權(quán)限
大家好,本篇文章主要講的是強(qiáng)制去除Unity自動(dòng)添加的Android隱私權(quán)限,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽2021-12-12Flutter 實(shí)現(xiàn)進(jìn)度條效果
在一些上傳頁(yè)面炫酷的進(jìn)度條效果都是怎么實(shí)現(xiàn)的,今天小編通過(guò)本文給大家分享Flutter 一行代碼快速實(shí)現(xiàn)你的進(jìn)度條效果,感興趣的朋友一起看看吧2020-05-05android的got表HOOK實(shí)現(xiàn)代碼
對(duì)于android的so文件的hook根據(jù)ELF文件特性分為:Got表hook、Sym表hook和inline hook等。今天通過(guò)本文給大家介紹android HOOK實(shí)現(xiàn)got表的實(shí)例代碼,需要的朋友參考下吧2021-08-08Android中Activity跳轉(zhuǎn)的創(chuàng)建步驟總結(jié)
這篇文章主要介紹了Android中Activity跳轉(zhuǎn)的創(chuàng)建步驟總結(jié),本文詳細(xì)的講解了從工程創(chuàng)建到跳轉(zhuǎn)Activity的實(shí)現(xiàn)完整過(guò)程,需要的朋友可以參考下2014-10-10Android基于Sensor感應(yīng)器獲取重力感應(yīng)加速度的方法
這篇文章主要介紹了Android基于Sensor感應(yīng)器獲取重力感應(yīng)加速度的方法,涉及Android使用Sensor類實(shí)現(xiàn)感應(yīng)重力變化的功能,需要的朋友可以參考下2015-12-12Android UI設(shè)計(jì)與開(kāi)發(fā)之PopupWindow仿騰訊新聞底部彈出菜單
這篇文章主要為大家詳細(xì)介紹了Android UI設(shè)計(jì)與開(kāi)發(fā)之PopupWindow仿騰訊新聞底部彈出菜單,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08