ScalableImageView 是一个可放大 可滑动的一个View,简单实现以下,主要涉及到自定义View,以及 GestureDetector 手势监听,还有属性动画的使用
class ScalableImageView(context: Context, attrs: AttributeSet?) : View(context, attrs),
GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener, Runnable {
private var paint = Paint()
/**
* 是否是放大状态
*/
private var isBig = false
/**
* 放大系数,当最大化的时候,可以让所有的都超出边界,当放大的时候可以上下左右都能滑动
*/
private val scaleFactory = 1.5f
private var scale = 0f
set(value) {
field = value
invalidate()
}
/**
* 一开始的时候直接在View的正中间为中心
*/
private val centerX: Float by lazy { (width - avatar.width) / 2f }
private val centerY: Float by lazy { (height - avatar.height) / 2f }
/**
* x轴的偏移
*/
private var offsetX = 0f
/**
* y轴的偏移
*/
private var offsetY = 0f
/**
* 初始的缩放大小
*/
private var smallScale = 1f
/**
* 双击放大时的最大值
*/
private val bigScale: Float by lazy {
Math.max(height / avatar.height.toFloat(), width / avatar.width.toFloat()) * scaleFactory
}
/**
* 双击的时候的动画 放大缩小
*/
private val scaleAnimator: ObjectAnimator by lazy {
ObjectAnimator.ofFloat(this, "scale", 0f, 1f)
}
/**
* 计算滑动
*/
private val scroller: OverScroller = OverScroller(context)
/**
* 收拾监听
*/
private val gestureDetector: GestureDetectorCompat =
GestureDetectorCompat(
context,
this
).apply {
// 这行代码可以不用写
// 在他的内部 ,所以直接 实现 GestureDetector.OnDoubleTapListener 即可
// if (listener instanceof OnDoubleTapListener) {
// setOnDoubleTapListener((OnDoubleTapListener) listener);
// }
setOnDoubleTapListener(this@ScalableImageView)
}
private val avatar: Bitmap by lazy { getAvatar(this) }
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// 移动canvas
canvas.translate(offsetX, offsetY)
var currentScale = 1 + (bigScale - smallScale) * scale
canvas.scale(currentScale, currentScale, width / 2f, height / 2f)
// 绘制的时候使用offsetX 和 offsetY
canvas.drawBitmap(avatar, centerX, centerY, paint)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
return gestureDetector.onTouchEvent(event)
}
override fun onDown(e: MotionEvent?): Boolean {
return true
}
/**
* 是否为按下的状态
*/
override fun onShowPress(e: MotionEvent?) {
}
/**
* 当点击之后抬起的时候,可以认为是个click点击监听,
* @return 是否消费了点击,对于开发者来说 无影响,true 和 false 都可以,主要是系统做记录用的
*/
override fun onSingleTapUp(e: MotionEvent?): Boolean {
return true
}
/**
* 用户按下触摸屏 & 拖动
*
* @param distanceX 旧位置-新位置
* @param distanceY 旧位置-新位置
* @return 也没啥用
*/
override fun onScroll(
e1: MotionEvent?,
current: MotionEvent?,
distanceX: Float,
distanceY: Float
): Boolean {
// 放大状态才能 滑动。根据手指的移动算出来 offsetX ,offsetY
// 算出边界值
if (isBig) {
offsetX -= distanceX
offsetY -= distanceY
fixXY()
invalidate()
}
return true
}
private fun fixXY() {
val borderX = (avatar.width * bigScale - width) / 2f
val borderY = (avatar.height * bigScale - height) / 2f
if (offsetX > borderX) {
offsetX = borderX
}
if (offsetY > borderY) {
offsetY = borderY
}
if (offsetX < -borderX) {
offsetX = -borderX
}
if (offsetY < -borderY) {
offsetY = -borderY
}
}
/**
* 长按
*/
override fun onLongPress(e: MotionEvent?) {
}
/**
*惯性滑动
* velocityX 速度
*/
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent?,
velocityX: Float,
velocityY: Float
): Boolean {
val borderX = (avatar.width * bigScale - width) / 2
val borderY = (avatar.height * bigScale - height) / 2
if (isBig) {
// 前两个是 选的坐标系
scroller.fling(
offsetX.toInt(),
offsetY.toInt(),
velocityX.toInt(),
velocityY.toInt(),
-borderX.toInt(),
borderX.toInt(),
-borderY.toInt(),
borderY.toInt(),
50f.px.toInt(),
50f.px.toInt()
)
/**
* 一帧调用一次
*/
postOnAnimation(this)
}
return false
}
/**
* 如果 GestureDetectorCompat 设置了双击的监听,就不要使用 onSingleTapUp 这个方法做click处理了,
* 用 onSingleTapConfirmed 这个方法处理
*/
override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
return true
}
/**
* 双击
*/
override fun onDoubleTap(e: MotionEvent): Boolean {
isBig = !isBig
if (isBig) {
offsetX = (e.x - width / 2f) - (e.x - width / 2) * bigScale
offsetY = (e.y - height / 2f) - (e.y - height / 2) * bigScale
fixXY()
scaleAnimator.start()
} else {
scaleAnimator.reverse()
}
return true
}
override fun onDoubleTapEvent(e: MotionEvent?): Boolean {
return true
}
override fun run() {
// 计算 看看滑动结束了没
var isCompelte = scroller.computeScrollOffset()
if(isCompelte){
val currX = scroller.currX
val currY = scroller.currY
offsetX = currX.toFloat()
offsetY = currY.toFloat()
// 一帧执行一次
postOnAnimation(this)
}
}
}
utils
val Float.px:Float
get() = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
this,
Resources.getSystem().displayMetrics
)
fun getAvatar(view: View):Bitmap{
var bitmap = BitmapFactory.decodeResource(view.resources,R.drawable.avatar)
// BitmapFactory.decodeFile("${context.cacheDir.absolutePath}/dog.jpg", opt)
//拿到图片宽高
val imageWidth = bitmap.width
val imageHeight = bitmap.height
//拿到View的宽高
val viewWidth = view.width
val viewHeigh = view.height
//计算缩放比例
var scale = 1f
val scaleWidth = imageWidth / viewWidth.toFloat()
val scaleHeight = imageHeight / viewHeigh.toFloat()
//选择较大的那个比例
if (scaleWidth >= scaleHeight && scaleWidth >= 1) {
scale = scaleWidth
} else if (scaleWidth < scaleHeight && scaleHeight >= 1) {
scale = scaleHeight
}
return bitmap.scale((imageWidth/scale).toInt(),(imageHeight/scale).toInt())
}