一、前景提示
最近接到一个检查大图的需求,现在图片展示还不够大,要求还要能缩小能扩大还能保存相片。直接开端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的矩阵保存,后续对相片移动,缩放都是经过改换矩阵来完成的。 至于核算两个手指之间的间隔用的勾股定理,来个示意图,咱们就明白了。
核算如下。
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加载后很流畅,猜测是相片巨细问题。可是思路是一样的问题不大。