IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android自定义view---手势识别,双击,惯性滑动,相册图片效果 -> 正文阅读

[移动开发]Android自定义view---手势识别,双击,惯性滑动,相册图片效果


前言

在view中我们可以重写onTouchEvent来自定义点击事件,但是MotionEvent给我们的选择太少,无法满足一些个性化的需求,比如双击,惯性滑动等等,所以我们引入GestureDetectorCompat监听器来实现一些额外的功能


一、GestureDetectorCompat是什么?

GestureDetectorCompat,翻译系为手势检测器,类似于外挂,钩子,把你在屏幕上的触摸和点击截取到,去替代默认的super.onTouchEvent(event),而是走我们在手势检测器中自定义的触摸效果

二、使用步骤

1.定义一个GestureDetectorCompat的实例

代码如下:

private val gestureDetectorCompat = GestureDetectorCompat(context, this)

第二个参数为listener,让view实现GestureDetector.OnGestureListener接口,我们就可以直接填入this,但是要去重写抽象方法。

    override fun onDown(e: MotionEvent?): Boolean {
        TODO("Not yet implemented")
    }

    override fun onShowPress(e: MotionEvent?) {
        TODO("Not yet implemented")
    }

    override fun onSingleTapUp(e: MotionEvent?): Boolean {
        TODO("Not yet implemented")
    }

    override fun onScroll(
        e1: MotionEvent?,
        e2: MotionEvent?,
        distanceX: Float,
        distanceY: Float
    ): Boolean {
        TODO("Not yet implemented")
    }

    override fun onLongPress(e: MotionEvent?) {
        TODO("Not yet implemented")
    }

    override fun onFling(
        e1: MotionEvent?,
        e2: MotionEvent?,
        velocityX: Float,
        velocityY: Float
    ): Boolean {
        TODO("Not yet implemented")
    }

2.重写方法,实现自定义效果

①想让检测器消费一系列的触摸事件,那么就要在重写方法onDown中去返回true,那么后续的一系列触摸过程才能让手势检测器获取

代码如下:

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

②既然我们要去实现双击效果,那么就得再给手势检测器设置一个监听

    private val gestureDetectorCompat = GestureDetectorCompat(context, this).apply {
        setOnDoubleTapListener(this@ScalableImageView)
    }

setOnDoubleTapListener的参数是传入一个listener,那么我们还是填入view,让view去重写接口GestureDetector.OnDoubleTapListener的方法即可
重写的方法:

    override fun onSingleTapConfirmed(e: MotionEvent?): Boolean {
        TODO("Not yet implemented")
    }

    override fun onDoubleTap(e: MotionEvent?): Boolean {
        TODO("Not yet implemented")
    }

    override fun onDoubleTapEvent(e: MotionEvent?): Boolean {
        TODO("Not yet implemented")
    }

简化:
在这里插入图片描述
追踪GestureDetectorCompat的构造函数的源码我们可以看到,内部帮我们判断了listener的类型,如果是GestureDetector.OnDoubleTapListene的实现类,那么就会帮我们去执行setOnDoubleTapListener方法,不用我们再去配置,所以代码可以简化为:

    private val gestureDetectorCompat = GestureDetectorCompat(context, this)

③实现双击变大变小

效果:
在这里插入图片描述

④重写onDoubleTap

目的是双击实现图片的放大缩小,所以我们做一个动画,控制图片的大小

    private var scaleFraction = 0f
        set(value) {
            field = value
            invalidate()
        }
    private val animator by lazy { ObjectAnimator.ofFloat(this, "scaleFraction", 0f, 1f) }

ondraw中,拿到要缩放的比例系数,实现从小图片到大图片

            val scale = smallScale + (bigScale - smallScale) * scaleFraction
            scale(scale, scale, width / 2f, height / 2f)
            drawBitmap(bitmap, originalOffsetX, originalOffsetY, paint)

所以在实现双击的方法中,从小到大就是正常播放动画,从大到小就是反向播放动画

    override fun onDoubleTap(e: MotionEvent?): Boolean {
        if (isBig) {
            //本来是大图片,那么就从小到大
            animator.reverse()
        } else {
            animator.start()
        }
        isBig = !isBig
        return true
    }

④实现惯性滑动

核心是重写onFling方法
创建一个OverScroller对象

private val overScroller = OverScroller(context)

