媒体缩略图为用户提供了图像和视频的快速视觉预览,便于更快地浏览,同时使应用界面更具视觉吸引力和互动性。由于缩略图比全尺寸媒体小,因此有助于节省内存、存储空间和带宽,同时提高媒体浏览性能。
根据文件类型以及您在应用和媒体资产中拥有的文件访问权限,您可以通过多种不同的方式创建缩略图。
使用图像加载库创建缩略图
图像加载库为您完成了大量繁重的工作;它们可以处理缓存以及根据 Uri 从本地或网络资源获取源媒体的逻辑。以下代码演示了 Coil 图像加载库如何用于图像和视频,以及如何在本地或网络资源上工作。
// Use Coil to create and display a thumbnail of a video or image with a specific height
// ImageLoader has its own memory and storage cache, and this one is configured to also
// load frames from videos
val videoEnabledLoader = ImageLoader.Builder(context)
.components {
add(VideoFrameDecoder.Factory())
}.build()
// Coil requests images that match the size of the AsyncImage composable, but this allows
// for precise control of the height
val request = ImageRequest.Builder(context)
.data(mediaUri)
.size(Int.MAX_VALUE, THUMBNAIL_HEIGHT)
.build()
AsyncImage(
model = request,
imageLoader = videoEnabledLoader,
modifier = Modifier
.clip(RoundedCornerShape(20)) ,
contentDescription = null
)
**如果可能,请在服务器端创建缩略图。** 有关如何使用 Compose 加载图像的详细信息,请参阅 加载图像,有关如何处理大型图像的指南,请参阅 高效加载大型位图。
从本地图像文件创建缩略图
获取缩略图图像涉及高效缩小尺寸同时保持视觉质量、避免过度使用内存、处理各种图像格式以及正确使用 Exif 数据。
如果您有权访问图像文件的路径,则 createImageThumbnail
方法可以完成所有这些操作。
val bitmap = ThumbnailUtils.createImageThumbnail(File(file_path), Size(640, 480), null)
如果您只有 Uri
,则可以从 Android 10(API 级别 29)开始,在 ContentResolver 中使用 loadThumbnail
方法。
val thumbnail: Bitmap =
applicationContext.contentResolver.loadThumbnail(
content-uri, Size(640, 480), null)
从 Android 9(API 级别 28)开始可用的 ImageDecoder 提供了一些可靠的选项,可以在解码图像时对其进行重新采样,以防止额外的内存使用。
class DecodeResampler(val size: Size, val signal: CancellationSignal?) : OnHeaderDecodedListener {
private val size: Size
override fun onHeaderDecoded(decoder: ImageDecoder, info: ImageInfo, source:
// sample down if needed.
val widthSample = info.size.width / size.width
val heightSample = info.size.height / size.height
val sample = min(widthSample, heightSample)
if (sample > 1) {
decoder.setTargetSampleSize(sample)
}
}
}
val resampler = DecoderResampler(size, null)
val source = ImageDecoder.createSource(context.contentResolver, imageUri)
val bitmap = ImageDecoder.decodeBitmap(source, resampler);
您可以使用 BitmapFactory 为面向早期 Android 版本的应用创建缩略图。BitmapFactory.Options 具有一个设置,用于仅解码图像的边界以进行重新采样。
首先,仅将位图的边界解码到 BitmapFactory.Options
中
private fun decodeResizedBitmap(context: Context, uri: Uri, size: Size): Bitmap?{
val boundsStream = context.contentResolver.openInputStream(uri)
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeStream(boundsStream, null, options)
boundsStream?.close()
使用 BitmapFactory.Options
中的 width
和 height
设置采样大小
if ( options.outHeight != 0 ) {
// we've got bounds
val widthSample = options.outWidth / size.width
val heightSample = options.outHeight / size.height
val sample = min(widthSample, heightSample)
if (sample > 1) {
options.inSampleSize = sample
}
}
解码流。根据 inSampleSize
,结果图像的大小将以 2 的幂进行采样。
options.inJustDecodeBounds = false
val decodeStream = context.contentResolver.openInputStream(uri)
val bitmap = BitmapFactory.decodeStream(decodeStream, null, options)
decodeStream?.close()
return bitmap
}
从本地视频文件创建缩略图
获取视频缩略图图像涉及到与获取图像缩略图类似的许多挑战,但文件大小可能大得多,并且获取有代表性的视频帧并不像选择视频的第一帧那样简单。
如果您能够访问视频文件的路径,则createVideoThumbnail
方法是一个不错的选择。
val bitmap = ThumbnailUtils.createVideoThumbnail(File(file_path), Size(640, 480), null)
如果您只能访问内容 Uri,则可以使用MediaMetadataRetriever
。
首先,检查视频是否包含嵌入式缩略图,如果可以,请使用该缩略图。
private suspend fun getVideoThumbnailFromMediaMetadataRetriever(context: Context, uri: Uri, size: Size): Bitmap? {
val mediaMetadataRetriever = MediaMetadataRetriever()
mediaMetadataRetriever.setDataSource(context, uri)
val thumbnailBytes = mediaMetadataRetriever.embeddedPicture
val resizer = Resizer(size, null)
ImageDecoder.createSource(context.contentResolver, uri)
// use a built-in thumbnail if the media file has it
thumbnailBytes?.let {
return ImageDecoder.decodeBitmap(ImageDecoder.createSource(it));
}
从MediaMetadataRetriever
获取视频的宽度和高度以计算缩放因子。
val width = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)
?.toFloat() ?: size.width.toFloat()
val height = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)
?.toFloat() ?: size.height.toFloat()
val widthRatio = size.width.toFloat() / width
val heightRatio = size.height.toFloat() / height
val ratio = max(widthRatio, heightRatio)
在 Android 9 及更高版本(API 级别 28)上,MediaMetadataRetriever
可以返回缩放后的帧。
if (ratio > 1) {
val requestedWidth = width * ratio
val requestedHeight = height * ratio
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val frame = mediaMetadataRetriever.getScaledFrameAtTime(
-1, OPTION_PREVIOUS_SYNC,
requestedWidth.toInt(), requestedHeight.toInt())
mediaMetadataRetriever.close()
return frame
}
}
否则,返回未缩放的第一帧。
// consider scaling this after the fact
val frame = mediaMetadataRetriever.frameAtTime
mediaMetadataRetriever.close()
return frame
}