一、前景提示
最近接到一個查看大圖的需求,現(xiàn)在圖片展示還不夠大,要求還要能縮小能放大還能保存照片。直接開始Google實現(xiàn)方式。
二、實現(xiàn)功能
根據(jù)查詢到的結(jié)果分為兩種,一個是使用手勢監(jiān)聽來實現(xiàn),第二種監(jiān)聽觸摸事件來實現(xiàn)
- 手勢監(jiān)聽-- ScaleGestureDetector Google提供的手勢監(jiān)聽類
- 觸摸事件–OnTouchListener 自己監(jiān)聽觸摸事件自己實現(xiàn)放大縮小的邏輯
2.1 手勢監(jiān)聽
先寫布局文件
<?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>
再去實現(xiàn)手勢監(jiān)聽方法
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
}
}
}
代碼很簡單直接使用ScaleGestureDetector去監(jiān)聽觸摸事件,手勢本質(zhì)也是Google內(nèi)部監(jiān)聽事件判斷再回調(diào)給我們使用。當(dāng)然我們這里不去查看源碼,只看實現(xiàn)過程。
在使用過程中發(fā)現(xiàn)這種縮放并不平滑,而且響應(yīng)有點慢,有延遲。猜想內(nèi)部是由很多其他的判斷吧。那我們只想簡單一點怎么搞呢,那就是自己去判斷縮放,還有實現(xiàn)單指滑動用手勢也不太好實現(xiàn)的樣子。所以我們試試第二種方式實現(xiàn)也就是觸摸事件。
2.2 觸摸事件
首先我們實現(xiàn)一下縮放,我們還是沿用上次使用onTouchListener來處理我們的觸摸事件,布局文件中需要把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
}
沒有自己處理過觸摸事件的小伙伴可能會好奇MotionEvent.ACTION_MASK是什么,其實這個是為了處理多點觸摸事件加的一個flag和action做and操作,我們就能處理ACTION_POINTER_DOWN和ACTION_POINTER_UP這兩個多點觸摸事件。
看下代碼邏輯,我們先計算兩個手指的距離,如果距離大于10就證明是縮放操作,設(shè)置成我們自己定義的模式,再把imageView的矩陣保存,后續(xù)對照片移動,縮放都是通過變換矩陣來實現(xiàn)的。
至于計算兩個手指之間的距離用的勾股定理,來個示意圖,大家就明白了。
計算如下。
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)
}
通過計算能得到直角邊和鄰邊,對他們使用勾股定理就能得到斜邊的值,也就是兩個手指之間的距離。
有做過觸摸事件監(jiān)聽的同學(xué)就應(yīng)該知道,我們下一步要監(jiān)聽移動事件了也就是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事件中我們也需要對手指之間的距離進行計算,如果距離超過10,就開始計算縮放倍數(shù),通過postScale進行矩陣變換。
在MotionEvent.ACTION_POINTER_UP事件中對mode值進行復(fù)位操作,畢竟還有個單指拖動操作。
如果大家把上面的代碼運行過就會發(fā)現(xiàn)怎么圖片沒有居中顯示,這是因為我們的縮放屬性被改為矩陣也就是android:scaleType=“matrix”,那么想要圖片居中顯示怎么操作呢,只需要在觸摸時去改變縮放屬性,其他的時候不變即可。
我們把imageView恢復(fù)成android:scaleType=“fitCenter”,在onTouchListener中加入(放在when前即可)
mImageView.scaleType = ImageView.ScaleType.MATRIX
這樣一開始就可以保持圖片在中央了。
這樣縮放功能實現(xiàn)了,下面實現(xiàn)單指拖動功能,思路很簡單記錄第一次按下的位置,在移動過程中計算應(yīng)該需要偏移的距離,再記錄下當(dāng)前的位置,以便于下次計算。
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) {
// 單指拖動
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
}
}
代碼實現(xiàn)和思路一樣,我們還需要在MotionEvent.ACTION_UP中復(fù)位模式,調(diào)用postTranslate進行偏移。
這樣基本上功能我們都簡單實現(xiàn)了。下面我們就需要優(yōu)化了代碼,如果各位跟著實現(xiàn)了,就會發(fā)現(xiàn)縮放倍數(shù)太大了導(dǎo)致輕輕動一下就會放很大,還有別的都是需要我們優(yōu)化的。
三、功能優(yōu)化
3.1 優(yōu)化縮放倍數(shù)太大問題
其實這個問題和我們處理move事件有關(guān)系,熟悉Android事件機制都知道一個完整的事件流程就是down->move…move->up。知道了這個之后,再仔細看我們的代碼
val currentDistance = getDistance(event)
if (currentDistance > 10f) {
val scale = currentDistance / startDistance
mImageView.imageMatrix = startMatrix.apply {
postScale(scale, scale, getMidX(event), getMidY(event))
}
}
在move事件中我們這樣處理的,計算縮放倍數(shù)然后縮放,大體一看是沒有什么問題的。**但是,我們的move事件不止執(zhí)行一次,這就導(dǎo)致我們的縮放不止執(zhí)行一次,每次都是在原來的基礎(chǔ)上放大或者縮小。**所以輕輕移動倍數(shù)就會很多。
最簡單的辦法就是我們記錄一下move過程中累計的倍數(shù),如果到達最大值或者最小值就不讓放大或者縮小了。代碼如下。
if (scale > 1.0f) {
sumScale += scale
} else {
sumScale -= scale
}
if (sumScale >= maxScale || sumScale <= minScale) {
return@setOnTouchListener true
}
簡單但是有效的方式。其中max和min,可以自己賦值。
3.2 保持原圖不縮小
實現(xiàn)起來也很簡單,需要先定義一個變量記錄當(dāng)前縮放之后的倍數(shù)。大家測試就會發(fā)現(xiàn),如果是放大操作那么倍數(shù)就會大于1如果是縮小倍數(shù)就會比1 小。我們就可以利用這點來處理我們的邏輯。文章來源:http://www.zghlxwxcb.cn/news/detail-679093.html
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在這里點我點我
tips:demo好像不是放大不是很順暢,但是在項目里用Gilde加載后很流暢,猜測是照片大小問題。但是思路是一樣的問題不大。文章來源地址http://www.zghlxwxcb.cn/news/detail-679093.html
到了這里,關(guān)于Android 實現(xiàn)單指滑動、雙指縮放照片的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!