OverScroller的作用:控制一个点在一定范围内的惯性滑动
如图所示
在这里插入图片描述
那么如何控制一张图片的惯性移动呢?
在这里插入图片描述
把图片在大框中的移动等价为触摸点在小框中的移动,触摸点在x,y轴上移动的位移,同步到图片在xy轴上的偏移,那么就可以实现图片的惯性
说白了就是把你的触摸点控制在一个小框的范围内,就可以把图片控制在大框内
实现步骤:
①:定义两个变量作为小圆在x轴上移动的偏移,y轴上移动的偏移

    private var offsetX = 0f
    private var offsetY = 0f

②:重写onFling方法

    override fun onFling(
        e1: MotionEvent?,
        e2: MotionEvent?,
        velocityX: Float,
        velocityY: Float
    ): Boolean {
        overScroller.fling(
            offsetX.toInt(),
            offsetY.toInt(),
            velocityX.toInt(),
            velocityY.toInt(),
            (-(bitmap.width * bigScale - width) / 2f).toInt(),
            ((bitmap.width * bigScale - width) / 2f).toInt(),
            (-(bitmap.height * bigScale - height) / 2f).toInt(),
            ((bitmap.height * bigScale - height) / 2f).toInt(), 40.dp.toInt(), 40.dp.toInt()
        )
        postOnAnimation(this)
        return false
    }

第一个参数和第二个参数:手指点击下去时的位置
第三第四个参数:手指用力滑动时,在两个方向上的速度
第五第六个参数:包围触摸点的小框的范围
最后两个参数指滑出小框边界时,超出又恢复的范围
如下图所示的效果
在这里插入图片描述
总结:把触摸点的位移同步给图片
③实现流畅的惯性滑动
让view实现Runnable接口,重写run方法,按帧去更新滑动

    override fun run() {
        //只要惯性还没结束,就递归去更新图片位置
        if (overScroller.computeScrollOffset()) {
            offsetX = overScroller.currX.toFloat()
            offsetY = overScroller.currY.toFloat()
            invalidate()
            postOnAnimation(this)
        }
    }

④在onFling中使用run方法

postOnAnimation(this)

总结

使用了一个手势检测器去检测一些额外的手势,如双击,惯性滑动等等,再去重写手势检测器的抽象方法,实现这些手势该有的反馈

完整代码

package com.lbj23.customview.customview

import android.animation.ObjectAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.widget.OverScroller
import androidx.core.view.GestureDetectorCompat
import com.lbj23.customview.R
import com.lbj23.customview.dp
import com.lbj23.customview.getAvatar
import kotlin.math.max
import kotlin.math.min

private const val EXTRA_SCALE = 1.5f

class ScalableImageView(context: Context?, attrs: AttributeSet?) : View(context, attrs),
    GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener, Runnable {
    private val gestureDetectorCompat = GestureDetectorCompat(context, this).apply {
        setIsLongpressEnabled(false)
    }
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val imageSize = 120.dp.toInt()
    private val bitmap = getAvatar(resources, R.drawable.test, imageSize)
    private var originalOffsetX = 0f
    private var originalOffsetY = 0f
    private var offsetX = 0f
    private var offsetY = 0f
    private var bigScale = 0f
    private var smallScale = 0f
    private var isBig = false
    private var scaleFraction = 0f
        set(value) {
            field = value
            invalidate()
        }
    private val animator by lazy { ObjectAnimator.ofFloat(this, "scaleFraction", 0f, 1f) }
    private val overScroller = OverScroller(context)
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.apply {
            if (isBig) {
                translate(offsetX, offsetY)
            }
            val scale = smallScale + (bigScale - smallScale) * scaleFraction
            scale(scale, scale, width / 2f, height / 2f)
            drawBitmap(bitmap, originalOffsetX, originalOffsetY, paint)
        }
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        originalOffsetX = (width - bitmap.width) / 2f
        originalOffsetY = (height - bitmap.height) / 2f
        if (width / height.toFloat() < bitmap.width / bitmap.height.toFloat()) {
            smallScale = width / bitmap.width.toFloat()
            bigScale = (height / bitmap.height.toFloat()) * EXTRA_SCALE
        } else {
            smallScale = height / bitmap.height.toFloat()
            bigScale = (width / bitmap.width.toFloat()) * EXTRA_SCALE
        }
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        return gestureDetectorCompat.onTouchEvent(event)
    }

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

    override fun onShowPress(e: MotionEvent?) {
    }

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

    override fun onScroll(
        e1: MotionEvent?,
        e2: MotionEvent?,
        distanceX: Float,
        distanceY: Float
    ): Boolean {
        if (isBig) {
            offsetX -= distanceX
            offsetX = min((bitmap.width * bigScale - width) / 2f, offsetX)
            offsetX = max(-(bitmap.width * bigScale - width) / 2f, offsetX)
            offsetY -= distanceY
            offsetY = min((bitmap.height * bigScale - height) / 2f, offsetY)
            offsetY = max(-(bitmap.height * bigScale - height) / 2f, offsetY)
            invalidate()
        }
        return true
    }

    override fun onLongPress(e: MotionEvent?) {
    }

    override fun onFling(
        e1: MotionEvent?,
        e2: MotionEvent?,
        velocityX: Float,
        velocityY: Float
    ): Boolean {
        overScroller.fling(
            offsetX.toInt(),
            offsetY.toInt(),
            velocityX.toInt(),
            velocityY.toInt(),
            (-(bitmap.width * bigScale - width) / 2f).toInt(),
            ((bitmap.width * bigScale - width) / 2f).toInt(),
            (-(bitmap.height * bigScale - height) / 2f).toInt(),
            ((bitmap.height * bigScale - height) / 2f).toInt(), 40.dp.toInt(), 40.dp.toInt()
        )
        postOnAnimation(this)
        return false
    }

    override fun run() {
        //只要惯性还没结束,就递归去更新图片位置
        if (overScroller.computeScrollOffset()) {
            offsetX = overScroller.currX.toFloat()
            offsetY = overScroller.currY.toFloat()
            invalidate()
            postOnAnimation(this)
        }
    }

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

    override fun onDoubleTap(e: MotionEvent?): Boolean {
        if (isBig) {
            //本来是大图片,那么就从小到大
            animator.reverse()
        } else {
            animator.start()
        }
        isBig = !isBig
        return true
    }

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

