文章源码来自:容华谢后的博客
一、图片缩放自定义View
博主写的这个自定义控件是继承自 ImageView 所实现的,实现的具体功能有:移动、缩放、两指旋转
在阅读源码之前我觉得有必要先了解以下几点:
1、Matrix 类
Matrix 类是 Android 中的一个类,它用于表示和存储 3x3 矩阵,这个矩阵可以用来对 2D 图形进行几何变换,
比如平移(translate)、缩放(scale)、旋转(rotate)和倾斜(skew)。
在 Android 中,Matrix 类是在自定义视图(View)和图像处理时的一个重要组成部分。
Matrix 类的变换操作:
- postTranslate (float dx, float dy):在矩阵上应用平移变换,平移距离为 (dx, dy)。
- preTranslate (float dx, float dy):在矩阵前应用平移变换。
- postScale (float sx, float sy, float px, float py):在矩阵上应用缩放变换,缩放中心为 (px, py)。
- preScale (float sx, float sy, float px, float py):在矩阵前应用缩放变换。
- postRotate (float degrees, float px, float py):在矩阵上应用旋转变换,旋转中心为 (px, py)。
- preRotate (float degrees, float px, float py):在矩阵前应用旋转变换。
- postSkew (float kx, float ky, float px, float py):在矩阵上应用倾斜变换,倾斜中心为 (px, py)。
- 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 库的一部分,用于控制和管理协程的生命周期
- 表示协程的生命周期:Job 对象代表一个协程的执行,它可以是挂起的、运行中的或已完成的。
- 取消操作:Job 提供了取消协程的能力。如果一个协程被取消,它将抛出一个 CancellationException。
- 父子关系:协程可以创建子协程,并且可以管理它们的生命周期。当父协程取消时,所有子协程也会被自动取消。
- 状态检查:Job 对象提供了检查其当前状态的方法,例如 isActive, isCompleted, 和 isCancelled。
- 等待完成:可以使用 join 方法等待协程完成。如果协程已经被取消,join 会抛出 CancellationException。
- 异常处理:如果协程中抛出了异常,它会被封装在 Job 对象中,可以通过 exception 属性访问。
- 构建器:Job 类提供了多种构建器方法来创建新的 Job 实例,例如 launch, async, 和 runBlocking。
- 调度器:Job 可以与调度器(如线程池或事件循环)关联,以控制协程的执行环境。
二、源码
源码作者GitHub地址: