class PhotoView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) :
View(context, attrs, defStyleAttr) {
private val TAG = PhotoView::class.java.simpleName
constructor(context: Context?) : this(context, null, 0)
constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)
private var bitmap: Bitmap = BitmapFactory.decodeResource(resources, R.drawable.drawable_photo)
private val OVER_SCALE_FACTOR = 1.5f
private var smallScaleSize = 1.0f//最小缩放比。一边撑满屏幕,另一边留白
private var bigScaleSize = 1.0f//最大缩放比
private var currScaleSize = 1.0f
private val gestureDetector: GestureDetector = GestureDetector(context, PhotoGestureListener())
private val scaleGestureDetector: ScaleGestureDetector =
ScaleGestureDetector(context, PhotoScaleGestureDetectorListener())
private var offsetX = 0f
private var offsetY = 0f
private var bitmapLeft = 0f
private var bitmapTop = 0f
private val overScroller = OverScroller(context)
private val filingRunnable = object : Runnable {
override fun run() {
if (overScroller.computeScrollOffset()) {
offsetX = overScroller.currX.toFloat()
offsetY = overScroller.currY.toFloat()
invalidate()
//postOnAnimation是每帧动画执行一次,性能更好
postOnAnimation(this)
}
}
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
val scaleValue = (currScaleSize - smallScaleSize) / (bigScaleSize - smallScaleSize)
canvas?.translate(offsetX * scaleValue, offsetY * scaleValue)
canvas?.scale(currScaleSize, currScaleSize, width / 2f, height / 2f)
//在这里进行图片的绘制
canvas?.drawBitmap(
bitmap,
bitmapLeft,
bitmapTop,
null
)
}
//measure后调用,执行onLayout后才执行onDraw
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
bitmapLeft = (width - bitmap.width) / 2f
bitmapTop = (height - bitmap.height) / 2f
val bitmapRatio = bitmap.width * 1f / bitmap.height
val screenRatio = width * 1f / height
if (bitmapRatio > screenRatio) {
//说明宽要长很多。宽先撑满屏幕,高留白。等高撑满的时候,宽超过屏幕了
smallScaleSize = width * 1f / bitmap.width
//最大size乘以一个系数
bigScaleSize = height * 1f / bitmap.height * OVER_SCALE_FACTOR
} else {
//说明高要高很多。高先撑满屏幕,宽留白。等宽撑满的时候,高超过屏幕了
smallScaleSize = height * 1f / bitmap.height
bigScaleSize = width * 1f / bitmap.width * OVER_SCALE_FACTOR
}
currScaleSize = smallScaleSize
Log.d(TAG, "onLayout: smallScaleSize=$smallScaleSize,bigScaleSize=$bigScaleSize")
invalidate()
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
//双指缩放优先处理
var onTouchEvent = scaleGestureDetector.onTouchEvent(event)
if (!scaleGestureDetector.isInProgress) {
onTouchEvent = gestureDetector.onTouchEvent(event)
}
return onTouchEvent
}
//双指缩放处理
inner class PhotoScaleGestureDetectorListener :
ScaleGestureDetector.SimpleOnScaleGestureListener() {
var currScaleScaleSize = 0f
override fun onScale(detector: ScaleGestureDetector?): Boolean {
detector?.run {
currScaleSize =
(currScaleScaleSize * scaleFactor).coerceAtLeast(smallScaleSize)
.coerceAtMost(bigScaleSize)//限制最大、最小缩放比
invalidate()
}
return super.onScale(detector)
}
override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean {
currScaleScaleSize = currScaleSize
return true//返回true,表示消费事件
}
override fun onScaleEnd(detector: ScaleGestureDetector?) {
super.onScaleEnd(detector)
}
}
//单指操作处理
inner class PhotoGestureListener : GestureDetector.SimpleOnGestureListener() {
override fun onDown(e: MotionEvent?): Boolean {
// 返回true才会被消费
// return super.onDown(e)
return true
}
// 延时触发 100ms -- 点击效果,设置水波纹效果
override fun onShowPress(e: MotionEvent?) {
super.onShowPress(e)
}
// 单击事件抬起时触发。双击事件第二次抬起时触发
override fun onSingleTapUp(e: MotionEvent?): Boolean {
return super.onSingleTapUp(e)
}
override fun onScroll(
e1: MotionEvent?,
e2: MotionEvent?,
distanceX: Float,//单位时间内滑动的距离:旧值-新值
distanceY: Float
): Boolean {
//滑动
if (currScaleSize > smallScaleSize) {
offsetX -= distanceX
offsetY -= distanceY
checkOffsetXAndOffsetY()
invalidate()
}
return super.onScroll(e1, e2, distanceX, distanceY)
}
override fun onLongPress(e: MotionEvent?) {
super.onLongPress(e)
}
//抛掷。只会开始时执行一次,接下来不会执行了
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent?,
velocityX: Float,
velocityY: Float
): Boolean {
if (currScaleSize > smallScaleSize) {
//都可滑动
overScroller.fling(
offsetX.toInt(),
offsetY.toInt(),
velocityX.toInt(),//x轴速度
velocityY.toInt(),//y轴速度
-(bitmap.width * bigScaleSize - width).toInt() / 2,
(bitmap.width * bigScaleSize - width).toInt() / 2,
-(bitmap.height * bigScaleSize - height).toInt() / 2,
(bitmap.height * bigScaleSize - height).toInt() / 2,
100,//x轴回弹距离
100//y轴回弹距离
)
postOnAnimation(filingRunnable)
}
return super.onFling(e1, e2, velocityX, velocityY)
}
//单击事件触发,双击不触发。在单击up后300ms后回调
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
return super.onSingleTapConfirmed(e)
}
//双击事件up时触发
override fun onDoubleTap(e: MotionEvent): Boolean {
if (currScaleSize == smallScaleSize) {
offsetX = e.x - width / 2 - (e.x - width / 2) * bigScaleSize / currScaleSize
offsetY = e.y - height / 2 - (e.y - height / 2) * bigScaleSize / currScaleSize
checkOffsetXAndOffsetY()
//当前是最小缩放状态,双击,放大到最大缩放比
getObjectAnimator(currScaleSize, bigScaleSize).start()
} else if (currScaleSize == bigScaleSize) {
//当前是最大缩放状态,双击,缩小到最小缩放比
getObjectAnimator(bigScaleSize, smallScaleSize).start()
} else{
//双击,放大到最大缩放比
getObjectAnimator(currScaleSize, bigScaleSize).start()
}
return super.onDoubleTap(e)
}
//双击事件,down、move、up都会触发
override fun onDoubleTapEvent(e: MotionEvent?): Boolean {
return super.onDoubleTapEvent(e)
}
override fun onContextClick(e: MotionEvent?): Boolean {
return super.onContextClick(e)
}
}
public fun setCurrScaleSize(currScaleSize: Float) {
this.currScaleSize = currScaleSize
invalidate()
}
private var objectAnimator: ObjectAnimator? = null
private fun getObjectAnimator(startScaleSize: Float, endScaleSize: Float): ObjectAnimator {
if (objectAnimator == null) {
objectAnimator = ObjectAnimator.ofFloat(this, "currScaleSize", 0f)//必须定义setCurrScaleSize()方法,通过反射调用
}
objectAnimator!!.setFloatValues(startScaleSize, endScaleSize)
objectAnimator!!.duration = 400//默认300ms
return objectAnimator!!
}
private fun checkOffsetXAndOffsetY() {
offsetX = offsetX.coerceAtMost((bitmap.width * bigScaleSize - width) / 2f)
.coerceAtLeast(-(bitmap.width * bigScaleSize - width) / 2f)
offsetY = offsetY.coerceAtMost((bitmap.height * bigScaleSize - height) / 2f)
.coerceAtLeast(-(bitmap.height * bigScaleSize - height) / 2f)
}
}
网友评论