0%

Glide自定义CropTransformation


裁剪是Android开发中一个常见的需求,虽然Android的ImageView的scaleType属性提供了多种图片的展示方式,其中包括一些裁剪方式,以及Glide自带的Transformation也提供了常见的圆角,圆形,居中裁剪,但有的时候仍然不能满足我们的需求,比如说为了版本迭代老版本apk能兼容新版本的图片资源,UI迭代的情况下产品会提出裁剪图片保留图片底部区域的规则,这时候ImageView和Glide自带的裁剪就不能满足需求了,需要我们自定义Transformation实现新的裁剪规则


裁剪类型

定义枚举:顶部裁剪,居中裁剪,底部裁剪

1
2
3
enum class CropType {
TOP, CENTER, BOTTOM
}

实线BitmapTransformation接口

默认居中裁剪,支持传入自定义的裁剪像素宽高,重写必要的方法,updateDiskCacheKey,transform,equals,hashCode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private val TAG: String = CropTransformation::class.java.name

class CropTransformation(var width: Int = 0, var height: Int = 0, var cropType: CropType = CropType.CENTER) : BitmapTransformation() {

override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update(TAG.toByteArray(CHARSET))
}

override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
...
}

override fun equals(other: Any?): Boolean {
return other is CropTransformation && other.width == width && other.height == height && other.cropType == cropType
}

override fun hashCode(): Int {
return TAG.hashCode()
}
}

实现核心方法transform

  • transform的入参为,图片复用线程池pool,下载后要处理的图片toTransform,目标View的宽高

  • 当宽或高为0时默认使用图片的宽或高

  • 图片格式默认采用要处理图片的格式,否则使用ARGB_8888

  • 从当前图片缓存池中取得一张复用的图片进行处理,打开alpha通道

  • 计算裁剪的宽和图片的宽之比,以及高之比,取大的那个

  • 计算缩放后的宽和高

  • 计算绘制的开始位置,x和y坐标,x坐标为(当前裁剪的宽度 - 缩放后的宽度)/ 2,从水平居中区域开始绘制,y坐标根据当前的裁剪类型而定,TOP从0开始,CENTER为(当前裁剪的高度 - 缩放后的高度)/ 2,从垂直居中区域开始,BOTTOM直接裁剪底部区域,结束位置的x坐标为开始位置的x坐标 + 缩放后的宽度,y坐标为开始位置的y坐标 + 缩放后的高度,最后计算出绘制的矩形区域,设置从当前图片缓存池中取得的图片密度为要处理的图片toTransform的密度,利用复用的图片创建canvas,drawBitmap把要处理的图片toTransform绘制到canvas的目标矩形区域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
width = if (width == 0) toTransform.width else width
height = if (height == 0) toTransform.height else height

val config = if (toTransform.config != null) toTransform.config else Bitmap.Config.ARGB_8888
val bitmap = pool[width, height, config]

bitmap.setHasAlpha(true)

val scaleX = width.toFloat() / toTransform.width
val scaleY = height.toFloat() / toTransform.height
val scale = max(scaleX, scaleY)

val scaledWidth = scale * toTransform.width
val scaledHeight = scale * toTransform.height
val left = (width - scaledWidth) / 2
val top: Float = getTop(scaledHeight)
val targetRect = RectF(left, top, left + scaledWidth, top + scaledHeight)

bitmap.density = toTransform.density
val canvas = Canvas(bitmap)
canvas.drawBitmap(toTransform, null, targetRect, null)

return bitmap
}

private fun getTop(scaledHeight: Float): Float {
return when (cropType) {
CropType.TOP -> 0f
CropType.CENTER -> (height - scaledHeight) / 2
CropType.BOTTOM -> height - scaledHeight
}
}

完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package com.kubi.kucoin.utils

import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.RectF
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import java.security.MessageDigest
import kotlin.math.max

/**
* @description: 图片裁剪
* @author: Jessie.Li
* @email: jessie.li@corp.kucoin.com
* @create: 2022-01-05 17:47
**/
private val TAG: String = CropTransformation::class.java.name

class CropTransformation(var width: Int = 0, var height: Int = 0, var cropType: CropType = CropType.CENTER) : BitmapTransformation() {
enum class CropType {
TOP, CENTER, BOTTOM
}

override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update(TAG.toByteArray(CHARSET))
}

override fun transform(pool: BitmapPool, toTransform: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
width = if (width == 0) toTransform.width else width
height = if (height == 0) toTransform.height else height

val config = if (toTransform.config != null) toTransform.config else Bitmap.Config.ARGB_8888
val bitmap = pool[width, height, config]

bitmap.setHasAlpha(true)

val scaleX = width.toFloat() / toTransform.width
val scaleY = height.toFloat() / toTransform.height
val scale = max(scaleX, scaleY)

val scaledWidth = scale * toTransform.width
val scaledHeight = scale * toTransform.height
val left = (width - scaledWidth) / 2
val top: Float = getTop(scaledHeight)
val targetRect = RectF(left, top, left + scaledWidth, top + scaledHeight)

bitmap.density = toTransform.density
val canvas = Canvas(bitmap)
canvas.drawBitmap(toTransform, null, targetRect, null)

return bitmap
}

private fun getTop(scaledHeight: Float): Float {
return when (cropType) {
CropType.TOP -> 0f
CropType.CENTER -> (height - scaledHeight) / 2
CropType.BOTTOM -> height - scaledHeight
}
}

override fun equals(other: Any?): Boolean {
return other is CropTransformation && other.width == width && other.height == height && other.cropType == cropType
}

override fun hashCode(): Int {
return TAG.hashCode()
}
}