进阶一:优化代码

.不要继续用view去实现那些接口了,写内部类去实现接口
.不用 GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener,继承 GestureDetector.SimpleOnGestureListener(),后者是一个实现类,实现了前面两个接口,所以我们继承后者后之后只需要重写我们需要用到的回调方法即可
GestureDetector.SimpleOnGestureListener()实现了GestureDetector.OnGestureListener,GestureDetector.OnDoubleTapListener
在这里插入图片描述
.优化后内部类的代码

    inner class ChildGestureDetector : GestureDetector.SimpleOnGestureListener() {
        override fun onDoubleTap(e: MotionEvent): Boolean {
            if (isBig) {
                //本来是大图片,那么就从小到大
                animator.reverse()
            } else {
                offsetX = -(e.x - width / 2) * (bigScale / smallScale - 1)
                offsetY = -(e.y - height / 2) * (bigScale / smallScale - 1)
                fixOffset()
                animator.start()
            }
            isBig = !isBig
            return true
        }

        override fun onFling(
            e1: MotionEvent?,
            e2: MotionEvent?,
            velocityX: Float,
            velocityY: Float
        ): Boolean {
            overScroller.fling(
                offsetX.toInt(),
                offsetY.toInt(),
                velocityX.toInt(),
                velocityY.toInt(),
                (-(bitmap.width * bigScale - width) / 2f).toInt(),
                ((bitmap.width * bigScale - width) / 2f).toInt(),
                (-(bitmap.height * bigScale - height) / 2f).toInt(),
                ((bitmap.height * bigScale - height) / 2f).toInt(), 40.dp.toInt(), 40.dp.toInt()
            )
            postOnAnimation(gestureAction)
            return false
        }

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

        override fun onScroll(
            e1: MotionEvent?,
            e2: MotionEvent?,
            distanceX: Float,
            distanceY: Float
        ): Boolean {
            if (isBig) {
                offsetX -= distanceX
                offsetY -= distanceY
                fixOffset()
                invalidate()
            }
            return true
        }
    }

进阶二—>手指点哪里放大到哪里

在这里插入图片描述
①:思路
我们点击框1的中心(黄色小点),那么框1放大后,中心就跑到绿色小点去了,我们想要的效果是点到小框的中心,小框变大框了,中心还在我们手指点击的位置,如图所示,把框2拉倒框3的位置
②:代码实现
在这里插入图片描述
在这里插入图片描述

关键是我画框的三行代码
点击变大后,距离图片几何中心为1距离的黑色小点跑到了最上面的绿色小点
(1距离+2距离)/1距离=大图片/小图片
所以,距离2=1距离*(大图片/小图片-1)
以为是把图片拉回来,offset = -距离2
最后再对offset的值进行矫正,防止拉的太多,导致出现白边效果
实现效果:
在这里插入图片描述

进阶三,捏撑效果

相册中的图片都有一个两指控制图片大小的功能
在这里插入图片描述
实现
①:设置一个缩放系数去取代scaleFraction,用于记录实时的缩放比例,而不是固定的从零到一的一个进度值。

    private var currentScale = 0f
        set(value) {
            field = value
            invalidate()
        }

