一、前景提示

最近接到一个检查大图的需求,现在图片展示还不够大,要求还要能缩小能扩大还能保存相片。直接开端Google完成方法。

二、完成功用

依据查询到的结果分为两种,一个是运用手势监听来完成,第二种监听接触事情来完成

  • 手势监听– ScaleGestureDetector Google提供的手势监听类
  • 接触事情–OnTouchListener 自己监听接触事情自己完成扩大缩小的逻辑

2.1 手势监听

先写布局文件

<?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>

再去完成手势监听方法

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去监听接触事情,手势实质也是Google内部监听事情判别再回调给咱们运用。当然咱们这儿不去检查源码,只看完成过程。 在运用过程中发现这种缩放并不滑润,并且呼应有点慢,有推迟。猜测内部是由很多其他的判别吧。那咱们只想简略一点怎样搞呢,那便是自己去判别缩放,还有完成单指滑动用手势也不太好完成的样子。所以咱们试试第二种方法完成也便是接触事情。

2.2 接触事情

首先咱们完成一下缩放,咱们仍是沿用上次运用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就证明是缩放操作,设置成咱们自己界说的模式,再把imageView的矩阵保存,后续对相片移动,缩放都是经过改换矩阵来完成的。 至于核算两个手指之间的间隔用的勾股定理,来个示意图,咱们就明白了。

Android 实现单指滑动、双指缩放照片

核算如下。

 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)
    }

经过核算能得到直角边和邻边,对他们运用勾股定理就能得到斜边的值,也便是两个手指之间的间隔。 有做过接触事情监听的同学就应该知道,咱们下一步要监听移动事情了也便是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,就开端核算缩放倍数,经过postScale进行矩阵改换。 在MotionEvent.ACTION_POINTER_UP事情中对mode值进行复位操作,毕竟还有个单指拖动操作。 假如咱们把上面的代码运行过就会发现怎样图片没有居中显现,这是因为咱们的缩放特点被改为矩阵也便是android:scaleType=”matrix”,那么想要图片居中显现怎样操作呢,只需求在接触时去改变缩放特点,其他的时候不变即可。 咱们把imageView康复成android:scaleType=”fitCenter”,在onTouchListener中加入(放在when前即可)

mImageView.scaleType = ImageView.ScaleType.MATRIX

这样一开端就能够坚持图片在中心了。 这样缩放功用完成了,下面完成单指拖动功用,思路很简略记载第一次按下的方位,在移动过程中核算应该需求偏移的间隔,再记载下当时的方位,以便于下次核算。

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
    }
}

代码完成和思路一样,咱们还需求在MotionEvent.ACTION_UP中复位模式,调用postTranslate进行偏移。 这样基本上功用咱们都简略完成了。下面咱们就需求优化了代码,假如各位跟着完成了,就会发现缩放倍数太大了导致悄悄动一下就会放很大,还有别的都是需求咱们优化的。

三、功用优化

3.1 优化缩放倍数太大问题

其实这个问题和咱们处理move事情有关系,熟悉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事情中咱们这样处理的,核算缩放倍数然后缩放,大体一看是没有什么问题的。可是,咱们的move事情不止履行一次,这就导致咱们的缩放不止履行一次,每次都是在本来的基础上扩大或许缩小。所以悄悄移动倍数就会很多。 最简略的方法便是咱们记载一下move过程中累计的倍数,假如到达最大值或许最小值就不让扩大或许缩小了。代码如下。

if (scale > 1.0f) {
	sumScale += scale
} else {
	sumScale -= scale
}
if (sumScale >= maxScale || sumScale <= minScale) {
	return@setOnTouchListener true
}

简略可是有用的方法。其中max和min,能够自己赋值。

3.2 坚持原图不缩小

完成起来也很简略,需求先界说一个变量记载当时缩放之后的倍数。咱们测验就会发现,假如是扩大操作那么倍数就会大于1假如是缩小倍数就会比1 小。咱们就能够使用这点来处理咱们的逻辑。

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加载后很流畅,猜测是相片巨细问题。可是思路是一样的问题不大。