实现ImageView的双指缩放

在支持双指的缩放的时候,首先要了解一下Android支持缩放的工具类ScaleGestureDetector,这个缩放是根据这个工具类来实现的。ScaleGestureDetector是用于处理缩放的工具类,用法与GestureDetector类似,都是通过onTouchEvent()关联相应的MotionEvent事件。

OnScaleGestureListener回调方法的介绍:

public interface OnScaleGestureListener {

        /**
         * 缩放进行中,返回值表示是否下次缩放需要重置,如果返回ture,那么detector就会重置缩放事件,如果返回false,detector会在之前的缩放上继续进行计算
         */
        public boolean onScale(ScaleGestureDetector detector);

        /**
         * 缩放开始,返回值表示是否受理后续的缩放事件
         */
        public boolean onScaleBegin(ScaleGestureDetector detector);

        /**
         * 缩放结束
         */
        public void onScaleEnd(ScaleGestureDetector detector);
    }

因为这个地方既有GestureDetector又有ScaleGestureDetector,下面在onTouchEvent的方法的处理会有所改变:

  override fun onTouchEvent(event: MotionEvent?): Boolean {
        var result= scaleGestureDetector.onTouchEvent(event)
        if(!scaleGestureDetector.isInProgress){
            result = gesture.onTouchEvent(event)
        }
        return result
    }

下面就可以上一下完整的代码了:

class ScaleImageView @JvmOverloads constructor(context: Context,attrs: AttributeSet? = null,defStyleAttr: Int=0) : AppCompatImageView(context,attrs,defStyleAttr),
    GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener, Runnable,
    ScaleGestureDetector.OnScaleGestureListener {
    private val scaleFactor  = 1.5f
    private val picture:Bitmap = BitmapFactory.decodeResource(resources, R.drawable.banner)
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private var bigScale = 1f
    private var smallScale = 1f
    private var offsetX = 0f
    private var offsetY = 0f
    private var currentOffsetY = 0f
    private var currentOffsetX = 0f
    private var isBig = false
    private val gesture:GestureDetectorCompat = GestureDetectorCompat(context,this)
    private val overScroller = OverScroller(context)
    private var animator:ObjectAnimator? = null
    private val scaleGestureDetector = ScaleGestureDetector(context,this)
    private var initScale:Float = 0f
    var currentScale:Float = 1f
        set(value) {
            field = value
            invalidate()
        }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        if(w/width.toFloat() > h/height.toFloat()){
            bigScale = width / picture.width.toFloat() * scaleFactor
            smallScale = height / picture.height.toFloat()
        }else{
            smallScale = width / picture.width.toFloat()
            bigScale = height / picture.height.toFloat() * scaleFactor
        }
        offsetX = (width - picture.width) / 2f
        offsetY = (height - picture.height) / 2f
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        var result= scaleGestureDetector.onTouchEvent(event)
        if(!scaleGestureDetector.isInProgress){
            result = gesture.onTouchEvent(event)
        }
        return result
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
        canvas?.let {
            var scaleFaction = (currentScale - smallScale) / (bigScale - smallScale)
            it.translate(currentOffsetX * scaleFaction, currentOffsetY * scaleFaction)
            it.scale(currentScale, currentScale, width / 2f, height / 2f)
            it.drawBitmap(picture,offsetX,offsetY,paint)
        }
    }

    //--------------------------GestureDetector.OnGestureListener---------------------------
    override fun onShowPress(e: MotionEvent?) {

    }

    override fun onSingleTapUp(e: MotionEvent?): Boolean {
        return false
    }

    override fun onDown(e: MotionEvent?): Boolean {
        return true
    }

    override fun onFling(
        down: MotionEvent?,
        event: MotionEvent?,
        velocityX: Float,
        velocityY: Float
    ): Boolean {
        if(isBig){
            overScroller.fling(currentOffsetX.toInt(),currentOffsetY.toInt(),
                velocityX.toInt(),velocityY.toInt(),
                -(bigScale * picture.width - width).toInt()/2,
                (bigScale * picture.width - width).toInt()/2,
                -(bigScale * picture.height - height).toInt()/2,
                (bigScale * picture.height - height).toInt()/2,
                TypedValue.complexToDimensionPixelSize(50,context.resources.displayMetrics),
                TypedValue.complexToDimensionPixelSize(50,context.resources.displayMetrics)
            )
            postOnAnimation(this)
        }
        return false
    }


    override fun run() {
        if(overScroller.computeScrollOffset()){
            currentOffsetY = overScroller.currY.toFloat()
            currentOffsetX = overScroller.currX.toFloat()
            invalidate()
            postOnAnimation(this)
        }
    }

    override fun onScroll(
        down: MotionEvent?,
        event: MotionEvent?,
        distanceX: Float,
        distanceY: Float
    ): Boolean {
        if(isBig) {
            currentOffsetX -= distanceX
            currentOffsetY -= distanceY
            fixOffset()
            invalidate()
        }
        return false
    }

    private fun fixOffset() {
        if (currentOffsetX > (bigScale * picture.width - width) / 2) {
            currentOffsetX = (bigScale * picture.width - width) / 2
        }
        if (currentOffsetX < -(bigScale * picture.width - width) / 2) {
            currentOffsetX = -(bigScale * picture.width - width) / 2
        }
        if (currentOffsetY > (bigScale * picture.height - height) / 2) {
            currentOffsetY = (bigScale * picture.height - height) / 2
        }
        if (currentOffsetY < -((bigScale * picture.height - height)) / 2) {
            currentOffsetY = -((bigScale * picture.height - height)) / 2
        }
    }

    override fun onLongPress(e: MotionEvent?) {

    }

    //----------------------------GestureDetector.OnDoubleTapListener---------------------------------
    override fun onDoubleTap(e: MotionEvent?): Boolean {
        isBig = !isBig
        if(isBig){
            e?.let {
                currentOffsetX = (it.x - width / 2f) - (e.x - width /2f) * bigScale / smallScale
                currentOffsetY = (it.y - height / 2f) - (e.y - height / 2f) * bigScale/ smallScale
            }
            fixOffset()
            getAnimator().start()
        }else{
            getAnimator().reverse()
        }
        return false
    }

    private fun getAnimator():ObjectAnimator {
        return if (animator == null) {
            animator = ObjectAnimator.ofFloat(this, "currentScale", smallScale, bigScale)
            animator!!
        } else {
            if(isBig) {
                animator?.setFloatValues(currentScale, bigScale)
            }else{
                animator?.setFloatValues(smallScale, bigScale)
            }
            animator!!
        }
    }

    override fun onDoubleTapEvent(e: MotionEvent?): Boolean {
        return false

    }

    override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
        return false
    }

    //----------------------------ScaleGestureDetector.OnScaleGestureListener---------------------------------
    override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean {
        //这个地方返回true 是为了可以处理后面的缩放事件
        initScale = currentScale
        return true
    }

    override fun onScaleEnd(detector: ScaleGestureDetector?) {
    }

    override fun onScale(detector: ScaleGestureDetector?): Boolean {
        currentScale = initScale * detector?.scaleFactor!!
        fixScale()
        invalidate()
        return false
    }

    private fun fixScale() {
        if (currentScale > bigScale) {
            currentScale = bigScale
        }
        if (currentScale < smallScale) {
            currentScale = smallScale
        }
    }
}

 

你可能感兴趣的:(android,android)