在 Android 开发中,我们常常需要对图片进行缩放的处理,对于这种需求我们可以直接使用 ImageView 的 scaleType 属性来实现。但是,对于指定高度或者宽度来适配的情况,scaleType 就无法精细控制了。
比如 FIT_START
、FIT_CENTER
和 FIT_END
、CENTER_INSIDE
虽然会缩放图片,但是其是根据长边来缩放的,目的是确保完整显示;CENTER
则是只居中不缩放; CENTER_CROP
是根据短边来缩放的。这时候就需要我们来自定义View来实现了。
指定宽高适配的 ImageView
实现方式也很简单,核心步骤如下:
- 第一步:确定是按照 高度适配 还是 宽度适配,计算出缩放比例
kotlin
// 定义不同的缩放类型枚举
enum class ScaleType {
NONE, // 不进行任何缩放
WIDTH_FIT, // 根据宽度缩放
HEIGHT_FIT, // 根据高度缩放
ASPECT_FIT // 根据比例缩放
}
// 原图的Rect信息
private val srcRect = Rect(0, 0, 0, 0)
// 目标的Rect信息
private val targetRect = Rect(0, 0, 0, 0)
// 计算原始图片宽度相对于目标宽度的缩放比例
val scaleX = targetWidth.toFloat() / bitmapWidth
// 计算原始图片高度相对于目标高度的缩放比例
val scaleY = targetHeight.toFloat() / bitmapHeight
srcRect.set(0, 0, bitmapWidth, bitmapHeight)
when (scaleType) {
ScaleType.NONE -> {
targetRect.set(srcRect)
}
ScaleType.WIDTH_FIT -> {
targetRect.set(0, 0, targetWidth, (bitmapHeight * scaleX).toInt())
}
ScaleType.HEIGHT_FIT -> {
targetRect.set(0, 0, (bitmapWidth * scaleY).toInt(), targetHeight)
}
ScaleType.ASPECT_FIT -> {
val scale = max(scaleX, scaleY)
targetRect.set(0, 0, (bitmapWidth * scale).toInt(), (bitmapHeight * scale).toInt())
}
else -> {}
}
- 第二步:确定裁剪方式,是居中裁剪、还是左上角裁剪等等,计算出偏移位置
kotlin
enum class CropType {
CENTER, // 居中裁剪
TOP_LEFT, // 从左上角开始裁剪
TOP_RIGHT, // 从右上角开始裁剪
BOTTOM_LEFT, // 从左下角开始裁剪
BOTTOM_RIGHT, // 从右下角开始裁剪
}
var left = 0f
var top = 0f
when (cropType) {
CropType.CENTER -> {
left = (width - targetRect.width()) / 2f
top = (height - targetRect.height()) / 2f
}
CropType.TOP_LEFT -> {
left = 0f
top = 0f
}
CropType.TOP_RIGHT -> {
left = (width - targetRect.width()).toFloat()
top = 0f
}
CropType.BOTTOM_LEFT -> {
left = 0f
top = (height - targetRect.height()).toFloat()
}
CropType.BOTTOM_RIGHT -> {
left = (width - targetRect.width()).toFloat()
top = (height - targetRect.height()).toFloat()
}
else -> {}
}
- 最后根据缩放比例和偏移位置绘制图片
kotlin
// 保存当前画布状态
canvas.save()
// 平移画布到指定位置
canvas.translate(left, top)
// 在画布上绘制图片
canvas.drawBitmap(bitmap, srcRect, targetRect, null)
// 恢复画布状态
canvas.restore()
效果如下图所示:
完整代码
kotlin
class FitWidthAndHeightImageView @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attributeSet, defStyleAttr) {
// 存储要显示的图片
private var bitmap: Bitmap? = null
// 目标宽度
private var targetWidth = 0
// 目标高度
private var targetHeight = 0
// 缩放类型
private var scaleType: ScaleType = ScaleType.NONE
// 裁剪类型
private var cropType: CropType = CropType.CENTER
private val srcRect = Rect(0, 0, 0, 0)
private val targetRect = Rect(0, 0, 0, 0)
// 定义不同的缩放类型枚举
enum class ScaleType {
NONE, // 不进行任何缩放
WIDTH_FIT, // 根据宽度缩放
HEIGHT_FIT, // 根据高度缩放
ASPECT_FIT // 根据比例缩放
}
enum class CropType {
CENTER, // 居中裁剪
TOP_LEFT, // 从左上角开始裁剪
TOP_RIGHT, // 从右上角开始裁剪
BOTTOM_LEFT, // 从左下角开始裁剪
BOTTOM_RIGHT, // 从右下角开始裁剪
}
// 设置图片资源的方法
fun setImageResource(@DrawableRes resId: Int) {
bitmap = BitmapFactory.decodeResource(resources, resId)
// 触发重绘
invalidate()
}
fun setImageBitmap(srcBitmap: Bitmap) {
bitmap = srcBitmap
invalidate()
}
fun setScaleType(type: ScaleType) {
setScaleAndCropType(type, cropType)
}
fun setCropType(crop: CropType) {
setScaleAndCropType(scaleType, crop)
}
// 设置缩放类型的方法
fun setScaleAndCropType(type: ScaleType, crop: CropType) {
scaleType = type
cropType = crop
// 触发重绘
invalidate()
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
targetWidth = w
targetHeight = h
invalidate()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) {
// 如果没有指定当前View的size
val bitmap = bitmap
val measuredWidth = when {
scaleType == ScaleType.NONE && bitmap!= null -> bitmap.width
bitmap == null && targetWidth > 0 -> targetWidth
else -> 0 // 默认大小,可以根据实际需求调整
}
val measuredHeight = when {
scaleType == ScaleType.NONE && bitmap!= null -> bitmap.height
bitmap == null && targetHeight > 0 -> targetHeight
else -> 0 // 默认大小,可以根据实际需求调整
}
setMeasuredDimension(measuredWidth, measuredHeight)
} else {
setMeasuredDimension(widthSize, heightSize)
}
}
// 重写 onDraw 方法进行绘制操作
override fun onDraw(canvas: Canvas) {
// 如果图片为空或者目标宽度、高度为 0,则不进行绘制
val bitmap = bitmap
val targetHeight = targetHeight
val targetWidth = targetWidth
val bitmapWidth = bitmap?.width ?: 0
val bitmapHeight = bitmap?.height ?: 0
if (bitmap == null || targetWidth <= 0 || targetHeight <= 0 || bitmapWidth <= 0 || bitmapHeight <= 0) {
return
}
val viewWidth = width
val viewHeight = height
println("bitmapWidth $bitmapWidth bitmapHeight $bitmapHeight viewWidth = $viewWidth viewHeight $viewHeight")
// 计算原始图片宽度相对于目标宽度的缩放比例
val scaleX = targetWidth.toFloat() / bitmapWidth
// 计算原始图片高度相对于目标高度的缩放比例
val scaleY = targetHeight.toFloat() / bitmapHeight
println("scaleX = $scaleX scaleY = $scaleY")
srcRect.set(0, 0, bitmapWidth, bitmapHeight)
when (scaleType) {
ScaleType.NONE -> {
targetRect.set(srcRect)
}
ScaleType.WIDTH_FIT -> {
targetRect.set(0, 0, targetWidth, (bitmapHeight * scaleX).toInt())
}
ScaleType.HEIGHT_FIT -> {
targetRect.set(0, 0, (bitmapWidth * scaleY).toInt(), targetHeight)
}
ScaleType.ASPECT_FIT -> {
val scale = max(scaleX, scaleY)
targetRect.set(0, 0, (bitmapWidth * scale).toInt(), (bitmapHeight * scale).toInt())
}
else -> {}
}
var left = 0f
var top = 0f
when (cropType) {
CropType.CENTER -> {
left = (width - targetRect.width()) / 2f
top = (height - targetRect.height()) / 2f
}
CropType.TOP_LEFT -> {
left = 0f
top = 0f
}
CropType.TOP_RIGHT -> {
left = (width - targetRect.width()).toFloat()
top = 0f
}
CropType.BOTTOM_LEFT -> {
left = 0f
top = (height - targetRect.height()).toFloat()
}
CropType.BOTTOM_RIGHT -> {
left = (width - targetRect.width()).toFloat()
top = (height - targetRect.height()).toFloat()
}
else -> {}
}
// 保存当前画布状态
canvas.save()
// 平移画布到指定位置
canvas.translate(left, top)
// 在画布上绘制图片
canvas.drawBitmap(bitmap, srcRect, targetRect, null)
// 恢复画布状态
canvas.restore()
}
}