双击缩放使图像向图像中心缩放
Double tap to Zoom makes the Image Zoom towards the center of the Image
我目前已经实现了一个自定义 class,它实现了 AppCompatImageView、OnGestureListener 和 OnDoubleTapListener,以构建我自己的 ImageView,它具有双指缩放、双指缩放 in/out 在这篇文章的帮助下 https://daveson.medium.com/android-imageview-double-tap-and-pinch-zoom-with-multi-touch-gestures-in-kotlin-1559a5dd4a69
我在这里遗漏的是,当用户双击图像的一角时,默认情况下图像会向图像中心缩放。如何确保双击考虑到点击坐标以向该坐标缩放。
这是我的 TouchImageView class
class TouchImageView : androidx.appcompat.widget.AppCompatImageView, GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {
var matrix1: Matrix? = null
var mode = NONE
// Remember some things for zooming
var last = PointF()
var start = PointF()
var minScale = 1f
var maxScale = 3f
var m: FloatArray? = null
var viewWidth = 0
var viewHeight = 0
var saveScale = 1f
protected var origWidth = 0f
protected var origHeight = 0f
var oldMeasuredWidth = 0
var oldMeasuredHeight = 0
var mScaleDetector: ScaleGestureDetector? = null
// var context: Context? = null
var context1 : Context? = null
constructor(context: Context) : super(context) {
sharedConstructing(context)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
sharedConstructing(context)
}
var mGestureDetector: GestureDetector? = null
private fun sharedConstructing(context: Context) {
super.setClickable(true)
this.context1 = context
mGestureDetector = GestureDetector(context, this)
mGestureDetector!!.setOnDoubleTapListener(this)
mScaleDetector = ScaleGestureDetector(context, ScaleListener())
matrix1 = Matrix()
m = FloatArray(9)
imageMatrix = matrix1
scaleType = ScaleType.MATRIX
setOnTouchListener { v, event ->
mScaleDetector!!.onTouchEvent(event)
mGestureDetector!!.onTouchEvent(event)
val curr = PointF(event.x, event.y)
when (event.action) {
MotionEvent.ACTION_DOWN -> {
last.set(curr)
start.set(last)
mode = DRAG
}
MotionEvent.ACTION_MOVE -> if (mode == DRAG) {
val deltaX = curr.x - last.x
val deltaY = curr.y - last.y
val fixTransX = getFixDragTrans(
deltaX, viewWidth.toFloat(),
origWidth * saveScale
)
val fixTransY = getFixDragTrans(
deltaY, viewHeight.toFloat(),
origHeight * saveScale
)
matrix1!!.postTranslate(fixTransX, fixTransY)
fixTrans()
last[curr.x] = curr.y
}
MotionEvent.ACTION_UP -> {
mode = NONE
val xDiff = Math.abs(curr.x - start.x).toInt()
val yDiff = Math.abs(curr.y - start.y).toInt()
if (xDiff < CLICK && yDiff < CLICK) performClick()
}
MotionEvent.ACTION_POINTER_UP -> mode = NONE
}
imageMatrix = matrix1
invalidate()
true // indicate event was handled
}
}
fun setMaxZoom(x: Float) {
maxScale = x
}
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
return false
}
override fun onDoubleTap(e: MotionEvent): Boolean {
// Double tap is detected
Log.i("MAIN_TAG", "Double tap detected")
val origScale = saveScale
val mScaleFactor: Float
if (saveScale == maxScale) {
saveScale = minScale
mScaleFactor = minScale / origScale
} else {
saveScale = maxScale
mScaleFactor = maxScale / origScale
}
matrix1!!.postScale(
mScaleFactor, mScaleFactor, (viewWidth / 2).toFloat(), (
viewHeight / 2).toFloat()
)
fixTrans()
return false
}
override fun onDoubleTapEvent(e: MotionEvent): Boolean {
return false
}
override fun onDown(e: MotionEvent): Boolean {
return false
}
override fun onShowPress(e: MotionEvent) {}
override fun onSingleTapUp(e: MotionEvent): Boolean {
return false
}
override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
return false
}
override fun onLongPress(e: MotionEvent) {}
override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
return false
}
private inner class ScaleListener : SimpleOnScaleGestureListener() {
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
mode = ZOOM
return true
}
override fun onScale(detector: ScaleGestureDetector): Boolean {
var mScaleFactor = detector.scaleFactor
val origScale = saveScale
saveScale *= mScaleFactor
if (saveScale > maxScale) {
saveScale = maxScale
mScaleFactor = maxScale / origScale
} else if (saveScale < minScale) {
saveScale = minScale
mScaleFactor = minScale / origScale
}
if (origWidth * saveScale <= viewWidth
|| origHeight * saveScale <= viewHeight
) matrix1!!.postScale(
mScaleFactor, mScaleFactor, (viewWidth / 2).toFloat(), (
viewHeight / 2).toFloat()
) else matrix1!!.postScale(
mScaleFactor, mScaleFactor,
detector.focusX, detector.focusY
)
fixTrans()
return true
}
}
fun fixTrans() {
matrix1!!.getValues(m)
val transX = m!![Matrix.MTRANS_X]
val transY = m!![Matrix.MTRANS_Y]
val fixTransX = getFixTrans(transX, viewWidth.toFloat(), origWidth * saveScale)
val fixTransY = getFixTrans(
transY, viewHeight.toFloat(), origHeight
* saveScale
)
if (fixTransX != 0f || fixTransY != 0f) matrix1!!.postTranslate(fixTransX, fixTransY)
}
fun getFixTrans(trans: Float, viewSize: Float, contentSize: Float): Float {
val minTrans: Float
val maxTrans: Float
if (contentSize <= viewSize) {
minTrans = 0f
maxTrans = viewSize - contentSize
} else {
minTrans = viewSize - contentSize
maxTrans = 0f
}
if (trans < minTrans) return -trans + minTrans
return if (trans > maxTrans) -trans + maxTrans else 0f
}
private fun getFixDragTrans(delta: Float, viewSize: Float, contentSize: Float): Float {
return if (contentSize <= viewSize) {
0f
} else delta
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
viewWidth = MeasureSpec.getSize(widthMeasureSpec)
viewHeight = MeasureSpec.getSize(heightMeasureSpec)
//
// Rescales image on rotation
//
if (oldMeasuredHeight == viewWidth && oldMeasuredHeight == viewHeight || viewWidth == 0 || viewHeight == 0) return
oldMeasuredHeight = viewHeight
oldMeasuredWidth = viewWidth
if (saveScale == 1f) {
// Fit to screen.
val scale: Float
val drawable = drawable
if (drawable == null || drawable.intrinsicWidth == 0 || drawable.intrinsicHeight == 0) return
val bmWidth = drawable.intrinsicWidth
val bmHeight = drawable.intrinsicHeight
Log.d("bmSize", "bmWidth: $bmWidth bmHeight : $bmHeight")
val scaleX = viewWidth.toFloat() / bmWidth.toFloat()
val scaleY = viewHeight.toFloat() / bmHeight.toFloat()
scale = Math.min(scaleX, scaleY)
matrix1!!.setScale(scale, scale)
// Center the image
var redundantYSpace = (viewHeight.toFloat()
- scale * bmHeight.toFloat())
var redundantXSpace = (viewWidth.toFloat()
- scale * bmWidth.toFloat())
redundantYSpace /= 2.toFloat()
redundantXSpace /= 2.toFloat()
matrix1!!.postTranslate(redundantXSpace, redundantYSpace)
origWidth = viewWidth - 2 * redundantXSpace
origHeight = viewHeight - 2 * redundantYSpace
imageMatrix = matrix1
}
fixTrans()
}
companion object {
// We can be in one of these 3 states
const val NONE = 0
const val DRAG = 1
const val ZOOM = 2
const val CLICK = 3
}
}
你试过了吗https://github.com/MikeOrtiz/TouchImageView
我可以想象它具有您需要的所有功能并且维护得很好
我目前已经实现了一个自定义 class,它实现了 AppCompatImageView、OnGestureListener 和 OnDoubleTapListener,以构建我自己的 ImageView,它具有双指缩放、双指缩放 in/out 在这篇文章的帮助下 https://daveson.medium.com/android-imageview-double-tap-and-pinch-zoom-with-multi-touch-gestures-in-kotlin-1559a5dd4a69
我在这里遗漏的是,当用户双击图像的一角时,默认情况下图像会向图像中心缩放。如何确保双击考虑到点击坐标以向该坐标缩放。
这是我的 TouchImageView class
class TouchImageView : androidx.appcompat.widget.AppCompatImageView, GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {
var matrix1: Matrix? = null
var mode = NONE
// Remember some things for zooming
var last = PointF()
var start = PointF()
var minScale = 1f
var maxScale = 3f
var m: FloatArray? = null
var viewWidth = 0
var viewHeight = 0
var saveScale = 1f
protected var origWidth = 0f
protected var origHeight = 0f
var oldMeasuredWidth = 0
var oldMeasuredHeight = 0
var mScaleDetector: ScaleGestureDetector? = null
// var context: Context? = null
var context1 : Context? = null
constructor(context: Context) : super(context) {
sharedConstructing(context)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
sharedConstructing(context)
}
var mGestureDetector: GestureDetector? = null
private fun sharedConstructing(context: Context) {
super.setClickable(true)
this.context1 = context
mGestureDetector = GestureDetector(context, this)
mGestureDetector!!.setOnDoubleTapListener(this)
mScaleDetector = ScaleGestureDetector(context, ScaleListener())
matrix1 = Matrix()
m = FloatArray(9)
imageMatrix = matrix1
scaleType = ScaleType.MATRIX
setOnTouchListener { v, event ->
mScaleDetector!!.onTouchEvent(event)
mGestureDetector!!.onTouchEvent(event)
val curr = PointF(event.x, event.y)
when (event.action) {
MotionEvent.ACTION_DOWN -> {
last.set(curr)
start.set(last)
mode = DRAG
}
MotionEvent.ACTION_MOVE -> if (mode == DRAG) {
val deltaX = curr.x - last.x
val deltaY = curr.y - last.y
val fixTransX = getFixDragTrans(
deltaX, viewWidth.toFloat(),
origWidth * saveScale
)
val fixTransY = getFixDragTrans(
deltaY, viewHeight.toFloat(),
origHeight * saveScale
)
matrix1!!.postTranslate(fixTransX, fixTransY)
fixTrans()
last[curr.x] = curr.y
}
MotionEvent.ACTION_UP -> {
mode = NONE
val xDiff = Math.abs(curr.x - start.x).toInt()
val yDiff = Math.abs(curr.y - start.y).toInt()
if (xDiff < CLICK && yDiff < CLICK) performClick()
}
MotionEvent.ACTION_POINTER_UP -> mode = NONE
}
imageMatrix = matrix1
invalidate()
true // indicate event was handled
}
}
fun setMaxZoom(x: Float) {
maxScale = x
}
override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
return false
}
override fun onDoubleTap(e: MotionEvent): Boolean {
// Double tap is detected
Log.i("MAIN_TAG", "Double tap detected")
val origScale = saveScale
val mScaleFactor: Float
if (saveScale == maxScale) {
saveScale = minScale
mScaleFactor = minScale / origScale
} else {
saveScale = maxScale
mScaleFactor = maxScale / origScale
}
matrix1!!.postScale(
mScaleFactor, mScaleFactor, (viewWidth / 2).toFloat(), (
viewHeight / 2).toFloat()
)
fixTrans()
return false
}
override fun onDoubleTapEvent(e: MotionEvent): Boolean {
return false
}
override fun onDown(e: MotionEvent): Boolean {
return false
}
override fun onShowPress(e: MotionEvent) {}
override fun onSingleTapUp(e: MotionEvent): Boolean {
return false
}
override fun onScroll(e1: MotionEvent, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
return false
}
override fun onLongPress(e: MotionEvent) {}
override fun onFling(e1: MotionEvent, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
return false
}
private inner class ScaleListener : SimpleOnScaleGestureListener() {
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
mode = ZOOM
return true
}
override fun onScale(detector: ScaleGestureDetector): Boolean {
var mScaleFactor = detector.scaleFactor
val origScale = saveScale
saveScale *= mScaleFactor
if (saveScale > maxScale) {
saveScale = maxScale
mScaleFactor = maxScale / origScale
} else if (saveScale < minScale) {
saveScale = minScale
mScaleFactor = minScale / origScale
}
if (origWidth * saveScale <= viewWidth
|| origHeight * saveScale <= viewHeight
) matrix1!!.postScale(
mScaleFactor, mScaleFactor, (viewWidth / 2).toFloat(), (
viewHeight / 2).toFloat()
) else matrix1!!.postScale(
mScaleFactor, mScaleFactor,
detector.focusX, detector.focusY
)
fixTrans()
return true
}
}
fun fixTrans() {
matrix1!!.getValues(m)
val transX = m!![Matrix.MTRANS_X]
val transY = m!![Matrix.MTRANS_Y]
val fixTransX = getFixTrans(transX, viewWidth.toFloat(), origWidth * saveScale)
val fixTransY = getFixTrans(
transY, viewHeight.toFloat(), origHeight
* saveScale
)
if (fixTransX != 0f || fixTransY != 0f) matrix1!!.postTranslate(fixTransX, fixTransY)
}
fun getFixTrans(trans: Float, viewSize: Float, contentSize: Float): Float {
val minTrans: Float
val maxTrans: Float
if (contentSize <= viewSize) {
minTrans = 0f
maxTrans = viewSize - contentSize
} else {
minTrans = viewSize - contentSize
maxTrans = 0f
}
if (trans < minTrans) return -trans + minTrans
return if (trans > maxTrans) -trans + maxTrans else 0f
}
private fun getFixDragTrans(delta: Float, viewSize: Float, contentSize: Float): Float {
return if (contentSize <= viewSize) {
0f
} else delta
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
viewWidth = MeasureSpec.getSize(widthMeasureSpec)
viewHeight = MeasureSpec.getSize(heightMeasureSpec)
//
// Rescales image on rotation
//
if (oldMeasuredHeight == viewWidth && oldMeasuredHeight == viewHeight || viewWidth == 0 || viewHeight == 0) return
oldMeasuredHeight = viewHeight
oldMeasuredWidth = viewWidth
if (saveScale == 1f) {
// Fit to screen.
val scale: Float
val drawable = drawable
if (drawable == null || drawable.intrinsicWidth == 0 || drawable.intrinsicHeight == 0) return
val bmWidth = drawable.intrinsicWidth
val bmHeight = drawable.intrinsicHeight
Log.d("bmSize", "bmWidth: $bmWidth bmHeight : $bmHeight")
val scaleX = viewWidth.toFloat() / bmWidth.toFloat()
val scaleY = viewHeight.toFloat() / bmHeight.toFloat()
scale = Math.min(scaleX, scaleY)
matrix1!!.setScale(scale, scale)
// Center the image
var redundantYSpace = (viewHeight.toFloat()
- scale * bmHeight.toFloat())
var redundantXSpace = (viewWidth.toFloat()
- scale * bmWidth.toFloat())
redundantYSpace /= 2.toFloat()
redundantXSpace /= 2.toFloat()
matrix1!!.postTranslate(redundantXSpace, redundantYSpace)
origWidth = viewWidth - 2 * redundantXSpace
origHeight = viewHeight - 2 * redundantYSpace
imageMatrix = matrix1
}
fixTrans()
}
companion object {
// We can be in one of these 3 states
const val NONE = 0
const val DRAG = 1
const val ZOOM = 2
const val CLICK = 3
}
}
你试过了吗https://github.com/MikeOrtiz/TouchImageView 我可以想象它具有您需要的所有功能并且维护得很好