引言:一图胜千言
图像是移动应用中不可或缺的组成部分。从用户头像到商品图片,从启动图标到表情包,图像传递的信息往往比文字更直观、更有冲击力。然而,在移动应用中处理图像也伴随着一系列挑战:
-
内存占用:一张高分辨率图片可能占据几十 MB 内存
-
网络加载:图片需要从网络下载,必须处理加载中、加载失败等状态
-
缓存策略:避免重复下载和重复解码
-
性能:列表滑动时大量图片同时加载,必须确保流畅
Compose 提供了 Image 组件和 painterResource、rememberImagePainter 等工具来显示图像。但正确使用它们需要理解 Compose 的绘图模型和 Android 的图像处理机制。
本篇将系统讲解 Compose 中的图像与图标:
-
Icon与Image的基础用法 -
从资源、文件、网络加载图像
-
图像缩放与对齐(ContentScale)
-
图片加载库(Coil/Glide)的集成
-
图片占位符与错误处理
-
自定义绘制图像
-
性能优化最佳实践
-
完整实战:图片画廊应用
注意:本篇需要你对 Compose 基础(第 3 篇)、Modifier(第 4 篇)有基本了解。
一、图标(Icon)
1.1 基础 Icon 组件
Icon 用于显示 Material Design 图标,通常用于按钮、菜单等小尺寸图形。
kotlin
import androidx.compose.material3.Icon
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
@Composable
fun BasicIcons() {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Icon(
imageVector = Icons.Default.Home,
contentDescription = "首页"
)
Icon(
imageVector = Icons.Default.Search,
contentDescription = "搜索"
)
Icon(
imageVector = Icons.Default.Settings,
contentDescription = "设置"
)
Icon(
imageVector = Icons.Default.Person,
contentDescription = "个人"
)
}
}
1.2 自定义图标大小和颜色
kotlin
@Composable
fun StyledIcons() {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Icon(
imageVector = Icons.Default.Favorite,
contentDescription = "收藏",
tint = Color.Red,
modifier = Modifier.size(48.dp)
)
Icon(
imageVector = Icons.Default.Star,
contentDescription = "星标",
tint = Color.Yellow,
modifier = Modifier.size(32.dp)
)
Icon(
imageVector = Icons.Default.Delete,
contentDescription = "删除",
tint = MaterialTheme.colorScheme.error,
modifier = Modifier.size(24.dp)
)
}
}
1.3 使用自定义图标
kotlin
@Composable
fun CustomIcon() {
// 从矢量图资源加载
Icon(
painter = painterResource(R.drawable.ic_custom),
contentDescription = "自定义图标",
modifier = Modifier.size(32.dp)
)
}
二、Image 基础
2.1 从资源加载图片
kotlin
@Composable
fun ImageFromResource() {
Image(
painter = painterResource(R.drawable.sample_image),
contentDescription = "示例图片",
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
)
}
2.2 从文件加载图片
kotlin
@Composable
fun ImageFromFile(file: File) {
val bitmap = BitmapFactory.decodeFile(file.absolutePath)
val imageBitmap = bitmap?.asImageBitmap()
if (imageBitmap != null) {
Image(
bitmap = imageBitmap,
contentDescription = "从文件加载的图片",
modifier = Modifier.fillMaxSize()
)
}
}
2.3 从网络加载图片
Compose 本身不包含网络图片加载功能,需要配合第三方库。推荐使用 Coil 或 Glide。
使用 Coil:
添加依赖:
kotlin
implementation("io.coil-kt:coil-compose:2.6.0")
kotlin
import coil.compose.AsyncImage
@Composable
fun ImageFromNetwork() {
AsyncImage(
model = "https://example.com/image.jpg",
contentDescription = "网络图片",
modifier = Modifier
.fillMaxWidth()
.height(200.dp),
error = painterResource(R.drawable.ic_error), // 加载失败显示
placeholder = painterResource(R.drawable.ic_loading) // 加载中显示
)
}
三、图片缩放与对齐
ContentScale 控制图片如何在其容器中缩放和对齐。
| ContentScale | 效果 |
|---|---|
Fit |
保持比例,完整显示图片(可能留白) |
Crop |
保持比例,填满容器(可能裁剪) |
FillBounds |
拉伸图片填满容器(可能变形) |
Inside |
图片完整显示在容器内(不放大) |
FillWidth |
宽度填满,高度按比例 |
FillHeight |
高度填满,宽度按比例 |
None |
原始尺寸,不缩放 |
kotlin
@Composable
fun ContentScaleExamples() {
Column {
// Fit - 完整显示
Image(
painter = painterResource(R.drawable.landscape),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.height(150.dp)
.background(Color.LightGray),
contentScale = ContentScale.Fit
)
// Crop - 填满裁剪
Image(
painter = painterResource(R.drawable.landscape),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.height(150.dp)
.background(Color.LightGray),
contentScale = ContentScale.Crop
)
// FillBounds - 拉伸变形
Image(
painter = painterResource(R.drawable.landscape),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.height(150.dp)
.background(Color.LightGray),
contentScale = ContentScale.FillBounds
)
}
}
四、图片修饰与样式
4.1 圆角图片
kotlin
@Composable
fun RoundedImage() {
Image(
painter = painterResource(R.drawable.avatar),
contentDescription = "头像",
modifier = Modifier
.size(80.dp)
.clip(CircleShape) // 圆形裁剪
)
}
@Composable
fun RoundedCornerImage() {
Image(
painter = painterResource(R.drawable.image),
contentDescription = "图片",
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.clip(RoundedCornerShape(16.dp))
)
}
4.2 图片叠加层
kotlin
@Composable
fun ImageWithOverlay() {
Box(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
) {
Image(
painter = painterResource(R.drawable.landscape),
contentDescription = "背景",
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Crop
)
// 半透明遮罩
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.4f))
)
// 文字
Text(
text = "美丽的风景",
color = Color.White,
fontSize = 24.sp,
modifier = Modifier
.align(Alignment.Center)
.padding(16.dp)
)
}
}
4.3 图片滤镜(ColorFilter)
kotlin
@Composable
fun ImageWithColorFilter() {
Column {
// 灰度效果
Image(
painter = painterResource(R.drawable.image),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.height(150.dp),
colorFilter = ColorFilter.luminance()
)
// 着色
Image(
painter = painterResource(R.drawable.image),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.height(150.dp),
colorFilter = ColorFilter.tint(Color.Blue, BlendMode.Multiply)
)
// 透明度
Image(
painter = painterResource(R.drawable.image),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.height(150.dp)
.alpha(0.5f)
)
}
}
五、性能优化最佳实践
5.1 选择合适的图片尺寸
kotlin
// ❌ 不推荐:加载原始大图
val bitmap = BitmapFactory.decodeFile(largeImagePath)
Image(bitmap = bitmap.asImageBitmap(), ...)
// ✅ 推荐:按需加载缩略图
val options = BitmapFactory.Options().apply {
inSampleSize = 4 // 缩小为原图的 1/4
}
val bitmap = BitmapFactory.decodeFile(imagePath, options)
5.2 使用 remember 缓存 Bitmap
kotlin
@Composable
fun CachedImage(imagePath: String) {
val bitmap by remember(imagePath) {
mutableStateOf(
BitmapFactory.decodeFile(imagePath)?.asImageBitmap()
)
}
if (bitmap != null) {
Image(
bitmap = bitmap!!,
contentDescription = null,
modifier = Modifier.fillMaxSize()
)
}
}
5.3 在 LazyColumn 中使用 AsyncImage
kotlin
@Composable
fun ImageGrid(images: List<String>) {
LazyVerticalGrid(
columns = GridCells.Fixed(3),
contentPadding = PaddingValues(4.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
items(images) { url ->
AsyncImage(
model = url,
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f)
.clip(RoundedCornerShape(8.dp)),
contentScale = ContentScale.Crop,
// 关键:启用内存缓存
memoryCacheKey = url
)
}
}
}
六、完整实战:图片画廊
kotlin
@Composable
fun ImageGallery() {
val images = remember {
(1..50).map { "https://picsum.photos/id/${it + 10}/400/400" }
}
val selectedImage = remember { mutableStateOf<String?>(null) }
Scaffold(
topBar = { TopAppBar(title = { Text("图片画廊") }) }
) { paddingValues ->
LazyVerticalGrid(
columns = GridCells.Fixed(3),
modifier = Modifier.padding(paddingValues),
contentPadding = PaddingValues(4.dp),
horizontalArrangement = Arrangement.spacedBy(4.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
items(images.size) { index ->
AsyncImage(
model = images[index],
contentDescription = "图片 $index",
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f)
.clip(RoundedCornerShape(8.dp))
.clickable { selectedImage.value = images[index] },
contentScale = ContentScale.Crop,
placeholder = painterResource(R.drawable.ic_loading),
error = painterResource(R.drawable.ic_error)
)
}
}
}
// 全屏预览
selectedImage.value?.let { url ->
FullScreenImagePreview(
imageUrl = url,
onDismiss = { selectedImage.value = null }
)
}
}
@Composable
fun FullScreenImagePreview(
imageUrl: String,
onDismiss: () -> Unit
) {
Dialog(
onDismissRequest = onDismiss,
properties = DialogProperties(usePlatformDefaultWidth = false)
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black)
.clickable(onClick = onDismiss)
) {
AsyncImage(
model = imageUrl,
contentDescription = null,
modifier = Modifier.fillMaxSize(),
contentScale = ContentScale.Fit
)
IconButton(
onClick = onDismiss,
modifier = Modifier
.align(Alignment.TopEnd)
.padding(16.dp)
) {
Icon(
Icons.Default.Close,
contentDescription = "关闭",
tint = Color.White
)
}
}
}
}
七、常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 图片太大导致 OOM | 加载了超过屏幕分辨率的图片 | 使用 inSampleSize 压缩 |
| 列表滑动卡顿 | 大量图片同时加载 | 使用图片加载库的缓存机制 |
| 图片闪烁 | 没有正确使用 key | 为 LazyColumn 中的图片指定稳定 key |
| 网络图片不显示 | URL 错误或网络问题 | 检查 URL 格式,添加 placeholder 和 error |
| 图片变形 | ContentScale 设置不当 | 选择合适的 ContentScale |
练习题
-
题一:创建一个圆形头像组件,支持图片、默认图标、首字母三种显示方式。
-
题二:实现一个图片选择器,点击图片可全屏预览,支持左右滑动切换。
-
题三:为画廊应用添加缓存策略,使用 Coil 的内存和磁盘缓存。
-
题四:实现图片下载进度指示,下载过程中显示进度百分比。
-
题五:创建一个懒加载的水平图片轮播组件(Carousel)。
写在最后
图像处理是移动开发中绕不开的话题。Compose 的 Image 组件简洁而强大,配合 Coil 或 Glide 等图片加载库,可以轻松处理从网络加载、缓存到显示的全流程。
本篇是 Compose 入门教程的第三十四篇。掌握图像处理,你的应用将能够展示丰富的内容,给用户带来更好的视觉体验。
下一篇文章见。