自定义View中关于图片缩放的解决方法

文章源码来自容华谢后的博客

一、图片缩放自定义View

博主写的这个自定义控件是继承自 ImageView 所实现的,实现的具体功能有:移动缩放两指旋转

在阅读源码之前我觉得有必要先了解以下几点:

1、Matrix 类

Matrix 类是 Android 中的一个类,它用于表示和存储 3x3 矩阵,这个矩阵可以用来对 2D 图形进行几何变换,

比如平移(translate)、缩放(scale)、旋转(rotate)和倾斜(skew)。

在 Android 中,Matrix 类是在自定义视图(View)和图像处理时的一个重要组成部分。

Matrix 类的变换操作:

  1. postTranslate (float dx, float dy):在矩阵上应用平移变换,平移距离为 (dx, dy)。
  2. preTranslate (float dx, float dy):在矩阵前应用平移变换。
  3. postScale (float sx, float sy, float px, float py):在矩阵上应用缩放变换,缩放中心为 (px, py)。
  4. preScale (float sx, float sy, float px, float py):在矩阵前应用缩放变换。
  5. postRotate (float degrees, float px, float py):在矩阵上应用旋转变换,旋转中心为 (px, py)。
  6. preRotate (float degrees, float px, float py):在矩阵前应用旋转变换。
  7. postSkew (float kx, float ky, float px, float py):在矩阵上应用倾斜变换,倾斜中心为 (px, py)。
  8. preSkew (float kx, float ky, float px, float py):在矩阵前应用倾斜变换。

2、mapRect()方法

mapRect 是 Matrix 类 的一个方法,用于将一个矩形(RectF 或 Rect)应用矩阵变换,并返回变换后的矩形。

boolean mapRect(RectF dst, RectF src)

dst:目标矩形,将被填充为变换后的矩形。
src:源矩形,是要被应用变换的原始矩形。

该方法将 src 矩形应用 Matrix 中定义的变换,并将结果存储在 dst 矩形中。

3、atan2 函数

在Android中,Math.atan2 函数返回的值是弧度(radians),不是角度(degrees)

atan2 函数计算的是给定的 y 坐标和 x 坐标与正 x 轴之间形成的角的弧度值。需要将弧度转换为角度,可以使用 Math.toDegrees 方法

    //计算并返回两个点之间旋转的角度(基于图片的中心点,即图片中心点为原点)
    internal fun callRotation(baseX: Float, baseY: Float, rotateX: Float, rotateY: Float): Float {
        val deltaX = (baseX - rotateX).toDouble()
        val deltaY = (baseY - rotateY).toDouble()
        //atan2 函数接受两个参数:deltaY(y 坐标的差值)和 deltaX(x 坐标的差值)。
        //返回值:atan2 返回的结果是一个 `弧度值` ,表示从 x 轴正方向顺时针旋转到指向点 (deltaX, deltaY) 的角度。
        //范围:返回的角度范围是 -π 到 π(-180° 到 180°)。
        val radius = atan2(deltaY, deltaX)
        // toDegrees()用于将 `弧度值` 转换为 `角度`
        return Math.toDegrees(radius).toFloat()
    }

4、自定义的coroutineDelay函数

coroutineDelay函数创建了一个协程,延迟500毫秒后执行传入的代码块。这个代码块计算了触摸点的偏移量,并检查是否满足长按的条件。如果满足条件,则调用长按监听器的回调函数。通过这种方式,长按的确认逻辑被延迟执行,同时主线程可以继续处理其他事件。