②:currentScale值动态变化的时候要去重新绘制图片,onDraw就要做修改

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.apply {
            val scaleFraction = (currentScale - smallScale) / (bigScale - smallScale)
            translate(offsetX * scaleFraction, offsetY * scaleFraction)
            scale(currentScale, currentScale, width / 2f, height / 2f)
            drawBitmap(bitmap, originalOffsetX, originalOffsetY, paint)
        }
    }

进度值就为(最新比例-最小比例)/(大图片-小图片),由此可看出我们是要实现由smallScale?bigScale的大小变化,由双指控制
③:currentScale在每次view尺寸变化时去更新
currentScale
最后一行:由于bigScale , smallScale时动态变化的,所以要给动画也重新赋参数值
动画就是尺寸变化时的动画

    private val animator = ObjectAnimator.ofFloat(this, "currentScale", smallScale, bigScale)

④:用ScaleGestureDetector去替代手势检测器
声明

    private val childScaleGestureListener = ChildScaleGestureListener()
    private val scaleGestureDetector = ScaleGestureDetector(context, childScaleGestureListener)

listener的回调函数:

inner class ChildScaleGestureListener : ScaleGestureDetector.OnScaleGestureListener {
    override fun onScale(detector: ScaleGestureDetector): Boolean {
        currentScale *= detector.scaleFactor
        currentScale = currentScale.coerceAtLeast(smallScale).coerceAtMost(bigScale)
        return true
    }

    override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
        offsetX = -(detector.focusX - width / 2) * (bigScale / smallScale - 1)
        offsetY = -(detector.focusY - height / 2) * (bigScale / smallScale - 1)
        return true
    }

    override fun onScaleEnd(detector: ScaleGestureDetector?) {
    }
}

onScale中,返回true,detector.scaleFactor就是实时的比例系数,否则就是开始捏撑时到现在的比例系数变化
如果想要捏撑跟手,则在开始捏撑前给图片重新设置偏移,原理和双击时一致

        offsetX = -(detector.focusX - width / 2) * (bigScale / smallScale - 1)
        offsetY = -(detector.focusY - height / 2) * (bigScale / smallScale - 1)

detector.focusX:捏撑时两指的中心位置
⑥:效果
在这里插入图片描述
⑦:优化
一:限制缩放的范围

    override fun onScale(detector: ScaleGestureDetector): Boolean {
        currentScale *= detector.scaleFactor
        currentScale = currentScale.coerceAtLeast(smallScale).coerceAtMost(bigScale)
        return true
    }

给缩放系数设置范围,介于smallScalebigScale之间

二:实现捏撑到最大或者最小,继续捏撑,然后再反向捏撑,图像不是立马变大或者变小,而是达到最大后者最小的那个临界值后才开始变化
现在是立马变大变小
在这里插入图片描述
改进:

        override fun onScale(detector: ScaleGestureDetector): Boolean {
            val tmpCurScale = currentScale *detector.scaleFactor
            if(tmpCurScale<smallScale||tmpCurScale>bigScale){
                return false
            }else{
                currentScale = tmpCurScale
                return true
            }
        }

如果滑动超过了界限就不消费这次滑动,保留detector.scaleFactor
滑动达到了图片可以缩放的两指临界点后消费事件然后更新detector.scaleFactor
效果
在这里插入图片描述

捏撑和双击一起工作

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        scaleGestureDetector.onTouchEvent(event)
        if (!scaleGestureDetector.isInProgress) {
            gestureDetectorCompat.onTouchEvent(event)
        }
        return true
    }

两个检测器都去执行,如果进入捏撑状态,手势检测器就不执行了

最终的完整代码

package com.lbj23.customview.customview

import android.animation.ObjectAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.view.View
import android.widget.OverScroller
import androidx.core.view.GestureDetectorCompat
import com.lbj23.customview.R
import com.lbj23.customview.dp
import com.lbj23.customview.getAvatar
import kotlin.math.max
import kotlin.math.min

private const val EXTRA_SCALE = 1.5f

