转载请注明出处
使用场景
在日常开发中,经常会遇到的一个需求是将图片/视频从界面的一个位置,变换到另一个位置。在处理这类问题的时候经常会遇到的一个问题就是比例变化,如果两个位置的宽高比例不一致,如果不做任何处理直接将图像显示到另一个位置图像就会被拉伸。如果使用的是 ImageView 的话,可以设置 scaleType 去选择一个合适的填充方式,但是一些情况下,使用的不是 ImageView 。
以 Android 开发中的相机预览图处理为例,我们通过下面的代码去获取相机的预览图,在手机上,我们获取分辨率,都会有很多分辨率可选:
这里我截取我手机上断点看到的分辨率的可选的列表:
可选择的分辨率很多,一共有 47 个。以 1280*720 ,和 1280*960 两个格式预览图为例,最后换算得到的分辨率的比例分别是 16:9 和 4:3 这两个比例。
因为相机预览图获取到的是的时候横着的,需要这里的比例可以理解为预览图的比例可以理解为 height/width。使用预览图上屏的时需要旋转 90 度。
下面以相机预览图上屏为例进行问题解决,如果不进行处理,不按比例缩放上屏画面一定会被拉伸。
等比变换
而 Android 手机机型众多,屏幕纵横比也会有很多值,例如有 16 : 9,4 : 3,13 : 6 等多种比例。
等高填充
以一个 16 : 9 纵横比的手机为例,当我们获取到的预览图的比例是 4 : 3 的话,如何把这张 4 : 3 的图片贴到屏幕上避免图片被拉伸呢。
4 : 3 的比例指的宽高比是 3 : 4
在保持图片比例的情况下,
- 等高填充:宽度会有一部分超出。
- 等宽填充:屏幕高度部分会有部分剩余。
等宽填充
那反过来,我们将一个 16 : 9 的预览图放到 4 : 3 的屏幕上,按照不同的变换方式,我们可以得到下面两种变换结果。
在保持图片比例的情况下,
- 等高填充:屏幕宽度会有部分剩余。
- 等宽填充:高度部分会有一些超出。
代码进行比目标宽高计算
因为变换的时候我们的目标都是铺满目标区域,因此我们在变换的时候,采用上面铺满屏幕的变换方式,因此有下面的变换公式,这段代码在 Android 相机处理官方 Demo 中有存在:
// 预览图变换后的新的 宽高
val newWidth: Int
val newHeight: Int
val actualRatio = if (width > height) aspectRatio else 1f / aspectRatio
// 目标区域的宽高比例 和 实际的图片的比例 相比较
if (width < height * actualRatio) {
// 对应上面的 16:9 的手机,填充 4:3 的图片
newHeight = height
newWidth = (height * actualRatio).roundToInt()
} else {
// 对应上面的 4:3 的手机,填充 16:9 的
newWidth = width
newHeight = (width / actualRatio).roundToInt()
}
上面的代码在对应的地方都加了注释,经过上面的代码我们可以计算得到一个新的宽高。
超出部分处理
在上面计算的变换后的宽高后,超出的部分我们要进行相应的处理,确保画面显示正常
设置负的 margin
以宽超出为例,我们需要设置负的 leftMargin:
如果是高度部分超出的话,就设置对应的负的 topMargin
leftMargin = -(newWidth-originWidth) / 2;
将整体 View 偏移一个 负的距离后,就可以实现整体居中,画面回比较正常。
clip 裁剪
如果没办法将 View 的一部分偏移到界面的不可见部分,那么就将超出的部分裁剪不显示,确保画面的正常。
Android 裁剪方式:参考自己这篇博客中的 clipChildren 默认是 true 进行裁剪
https://blog.csdn.net/liu_12345_liu/article/details/111822877?spm=1001.2014.3001.5502#clipToPadding_1
鸿蒙上的裁剪方式:
默认为 false ,需要手动设置为 true 。
End
到这里,使用上面的方式,已经可以实现将图片不拉伸显示在界面中,当然如果要避免图片放大后画面模糊,尽量选择一个像素不是那么少的图片进行放大,缩小的话那就不牵扯模糊的问题。
参考:
android platform sample https://github.com/android/platform-samples/tree/main