练习-实现双击滑动双指缩放的view


事前准备

// 获取图片的bitmap
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){
    //拿到宽为300dp的bitmap
    private val mBitmap = getReplacement(resources, 300.dp.toInt(), R.drawable.ic_toma)
    private val mPaint = Paint(Paint.ANTI_ALIAS_FLAG)
    //初始偏移 用于使得bitmap居中
    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)
    //用于view的刷新
    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(){
        //一定要返回为true 才能拦截后续的事件序列
        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(){
        // 一但开始双指捏合操作会被频繁调用 如果返回true detector.scaleFactor的值为当前的两只手指距离的比和前一次被调用的时候且返回为true的时候两只手距离的比(大概是这个意思 可以自己去试一试)
        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)
        }

    }


}

文章作者: Lao Wu
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Lao Wu !
评论
  目录