Android 开发入门教程(第三十四篇):Compose 中的图像与图标 —— 从基础显示到性能优化

引言:一图胜千言

图像是移动应用中不可或缺的组成部分。从用户头像到商品图片,从启动图标到表情包,图像传递的信息往往比文字更直观、更有冲击力。然而,在移动应用中处理图像也伴随着一系列挑战:

  • 内存占用:一张高分辨率图片可能占据几十 MB 内存

  • 网络加载:图片需要从网络下载,必须处理加载中、加载失败等状态

  • 缓存策略:避免重复下载和重复解码

  • 性能:列表滑动时大量图片同时加载,必须确保流畅

Compose 提供了 Image 组件和 painterResourcerememberImagePainter 等工具来显示图像。但正确使用它们需要理解 Compose 的绘图模型和 Android 的图像处理机制。

本篇将系统讲解 Compose 中的图像与图标:

  • IconImage 的基础用法

  • 从资源、文件、网络加载图像

  • 图像缩放与对齐(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 本身不包含网络图片加载功能,需要配合第三方库。推荐使用 CoilGlide

使用 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

练习题

  1. 题一:创建一个圆形头像组件,支持图片、默认图标、首字母三种显示方式。

  2. 题二:实现一个图片选择器,点击图片可全屏预览,支持左右滑动切换。

  3. 题三:为画廊应用添加缓存策略,使用 Coil 的内存和磁盘缓存。

  4. 题四:实现图片下载进度指示,下载过程中显示进度百分比。

  5. 题五:创建一个懒加载的水平图片轮播组件(Carousel)。

写在最后

图像处理是移动开发中绕不开的话题。Compose 的 Image 组件简洁而强大,配合 Coil 或 Glide 等图片加载库,可以轻松处理从网络加载、缓存到显示的全流程。

本篇是 Compose 入门教程的第三十四篇。掌握图像处理,你的应用将能够展示丰富的内容,给用户带来更好的视觉体验。

下一篇文章见。

相关推荐
上天_去_做颗惺星 EVE_BLUE4 天前
Ubuntu Android 虚拟机安装使用教程
android·linux·测试工具·ubuntu·安卓
我命由我123454 天前
Android 开发问题:Could not find com.github.PicnicSupermarket:FingerPaintView:1.2.
android·github·android studio·安卓·android jetpack·android-studio·android runtime
Andy Wee5 天前
红米K40 BootLoader 解锁完整教程(避坑版)
安卓
vensli6 天前
消息跨端架构演进:基于 C++ 的多端一致性研发框架实践
java·人工智能·软件工程·安卓
vensli7 天前
来自 Android14 的“酷炫惊喜动画”——记录一次安卓动画缓存问题的排查过程
安卓
会Tk矩阵群控的小木11 天前
rcs安卓增强短信群发系统搭建与API集成实战教程
矩阵·新媒体运营·安卓·个人开发·tk
Java小学生丶11 天前
记录一下我的 Gradle 开发环境配置过程
android·java·gradle·maven·安卓
therese_1008615 天前
客户端设计(下):场景流派与实战设计方式
架构·安卓·鸿蒙
therese_1008617 天前
客户端架构:为什么、什么时候、怎么做
设计模式·安卓·鸿蒙