PS:使用协程可以避免阻塞主线程(UI线程)。在长按检测中,可能需要等待一段时间来确定用户的触摸是否真的是一个长按动作。如果这个等待直接在主线程上进行,可能会导致界面卡顿或响应变慢。协程通过挂起和恢复机制,可以在不阻塞线程的情况下等待。

                // 长按监听计时
                //调用 `coroutineDelay` 函数来初始化一个协程
                //这个 `coroutineDelay` 函数是一个通用的工具函数,可以用于在协程中实现各种需要延迟执行的场景
                mLongClickJob = coroutineDelay(Main, 500) {
                    //计算了自长按开始以来,当前触摸点与初始触摸点之间的水平和垂直偏移量
                    val offsetX = abs(x - mLastX)
                    val offsetY = abs(y - mLastY)
                    //将 dp(密度无关像素)单位转换为像素单位
                    val offset = dp2px(context, 10f)
                    //如果计算出的偏移量在 10dp 以内,则认为这是一个长按事件,
                    // 然后调用 mLongClickListener 长按监听回调函数
                    if (offsetX <= offset && offsetY <= offset) {
                        mLongClickListener?.invoke(this, PointF(x, y))
                    }
                }


    
            //它接受三个参数:协程的上下文 context,延迟的时间 timeMillis,以及在延迟后要执行的代码块 exec
            internal fun coroutineDelay(
                context: CoroutineContext,
                timeMillis: Long,
                exec: () -> Unit
            ): Job {
                //使用提供的 context 创建一个新的协程作用域
                val scope = CoroutineScope(context)
                //使用 launch 方法启动一个新的协程。            
                return scope.launch(getExceptionHandler("coroutineDelay")) {                  
                    // 在协程中,首先调用 delay 函数使协程挂起指定的时间(毫秒),然后执行传入的 exec 代码块
                    delay(timeMillis)
                    exec()
                }
            }

5、图片显示位置的计算

两个函数都名为 getImageRectF,但具有不同的参数列表,所以这里第二个函数重载了第一个函数,(这里不太明白为什么要用重载,不可以用一个函数处理完吗)

第一个getImageRectF 函数:从 view 中获取当前的 imageMatrix,这是一个 Matrix 类型的对象,表示图像的变换矩阵,return调用getImageRectF 的重载函数

第二个getImageRectF函数 :计算并返回一个 RectF 对象,表示应用了 matrix 变换后的图像边界。

     internal fun getImageRectF(view: ImageView): RectF {
        //从传入的 ImageView 对象中获取其 imageMatrix 属性。
        // imageMatrix 是一个 Matrix 类型的对象,表示当前图像的变换矩阵,包括平移、缩放、旋转等变换。
        val matrix = view.imageMatrix
        return getImageRectF(view, matrix)
    }

    // 获得ImageView中Image的显示边界
    internal fun getImageRectF(view: ImageView, matrix: Matrix): RectF {
        //获取 ImageView 中的 Drawable(图像)的显示边界。
        // bounds 是一个 Rect 对象,表示图像在 ImageView 中的位置和大小
        val bounds = view.drawable.bounds
        //用于存储变换后的边界
        val rectF = RectF()
        matrix.mapRect(
            rectF,
            RectF(
                bounds.left.toFloat(),
                bounds.top.toFloat(),
                bounds.right.toFloat(),
                bounds.bottom.toFloat()
            )
        )
        return rectF
    }

6、Job类

Job 类是协程中用于表示异步操作的一个基本构件。它是 kotlinx.coroutines 库的一部分,用于控制和管理协程的生命周期

  1. 表示协程的生命周期:Job 对象代表一个协程的执行,它可以是挂起的、运行中的或已完成的。
  2. 取消操作:Job 提供了取消协程的能力。如果一个协程被取消,它将抛出一个 CancellationException。
  3. 父子关系:协程可以创建子协程,并且可以管理它们的生命周期。当父协程取消时,所有子协程也会被自动取消。
  4. 状态检查:Job 对象提供了检查其当前状态的方法,例如 isActive, isCompleted, 和 isCancelled。
  5. 等待完成:可以使用 join 方法等待协程完成。如果协程已经被取消,join 会抛出 CancellationException。
  6. 异常处理:如果协程中抛出了异常,它会被封装在 Job 对象中,可以通过 exception 属性访问。
  7. 构建器:Job 类提供了多种构建器方法来创建新的 Job 实例,例如 launch, async, 和 runBlocking。
  8. 调度器:Job 可以与调度器(如线程池或事件循环)关联,以控制协程的执行环境。


二、源码

源码作者GitHub地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值