事前准备
fun getReplacement(res:Resources,width: Int,id:Int):Bitmap{
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeResource(res,id,options)
options.inJustDecodeBounds = false
options.inDensity = options.outWidth
options.inTargetDensity = width
return BitmapFactory.decodeResource(res,id,options)
}
val Float.dp
get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
this,
Resources.getSystem().displayMetrics)
val Float.sp
get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
this,
Resources.getSystem().displayMetrics)
val Int.dp
get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
this.toFloat(),
Resources.getSystem().displayMetrics)
val Int.sp
get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
this.toFloat(),
Resources.getSystem().displayMetrics)
自定义view
package com.wuhongru.workman.myView
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.view.GestureDetector.SimpleOnGestureListener
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.view.View
import android.widget.OverScroller
import androidx.core.view.GestureDetectorCompat
import androidx.core.view.ViewCompat
import com.wuhongru.workman.R
import com.wuhongru.workman.dp
import com.wuhongru.workman.getReplacement
import kotlin.math.abs
private const val EXTRA_SCALE_COEFFICIENT = 1.2f
class SecondView(context: Context, attributes: AttributeSet) : View(context, attributes){
private val mBitmap = getReplacement(resources, 300.dp.toInt(), R.drawable.ic_toma)
private val mPaint = Paint(Paint.ANTI_ALIAS_FLAG)
private var originalOffsetX = 0f
private var originalOffsetY = 0f
private var smallScale = 0f
private var bigScale = 0f
private var offsetX = 0f
private var offsetY = 0f
private var isBig = false
private var curScale = 0f
set(value) {
field = value
invalidate()
}
private val mGestureDetector = GestureDetectorCompat(context, GestureListener())
private val overScroller = OverScroller(context)
private val refreshRunnable = RefreshRunnable()
private val scaleGestureListener = ScaleGestureListener()
private val mScaleGestureDetector = ScaleGestureDetector(context,scaleGestureListener)
private val scaleAnimator = ObjectAnimator.ofFloat(this, "curScale", smallScale, bigScale)
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
mScaleGestureDetector.onTouchEvent(event)
if (!mScaleGestureDetector.isInProgress){
mGestureDetector.onTouchEvent(event)
}
return true
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
originalOffsetX = (width - mBitmap.width) / 2f
originalOffsetY = (height - mBitmap.height) / 2f
if (width / height.toFloat() > mBitmap.width / mBitmap.height) {
smallScale = height / mBitmap.height.toFloat()
bigScale = width / mBitmap.width.toFloat() * EXTRA_SCALE_COEFFICIENT
} else {
smallScale = width / mBitmap.width.toFloat()
bigScale = height / mBitmap.height.toFloat() * EXTRA_SCALE_COEFFICIENT
}
curScale = smallScale
scaleAnimator.setFloatValues(smallScale,bigScale)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val scaleCoefficient = (curScale-smallScale)/(bigScale-smallScale)
canvas.translate(offsetX*scaleCoefficient,offsetY*scaleCoefficient)
canvas.scale(curScale, curScale, width / 2f, height / 2f)
canvas.drawBitmap(mBitmap, originalOffsetX, originalOffsetY, mPaint)
}
private fun setOffsetMax(currOffset: Float, duration: Float): Float {
return if (abs(currOffset) >= duration) {
abs(currOffset) / currOffset * duration
} else {
currOffset
}
}
private inner class GestureListener:SimpleOnGestureListener(){
override fun onDown(e: MotionEvent): Boolean {
return true
}
override fun onFling(
e1: MotionEvent,
e2: MotionEvent,
velocityX: Float,
velocityY: Float
): Boolean {
if (isBig){
overScroller.fling(
offsetX.toInt(), offsetY.toInt(), velocityX.toInt(), velocityY.toInt(),
(-(mBitmap.width * bigScale - width) / 2).toInt(),
((mBitmap.width * bigScale - width) / 2).toInt(),
(-(mBitmap.height * bigScale - height) / 2).toInt(),
((mBitmap.height * bigScale - height) / 2).toInt(),
70.dp.toInt(),70.dp.toInt()
)
ViewCompat.postOnAnimation(this@SecondView, refreshRunnable)
}
return true
}
override fun onScroll(
e1: MotionEvent,
e2: MotionEvent,
distanceX: Float,
distanceY: Float
): Boolean {
if (isBig) {
offsetX -= distanceX
offsetX = setOffsetMax(offsetX, (mBitmap.width * bigScale - width) / 2)
offsetY -= distanceY
offsetY = setOffsetMax(offsetY, (mBitmap.height * bigScale - height) / 2)
invalidate()
}
return false
}
override fun onDoubleTap(e: MotionEvent): Boolean {
isBig = !isBig
if (isBig) {
offsetX = (e.x-width/2f)*(1-bigScale/smallScale)
offsetX = setOffsetMax(offsetX, (mBitmap.width * bigScale - width) / 2)
offsetY = (e.y-height/2f)*(1-bigScale/smallScale)
offsetY = setOffsetMax(offsetY, (mBitmap.height * bigScale - height) / 2)
scaleAnimator.start()
} else {
scaleAnimator.reverse()
}
return true
}
}
private inner class RefreshRunnable:Runnable{
override fun run() {
if (overScroller.computeScrollOffset()) {
offsetX = overScroller.currX.toFloat()
offsetY = overScroller.currY.toFloat()
invalidate()
ViewCompat.postOnAnimation(this@SecondView, refreshRunnable)
}
}
}
private inner class ScaleGestureListener:ScaleGestureDetector.SimpleOnScaleGestureListener(){
override fun onScale(detector: ScaleGestureDetector): Boolean {
val tempCurScale = curScale*detector.scaleFactor
return if (tempCurScale< smallScale||tempCurScale>bigScale){
false
}else{
curScale *= detector.scaleFactor
true
}
}
override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
offsetX = (detector.focusX-width/2f)*(1-bigScale/smallScale)
offsetY = (detector.focusY-height/2f)*(1-bigScale/smallScale)
return true
}
override fun onScaleEnd(detector: ScaleGestureDetector) {
super.onScaleEnd(detector)
}
}
}