class ScalableImageView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
    private val childGestureDetector = ChildGestureDetector()
    private val gestureDetectorCompat = GestureDetectorCompat(context, childGestureDetector)
    private val gestureAction = GestureAction()
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val imageSize = 120.dp.toInt()
    private val bitmap = getAvatar(resources, R.drawable.test, imageSize)
    private var originalOffsetX = 0f
    private var originalOffsetY = 0f
    private var offsetX = 0f
    private var offsetY = 0f
    private var bigScale = 0f
    private var smallScale = 0f
    private var isBig = false
    private var currentScale = 0f
        set(value) {
            field = value
            invalidate()
        }
    private val animator = ObjectAnimator.ofFloat(this, "currentScale", smallScale, bigScale)
    private val overScroller = OverScroller(context)
    private val childScaleGestureListener = ChildScaleGestureListener()
    private val scaleGestureDetector = ScaleGestureDetector(context, childScaleGestureListener)
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        canvas.apply {
            val scaleFraction = (currentScale - smallScale) / (bigScale - smallScale)
            translate(offsetX * scaleFraction, offsetY * scaleFraction)
            scale(currentScale, currentScale, width / 2f, height / 2f)
            drawBitmap(bitmap, originalOffsetX, originalOffsetY, paint)
        }
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        originalOffsetX = (width - bitmap.width) / 2f
        originalOffsetY = (height - bitmap.height) / 2f
        if (width / height.toFloat() < bitmap.width / bitmap.height.toFloat()) {
            smallScale = width / bitmap.width.toFloat()
            bigScale = (height / bitmap.height.toFloat()) * EXTRA_SCALE
        } else {
            smallScale = height / bitmap.height.toFloat()
            bigScale = (width / bitmap.width.toFloat()) * EXTRA_SCALE
        }
        currentScale = smallScale
        animator.setFloatValues(smallScale, bigScale)
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        scaleGestureDetector.onTouchEvent(event)
        if (!scaleGestureDetector.isInProgress) {
            gestureDetectorCompat.onTouchEvent(event)
        }
        return true
    }

    private fun fixOffset() {
        offsetX = min((bitmap.width * bigScale - width) / 2f, offsetX)
        offsetX = max(-(bitmap.width * bigScale - width) / 2f, offsetX)
        offsetY = min((bitmap.height * bigScale - height) / 2f, offsetY)
        offsetY = max(-(bitmap.height * bigScale - height) / 2f, offsetY)
    }

    inner class ChildGestureDetector : GestureDetector.SimpleOnGestureListener() {
        override fun onDoubleTap(e: MotionEvent): Boolean {
            if (isBig) {
                //本来是大图片,那么就从小到大
                animator.reverse()
            } else {
                offsetX = -(e.x - width / 2) * (bigScale / smallScale - 1)
                offsetY = -(e.y - height / 2) * (bigScale / smallScale - 1)
                fixOffset()
                animator.start()
            }
            isBig = !isBig
            return true
        }

        override fun onFling(
            e1: MotionEvent?,
            e2: MotionEvent?,
            velocityX: Float,
            velocityY: Float
        ): Boolean {
            overScroller.fling(
                offsetX.toInt(),
                offsetY.toInt(),
                velocityX.toInt(),
                velocityY.toInt(),
                (-(bitmap.width * bigScale - width) / 2f).toInt(),
                ((bitmap.width * bigScale - width) / 2f).toInt(),
                (-(bitmap.height * bigScale - height) / 2f).toInt(),
                ((bitmap.height * bigScale - height) / 2f).toInt(), 40.dp.toInt(), 40.dp.toInt()
            )
            postOnAnimation(gestureAction)
            return false
        }

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

        override fun onScroll(
            e1: MotionEvent?,
            e2: MotionEvent?,
            distanceX: Float,
            distanceY: Float
        ): Boolean {
            if (isBig) {
                offsetX -= distanceX
                offsetY -= distanceY
                fixOffset()
                invalidate()
            }
            return true
        }
    }

    inner class GestureAction : Runnable {
        override fun run() {
            //只要惯性还没结束,就递归去更新图片位置
            if (overScroller.computeScrollOffset()) {
                offsetX = overScroller.currX.toFloat()
                offsetY = overScroller.currY.toFloat()
                invalidate()
                postOnAnimation(this)
            }
        }
    }

    inner class ChildScaleGestureListener : ScaleGestureDetector.OnScaleGestureListener {
        override fun onScale(detector: ScaleGestureDetector): Boolean {
            val tmpCurScale = currentScale *detector.scaleFactor
            if(tmpCurScale<smallScale||tmpCurScale>bigScale){
                return false
            }else{
                currentScale = tmpCurScale
                return true
            }
        }

        override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {
            offsetX = -(detector.focusX - width / 2) * (bigScale / smallScale - 1)
            offsetY = -(detector.focusY - height / 2) * (bigScale / smallScale - 1)
            return true
        }

        override fun onScaleEnd(detector: ScaleGestureDetector?) {
        }

    }
}
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-10-06 12:20:40  更:2021-10-06 12:22:56 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 7:57:18-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码