上一期:Android Compose 框架图像与矢量图深入剖析(五十三)
下一期:Android Compose 框架尺寸与密度深入剖析(五十五)
本人掘金号,欢迎点击关注:https://juejin.cn/user/4406498335701950
一、引言
在 Android 开发中,图像和矢量图的处理是构建美观、交互性强的用户界面的重要组成部分。随着 Android Compose 框架的出现,为开发者提供了一种全新的声明式方式来处理图像和矢量图。Android Compose 以其简洁、高效和灵活的特点,极大地简化了图像和矢量图的使用和管理。
本博客将深入分析 Android Compose 框架中图像与矢量图的相关内容,从基础概念开始,逐步深入到源码级别,详细介绍图像和矢量图的加载、显示、绘制、优化等方面。通过对源码的分析,我们可以更好地理解 Android Compose 是如何实现这些功能的,以及如何在实际开发中更好地利用这些功能来构建出色的用户界面。
二、Android Compose 中图像与矢量图的基础概念
2.1 图像(Image)
在 Android Compose 中,图像是指各种格式的位图(Bitmap),如 JPEG、PNG 等。图像可以从不同的来源加载,包括本地资源、网络、文件系统等。Compose 提供了 Image
组件来显示图像,它可以根据需要进行缩放、裁剪、变换等操作。
2.2 矢量图(Vector Graphics)
矢量图是使用数学公式来描述图形的图像,与位图不同,矢量图可以无损缩放,不会出现锯齿或模糊的情况。在 Android Compose 中,矢量图通常以 XML 文件的形式存在,使用 VectorPainter
来绘制。矢量图适用于需要高分辨率显示和动态调整大小的场景,如图标、图形化界面等。
三、图像的加载与显示
3.1 使用 Image
组件显示本地资源图像
在 Android Compose 中,最简单的显示图像的方式是使用 Image
组件显示本地资源图像。以下是一个示例代码:
kotlin
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
@Composable
fun LocalResourceImage() {
// 使用 painterResource 函数从资源中获取图像的 Painter
val painter = painterResource(id = R.drawable.sample_image)
// 使用 Image 组件显示图像
Image(
painter = painter, // 设置图像的 Painter
contentDescription = "本地资源图像", // 设置图像的描述,用于无障碍访问
modifier = androidx.compose.ui.Modifier
.width(200.dp) // 设置图像的宽度
.height(200.dp) // 设置图像的高度
)
}
@Preview
@Composable
fun LocalResourceImagePreview() {
LocalResourceImage()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
在上述代码中,painterResource
函数用于从资源中获取图像的 Painter
对象,Painter
是一个用于绘制图像的抽象类。Image
组件接收 Painter
对象,并根据 modifier
进行布局和样式设置。
3.1.1 painterResource
源码分析
painterResource
函数的源码如下:
kotlin
@Composable
fun painterResource(
id: Int
): Painter {
// 获取当前的资源环境
val resources = LocalContext.current.resources
// 使用 ResourceLoader 加载资源
return remember { ResourceLoader(resources).load(id) }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
在这个函数中,首先通过 LocalContext.current.resources
获取当前的资源环境,然后使用 ResourceLoader
类的 load
方法加载指定 ID 的资源。ResourceLoader
类的 load
方法源码如下:
kotlin
class ResourceLoader(private val resources: Resources) {
fun load(id: Int): Painter {
// 根据资源 ID 获取资源类型
val resourceTypeName = resources.getResourceTypeName(id)
return when (resourceTypeName) {
"drawable" -> {
// 如果是 drawable 资源,使用 DrawablePainter 绘制
val drawable = resources.getDrawable(id, null)
DrawablePainter(drawable)
}
else -> throw IllegalArgumentException("Unsupported resource type: $resourceTypeName")
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
在 load
方法中,首先根据资源 ID 获取资源类型,然后根据资源类型进行不同的处理。如果是 drawable
资源,使用 DrawablePainter
来绘制图像。
3.1.2 Image
组件源码分析
Image
组件的源码如下:
kotlin
@Composable
fun Image(
painter: Painter,
contentDescription: String?,
modifier: Modifier = Modifier,
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
alpha: Float = 1f,
colorFilter: ColorFilter? = null
) {
// 创建一个 ImagePainter 对象,用于绘制图像
val imagePainter = remember(painter, alignment, contentScale, alpha, colorFilter) {
ImagePainter(painter, alignment, contentScale, alpha, colorFilter)
}
// 使用 Canvas 组件进行绘制
Canvas(modifier) {
imagePainter.draw(this)
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
在 Image
组件中,首先创建一个 ImagePainter
对象,该对象封装了图像的绘制逻辑,包括对齐方式、内容缩放、透明度和颜色滤镜等。然后使用 Canvas
组件进行绘制,调用 imagePainter
的 draw
方法将图像绘制到画布上。
3.2 从网络加载图像
在实际开发中,经常需要从网络加载图像。Compose 本身没有提供直接的网络图像加载功能,但可以使用第三方库,如 Coil 或 Glide。以下是使用 Coil 加载网络图像的示例代码:
kotlin
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.tooling.preview.Preview
import coil.compose.rememberAsyncImagePainter
import coil.request.ImageRequest
@Composable
fun NetworkImage() {
// 使用 rememberAsyncImagePainter 函数从网络加载图像
val painter = rememberAsyncImagePainter(
model = ImageRequest.Builder(LocalContext.current)
.data("https://example.com/image.jpg") // 设置图像的 URL
.crossfade(true) // 启用淡入淡出效果
.build()
)
// 使用 Image 组件显示图像
Image(
painter = painter, // 设置图像的 Painter
contentDescription = "网络图像", // 设置图像的描述,用于无障碍访问
modifier = Modifier
.width(200.dp) // 设置图像的宽度
.height(200.dp) // 设置图像的高度
contentScale = ContentScale.Crop // 设置内容缩放方式
)
}
@Preview
@Composable
fun NetworkImagePreview() {
NetworkImage()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
在上述代码中,rememberAsyncImagePainter
函数用于从网络加载图像,它接收一个 ImageRequest
对象,该对象可以设置图像的 URL、加载选项等。Image
组件用于显示加载的图像。
3.2.1 rememberAsyncImagePainter
源码分析
rememberAsyncImagePainter
函数的源码如下:
kotlin
@Composable
fun rememberAsyncImagePainter(
model: Any?,
imageLoader: ImageLoader = LocalImageLoader.current,
placeholder: Painter? = null,
error: Painter? = null,
fallback: Painter? = null,
onState: ((AsyncImagePainter.State) -> Unit)? = null,
contentScale: ContentScale = ContentScale.Fit,
alignment: Alignment = Alignment.Center,
alpha: Float = 1f,
colorFilter: ColorFilter? = null
): AsyncImagePainter {
// 创建一个 AsyncImagePainter 对象
return remember(model, imageLoader, placeholder, error, fallback, onState, contentScale, alignment, alpha, colorFilter) {
AsyncImagePainter(
model = model,
imageLoader = imageLoader,
placeholder = placeholder,
error = error,
fallback = fallback,
onState = onState,
contentScale = contentScale,
alignment = alignment,
alpha = alpha,
colorFilter = colorFilter
)
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
在这个函数中,使用 remember
来记忆 AsyncImagePainter
对象,确保在重组时不会重新创建。AsyncImagePainter
类负责从网络加载图像,并处理加载状态的变化。
3.2.2 AsyncImagePainter
源码分析
AsyncImagePainter
类的部分源码如下:
kotlin
class AsyncImagePainter(
private val model: Any?,
private val imageLoader: ImageLoader,
private val placeholder: Painter? = null,
private val error: Painter? = null,
private val fallback: Painter? = null,
private val onState: ((State) -> Unit)? = null,
private val contentScale: ContentScale = ContentScale.Fit,
private val alignment: Alignment = Alignment.Center,
private val alpha: Float = 1f,
private val colorFilter: ColorFilter? = null
) : Painter() {
private var state: State = State.Loading
private var result: ImageResult? = null
init {
// 启动图像加载任务
launchImageLoad()
}
private fun launchImageLoad() {
val request = ImageRequest.Builder(imageLoader.context)
.data(model)
.target(
onStart = {
// 开始加载时,更新状态为 Loading
state = State.Loading
onState?.invoke(state)
},
onSuccess = { drawable ->
// 加载成功时,更新状态为 Success
result = drawable
state = State.Success(drawable)
onState?.invoke(state)
},
onError = { throwable ->
// 加载失败时,更新状态为 Error
state = State.Error(throwable)
onState?.invoke(state)
}
)
.build()
// 使用 ImageLoader 加载图像
imageLoader.enqueue(request)
}
override fun DrawScope.onDraw() {
when (state) {
is State.Loading -> {
// 加载中,绘制占位符
placeholder?.draw(this, alpha = alpha, colorFilter = colorFilter)
}
is State.Success -> {
// 加载成功,绘制图像
val drawable = (state as State.Success).drawable
drawable.draw(this, contentScale, alignment, alpha, colorFilter)
}
is State.Error -> {
// 加载失败,绘制错误图像
error?.draw(this, alpha = alpha, colorFilter = colorFilter)
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
在 AsyncImagePainter
类中,launchImageLoad
方法用于启动图像加载任务,使用 ImageLoader
来加载图像,并根据加载状态更新 state
变量。onDraw
方法根据不同的状态绘制不同的图像,如占位符、成功加载的图像或错误图像。
四、图像的裁剪与缩放
4.1 图像裁剪
在 Android Compose 中,可以使用 Modifier.clip
方法对图像进行裁剪。以下是一个示例代码:
kotlin
import androidx.compose.foundation.Image
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
@Composable
fun CroppedImage() {
// 使用 painterResource 函数从资源中获取图像的 Painter
val painter = painterResource(id = R.drawable.sample_image)
// 使用 Image 组件显示图像,并进行裁剪
Image(
painter = painter, // 设置图像的 Painter
contentDescription = "裁剪后的图像", // 设置图像的描述,用于无障碍访问
modifier = Modifier
.width(200.dp) // 设置图像的宽度
.height(200.dp) // 设置图像的高度
.clip(CircleShape) // 使用圆形形状进行裁剪
)
}
@Preview
@Composable
fun CroppedImagePreview() {
CroppedImage()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
在上述代码中,使用 Modifier.clip
方法结合 CircleShape
对图像进行圆形裁剪。
4.1.1 Modifier.clip
源码分析
Modifier.clip
方法的源码如下:
kotlin
fun Modifier.clip(shape: Shape): Modifier = this.then(ClipShapeModifier(shape, ClipOp.Intersect))
- 1
在这个方法中,使用 then
方法将 ClipShapeModifier
修饰符添加到当前修饰符链中。ClipShapeModifier
类的源码如下:
kotlin
private class ClipShapeModifier(
private val shape: Shape,
private val clipOp: ClipOp
) : DrawModifier {
override fun DrawScope.draw() {
// 创建一个 Path 对象,用于定义裁剪路径
val path = shape.createOutline(size, layoutDirection, this).asPath()
// 保存当前画布状态
val saveCount = drawContext.canvas.save()
try {
// 设置裁剪操作
drawContext.canvas.clipPath(path, clipOp)
// 绘制内容
drawContent()
} finally {
// 恢复画布状态
drawContext.canvas.restoreToCount(saveCount)
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
在 ClipShapeModifier
类的 draw
方法中,首先根据 Shape
创建一个 Path
对象,然后使用 canvas.clipPath
方法对画布进行裁剪,最后绘制内容。
4.2 图像缩放
在 Android Compose 中,可以使用 ContentScale
参数对图像进行缩放。ContentScale
是一个枚举类,定义了不同的缩放方式,如 Fit
、Crop
、FillBounds
等。以下是一个示例代码:
kotlin
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
@Composable
fun ScaledImage() {
// 使用 painterResource 函数从资源中获取图像的 Painter
val painter = painterResource(id = R.drawable.sample_image)
// 使用 Image 组件显示图像,并进行缩放
Image(
painter = painter, // 设置图像的 Painter
contentDescription = "缩放后的图像", // 设置图像的描述,用于无障碍访问
modifier = Modifier
.width(200.dp) // 设置图像的宽度
.height(200.dp) // 设置图像的高度
contentScale = ContentScale.Crop // 设置内容缩放方式为裁剪
)
}
@Preview
@Composable
fun ScaledImagePreview() {
ScaledImage()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
在上述代码中,使用 ContentScale.Crop
对图像进行裁剪缩放。
4.2.1 ContentScale
源码分析
ContentScale
枚举类的源码如下:
kotlin
enum class ContentScale {
/**
* 缩放图像以适应容器,保持图像的宽高比,可能会在容器内留下空白。
*/
Fit,
/**
* 缩放图像以填充容器,保持图像的宽高比,可能会裁剪图像。
*/
Crop,
/**
* 缩放图像以填充容器,不保持图像的宽高比。
*/
FillBounds,
/**
* 不进行缩放,直接显示图像。
*/
None;
internal fun computeScaleFactor(
sourceWidth: Float,
sourceHeight: Float,
destWidth: Float,
destHeight: Float
): Float {
return when (this) {
Fit -> minOf(destWidth / sourceWidth, destHeight / sourceHeight)
Crop -> maxOf(destWidth / sourceWidth, destHeight / sourceHeight)
FillBounds -> 1f
None -> 1f
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
在 ContentScale
枚举类中,定义了不同的缩放方式,并提供了 computeScaleFactor
方法用于计算缩放因子。在 Image
组件的 ImagePainter
类中,会根据 ContentScale
计算缩放因子,并对图像进行相应的缩放。
五、矢量图的使用与绘制
5.1 使用 ImageVector
显示矢量图
在 Android Compose 中,可以使用 ImageVector
来显示矢量图。ImageVector
是一个表示矢量图的抽象类,可以通过 imageVectorResource
函数从资源中加载矢量图。以下是一个示例代码:
kotlin
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.imageVectorResource
import androidx.compose.ui.tooling.preview.Preview
@Composable
fun VectorImage() {
// 使用 imageVectorResource 函数从资源中获取矢量图的 ImageVector
val imageVector: ImageVector = imageVectorResource(id = R.drawable.sample_vector)
// 使用 Image 组件显示矢量图
Image(
imageVector = imageVector, // 设置矢量图的 ImageVector
contentDescription = "矢量图", // 设置图像的描述,用于无障碍访问
modifier = Modifier
.width(200.dp) // 设置图像的宽度
.height(200.dp) // 设置图像的高度
)
}
@Preview
@Composable
fun VectorImagePreview() {
VectorImage()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
在上述代码中,imageVectorResource
函数用于从资源中获取矢量图的 ImageVector
对象,Image
组件接收 ImageVector
对象并显示矢量图。
5.1.1 imageVectorResource
源码分析
imageVectorResource
函数的源码如下:
kotlin
@Composable
fun imageVectorResource(
id: Int
): ImageVector {
// 获取当前的资源环境
val resources = LocalContext.current.resources
// 使用 VectorResourceLoader 加载矢量图资源
return remember { VectorResourceLoader(resources).load(id) }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
在这个函数中,首先通过 LocalContext.current.resources
获取当前的资源环境,然后使用 VectorResourceLoader
类的 load
方法加载指定 ID 的矢量图资源。VectorResourceLoader
类的 load
方法源码如下:
kotlin
class VectorResourceLoader(private val resources: Resources) {
fun load(id: Int): ImageVector {
// 根据资源 ID 获取矢量图的 XML 文件
val xml = resources.getXml(id)
try {
// 使用 VectorDrawableCompat 解析 XML 文件
val vectorDrawable = VectorDrawableCompat.createFromXmlInner(resources, xml, null)
if (vectorDrawable != null) {
// 将 VectorDrawableCompat 转换为 ImageVector
return vectorDrawable.toImageVector()
}
} catch (e: Exception) {
throw IllegalArgumentException("Failed to load vector resource: $id", e)
}
throw IllegalArgumentException("Failed to load vector resource: $id")
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
在 load
方法中,首先根据资源 ID 获取矢量图的 XML 文件,然后使用 VectorDrawableCompat
解析 XML 文件,最后将 VectorDrawableCompat
转换为 ImageVector
。
5.1.2 Image
组件显示 ImageVector
的源码分析
当 Image
组件接收 ImageVector
对象时,会使用 VectorPainter
来绘制矢量图。Image
组件的部分源码如下:
kotlin
@Composable
fun Image(
imageVector: ImageVector,
contentDescription: String?,
modifier: Modifier = Modifier,
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
alpha: Float = 1f,
colorFilter: ColorFilter? = null
) {
// 创建一个 VectorPainter 对象,用于绘制矢量图
val painter = remember(imageVector, alignment, contentScale, alpha, colorFilter) {
VectorPainter(imageVector, alignment, contentScale, alpha, colorFilter)
}
// 使用 Canvas 组件进行绘制
Canvas(modifier) {
painter.draw(this)
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
在这个方法中,创建一个 VectorPainter
对象,该对象封装了矢量图的绘制逻辑,然后使用 Canvas
组件调用 painter
的 draw
方法将矢量图绘制到画布上。
5.2 自定义矢量图绘制
除了从资源中加载矢量图,还可以自定义矢量图的绘制。在 Android Compose 中,可以使用 Path
和 DrawScope
来绘制自定义的矢量图形。以下是一个示例代码:
kotlin
import androidx.compose.foundation.Canvas
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.tooling.preview.Preview
@Composable
fun CustomVectorDrawing() {
Canvas(
modifier = Modifier
.width(200.dp) // 设置画布的宽度
.height(200.dp) // 设置画布的高度
) {
// 创建一个 Path 对象,用于定义矢量图形的路径
val path = Path()
// 移动到起始点
path.moveTo(100f, 20f)
// 绘制线条到指定点
path.lineTo(180f, 180f)
path.lineTo(20f, 180f)
// 闭合路径
path.close()
// 设置画笔颜色
drawPath(
path = path,
color = Color.Blue
)
}
}
@Preview
@Composable
fun CustomVectorDrawingPreview() {
CustomVectorDrawing()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
在上述代码中,使用 Canvas
组件创建一个画布,然后使用 Path
对象定义矢量图形的路径,最后使用 drawPath
方法将路径绘制到画布上。
5.2.1 Path
类源码分析
Path
类是用于定义矢量图形路径的类,它提供了一系列方法来创建和操作路径,如 moveTo
、lineTo
、arcTo
等。以下是 Path
类的部分源码:
kotlin
class Path {
private val nativePath = android.graphics.Path()
/**
* 将路径的当前点移动到指定的坐标。
*/
fun moveTo(x: Float, y: Float) {
nativePath.moveTo(x, y)
}
/**
* 从当前点绘制一条直线到指定的坐标。
*/
fun lineTo(x: Float, y: Float) {
nativePath.lineTo(x, y)
}
/**
* 闭合路径。
*/
fun close() {
nativePath.close()
}
// 其他方法...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
在 Path
类中,内部使用 android.graphics.Path
来实现路径的创建和操作。
5.2.2 DrawScope
中绘制路径的源码分析
在 Canvas
组件的 DrawScope
中,drawPath
方法用于绘制路径。DrawScope
是一个接口,定义了一系列绘制方法。drawPath
方法的源码如下:
kotlin
fun DrawScope.drawPath(
path: Path,
color: Color,
alpha: Float = 1f,
style: PaintingStyle = PaintingStyle.Fill,
strokeWidth: Float = 0f,
strokeCap: StrokeCap = StrokeCap.Butt,
strokeJoin: StrokeJoin = StrokeJoin.Miter,
strokeMiter: Float = 4f,
colorFilter: ColorFilter? = null
) {
// 创建一个 Paint 对象,用于设置绘制样式
val paint = Paint().apply {
this.color = color
this.alpha = alpha
this.style = style
this.strokeWidth = strokeWidth
this.strokeCap = strokeCap
this.strokeJoin = strokeJoin
this.strokeMiter = strokeMiter
this.colorFilter = colorFilter
}
// 使用 Canvas 绘制路径
drawContext.canvas.drawPath(path.nativePath, paint.asFrameworkPaint())
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
在 drawPath
方法中,首先创建一个 Paint
对象,用于设置绘制样式,然后使用 Canvas
的 drawPath
方法将路径绘制到画布上。
六、图像与矢量图的动画效果
6.1 图像淡入淡出动画
在 Android Compose 中,可以使用 AnimatedVisibility
组件实现图像的淡入淡出动画。以下是一个示例代码:
kotlin
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
@OptIn(ExperimentalAnimationApi::class)
@Composable
fun FadeInOutImage() {
// 定义一个可变状态,用于控制图像的可见性
var isVisible by mutableStateOf(true)
// 使用 AnimatedVisibility 组件实现淡入淡出动画
AnimatedVisibility(
visible = isVisible,
enter = fadeIn(), // 淡入动画
exit = fadeOut() // 淡出动画
) {
// 使用 Image 组件显示图像
Image(
painter = painterResource(id = R.drawable.sample_image), // 设置图像的 Painter
contentDescription = "淡入淡出图像", // 设置图像的描述,用于无障碍访问
modifier = Modifier
.width(200.dp) // 设置图像的宽度
.height(200.dp) // 设置图像的高度
)
}
// 切换图像的可见性
if (isVisible) {
isVisible = false
} else {
isVisible = true
}
}
@Preview
@Composable
fun FadeInOutImagePreview() {
FadeInOutImage()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
在上述代码中,使用 AnimatedVisibility
组件包裹 Image
组件,并设置 enter
和 exit
动画为 fadeIn()
和 fadeOut()
,实现图像的淡入淡出动画。
6.1.1 AnimatedVisibility
源码分析
AnimatedVisibility
组件的源码如下:
kotlin
@Composable
@ExperimentalAnimationApi
fun AnimatedVisibility(
visible: Boolean,
modifier: Modifier = Modifier,
enter: EnterTransition = fadeIn() + expandVertically(),
exit: ExitTransition = shrinkVertically() + fadeOut(),
content: @Composable () -> Unit
) {
// 创建一个 Transition 对象,用于管理动画状态
val transition = updateTransition(visible, label = "AnimatedVisibility")
// 根据可见性状态执行相应的动画
transition.AnimatedVisibilityScope(
modifier = modifier,
enter = enter,
exit = exit,
content = content
)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
在 AnimatedVisibility
组件中,使用 updateTransition
函数创建一个 Transition
对象,用于管理动画状态。然后调用 Transition
对象的 AnimatedVisibilityScope
方法,根据可见性状态执行相应的动画。
6.1.2 fadeIn
和 fadeOut
源码分析
fadeIn
和 fadeOut
函数是用于创建淡入和淡出动画的函数,它们的源码如下:
kotlin
@Composable
@ExperimentalAnimationApi
fun fadeIn(
animationSpec: FiniteAnimationSpec<Float> = tween(durationMillis = 300),
initialAlpha: Float = 0f
): EnterTransition {
return FadeTransition(
alpha = transition(
to = { 1f },
from = { initialAlpha },
animationSpec = animationSpec
)
)
}
@Composable
@ExperimentalAnimationApi
fun fadeOut(
animationSpec: FiniteAnimationSpec<Float> = tween(durationMillis = 300),
targetAlpha: Float = 0f
): ExitTransition {
return FadeTransition(
alpha = transition(
to = { targetAlpha },
from = { 1f },
animationSpec = animationSpec
)
)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
在 fadeIn
和 fadeOut
函数中,使用 transition
函数创建一个动画过渡,控制透明度的变化,然后创建一个 FadeTransition
对象,用于实现淡入淡出效果。
6.2 矢量图的缩放动画
可以使用 animateFloatAsState
函数实现矢量图的缩放动画。以下是一个示例代码:
kotlin
import androidx.compose.animation.animateFloatAsState
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.imageVectorResource
import androidx.compose.ui.tooling.preview.Preview
@Composable
fun VectorScaleAnimation() {
// 使用 imageVectorResource 函数从资源中获取矢量图的 ImageVector
val imageVector: ImageVector = imageVectorResource(id = R.drawable.sample_vector)
// 定义一个可变状态,用于控制缩放比例
var scale by androidx.compose.runtime.mutableStateOf(1f)
// 使用 animateFloatAsState 函数实现缩放动画
val animatedScale: Float by animateFloatAsState(
targetValue = scale,
animationSpec = androidx.compose.animation.tween(durationMillis = 1000)
)
// 使用 Image 组件显示矢量图,并应用缩放动画
Image(
imageVector = imageVector, // 设置矢量图的 ImageVector
contentDescription = "矢量图缩放动画", // 设置图像的描述,用于无障碍访问
modifier = Modifier
.width(200.dp) // 设置图像的宽度
.height(200.dp) // 设置图像的高度
.scale(animatedScale) // 应用缩放动画
)
// 切换缩放比例
if (scale == 1f) {
scale = 2f
} else {
scale = 1f
}
}
@Preview
@Composable
fun VectorScaleAnimationPreview() {
VectorScaleAnimation()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
在上述代码中,使用 animateFloatAsState
函数实现缩放动画,通过改变 scale
值来触发动画,然后使用 Modifier.scale
方法应用缩放效果。
6.2.1 animateFloatAsState
源码分析
animateFloatAsState
函数的源码如下:
kotlin
@Composable
fun animateFloatAsState(
targetValue: Float,
animationSpec: AnimationSpec<Float> = spring(),
finishedListener: ((Float) -> Unit)? = null
): State<Float> {
// 创建一个 AnimationState 对象,用于管理动画状态
val animationState = remember {
AnimationState(
initialValue = targetValue,
animationSpec = animationSpec
)
}
// 更新动画目标值
LaunchedEffect(targetValue) {
animationState.animateTo(targetValue, finishedListener)
}
// 返回动画状态
return animationState.asState()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
在 animateFloatAsState
函数中,首先创建一个 AnimationState
对象,用于管理动画状态。然后使用 LaunchedEffect
函数在 targetValue
变化时更新动画目标值,最后返回动画状态。
6.2.2 Modifier.scale
源码分析
Modifier.scale
方法的源码如下:
kotlin
fun Modifier.scale(scale: Float): Modifier = this.then(ScaleModifier(scale))
- 1
在这个方法中,使用 then
方法将 ScaleModifier
修饰符添加到当前修饰符链中。ScaleModifier
类的源码如下:
kotlin
private class ScaleModifier(private val scale: Float) : DrawModifier {
override fun MeasureScope.measure(
measurable: Measurable,
constraints: Constraints
): MeasureResult {
// 计算缩放后的尺寸
val placeable = measurable.measure(constraints)
val width = (placeable.width * scale).roundToInt()
val height = (placeable.height * scale).roundToInt()
return layout(width, height) {
// 放置可测量对象
placeable.placeRelative(0, 0, scaleX = scale, scaleY = scale)
}
}
override fun DrawScope.draw() {
// 绘制内容
drawContent()
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
在 ScaleModifier
类的 measure
方法中,计算缩放后的尺寸,并使用 placeRelative
方法放置可测量对象,应用缩放效果。
七、图像与矢量图的性能优化
7.1 图像缓存
在从网络加载图像时,使用缓存可以减少重复加载,提高性能。Coil 库提供了内置的缓存机制。以下是一个使用 Coil 缓存的示例代码:
kotlin
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.tooling.preview.Preview
import coil.compose.rememberAsyncImagePainter
import coil.request.ImageRequest
import coil.size.Size
@Composable
fun CachedNetworkImage() {
// 创建一个 ImageLoader,设置缓存
val imageLoader = coil.ImageLoader.Builder(LocalContext.current)
.memoryCache {
// 设置内存缓存大小
coil.memory.MemoryCache.Builder(LocalContext.current)
.maxSizePercent(0.25)
.build()
}
.diskCache {
// 设置磁盘缓存
coil.disk.DiskCache.Builder()
.directory(LocalContext.current.cacheDir.resolve("image_cache"))
.maxSizeBytes(1024 * 1024 * 50) // 50MB
.build()
}
.build()
// 使用 rememberAsyncImagePainter 函数从网络加载图像,并使用 ImageLoader
val painter = rememberAsyncImagePainter(
model = ImageRequest.Builder(Local
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
kotlin
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.tooling.preview.Preview
import coil.compose.rememberAsyncImagePainter
import coil.request.ImageRequest
import coil.size.Size
@Composable
fun CachedNetworkImage() {
// 创建一个 ImageLoader,设置缓存
val imageLoader = coil.ImageLoader.Builder(LocalContext.current)
.memoryCache {
// 设置内存缓存大小
coil.memory.MemoryCache.Builder(LocalContext.current)
.maxSizePercent(0.25)
.build()
}
.diskCache {
// 设置磁盘缓存
coil.disk.DiskCache.Builder()
.directory(LocalContext.current.cacheDir.resolve("image_cache"))
.maxSizeBytes(1024 * 1024 * 50) // 50MB
.build()
}
.build()
// 使用 rememberAsyncImagePainter 函数从网络加载图像,并使用 ImageLoader
val painter = rememberAsyncImagePainter(
model = ImageRequest.Builder(LocalContext.current)
.data("https://example.com/image.jpg") // 设置图像的 URL
.size(Size.ORIGINAL) // 使用原始尺寸
.crossfade(true) // 启用淡入淡出效果
.build(),
imageLoader = imageLoader // 使用自定义的 ImageLoader
)
// 使用 Image 组件显示图像
Image(
painter = painter, // 设置图像的 Painter
contentDescription = "缓存网络图像", // 设置图像的描述,用于无障碍访问
modifier = Modifier
.width(200.dp) // 设置图像的宽度
.height(200.dp) // 设置图像的高度
contentScale = ContentScale.Crop // 设置内容缩放方式
)
}
@Preview
@Composable
fun CachedNetworkImagePreview() {
CachedNetworkImage()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
7.1.1 ImageLoader
缓存机制源码分析
ImageLoader
是 Coil 库中负责图像加载和缓存管理的核心类。以下是 ImageLoader.Builder
中设置缓存的相关源码分析:
kotlin
// ImageLoader.Builder 中设置内存缓存的部分
fun memoryCache(block: () -> MemoryCache): Builder {
this.memoryCache = block()
return this
}
// MemoryCache.Builder 类的部分源码
class MemoryCache.Builder(
private val context: Context
) {
private var weakValues = false
private var maxSizePercent = 0.25f
fun weakValues(weakValues: Boolean): Builder {
this.weakValues = weakValues
return this
}
fun maxSizePercent(maxSizePercent: Float): Builder {
require(maxSizePercent in 0f..1f) { "maxSizePercent must be between 0 and 1." }
this.maxSizePercent = maxSizePercent
return this
}
fun build(): MemoryCache {
val maxSize = (Runtime.getRuntime().maxMemory() * maxSizePercent).toInt()
return RealMemoryCache(context, maxSize, weakValues)
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
在 ImageLoader.Builder
中,memoryCache
方法接收一个返回 MemoryCache
对象的 lambda 表达式。MemoryCache.Builder
类用于构建内存缓存,maxSizePercent
方法设置内存缓存占总可用内存的百分比,build
方法根据设置的参数创建 RealMemoryCache
对象。
kotlin
// ImageLoader.Builder 中设置磁盘缓存的部分
fun diskCache(block: () -> DiskCache): Builder {
this.diskCache = block()
return this
}
// DiskCache.Builder 类的部分源码
class DiskCache.Builder {
private var directory: File? = null
private var maxSizeBytes = 250L * 1024 * 1024 // 250MB by default
private var strictMode = false
fun directory(directory: File): Builder {
this.directory = directory
return this
}
fun maxSizeBytes(maxSizeBytes: Long): Builder {
require(maxSizeBytes > 0) { "maxSizeBytes must be > 0." }
this.maxSizeBytes = maxSizeBytes
return this
}
fun strictMode(strictMode: Boolean): Builder {
this.strictMode = strictMode
return this
}
fun build(): DiskCache {
checkNotNull(directory) { "directory == null" }
return RealDiskCache(directory!!, maxSizeBytes, strictMode)
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
在 ImageLoader.Builder
中,diskCache
方法接收一个返回 DiskCache
对象的 lambda 表达式。DiskCache.Builder
类用于构建磁盘缓存,directory
方法设置磁盘缓存的目录,maxSizeBytes
方法设置磁盘缓存的最大大小,build
方法根据设置的参数创建 RealDiskCache
对象。
7.1.2 rememberAsyncImagePainter
与缓存的交互
rememberAsyncImagePainter
会使用传入的 ImageLoader
进行图像加载。当请求图像时,ImageLoader
会先检查内存缓存,如果内存缓存中有对应的图像,则直接返回;如果内存缓存中没有,则检查磁盘缓存;如果磁盘缓存中也没有,则从网络加载图像,并将图像存入内存缓存和磁盘缓存中。
kotlin
// AsyncImagePainter 中与 ImageLoader 交互的部分源码
private fun launchImageLoad() {
val request = ImageRequest.Builder(imageLoader.context)
.data(model)
.target(
onStart = {
// 开始加载时,更新状态为 Loading
state = State.Loading
onState?.invoke(state)
},
onSuccess = { drawable ->
// 加载成功时,更新状态为 Success
result = drawable
state = State.Success(drawable)
onState?.invoke(state)
},
onError = { throwable ->
// 加载失败时,更新状态为 Error
state = State.Error(throwable)
onState?.invoke(state)
}
)
.build()
// 使用 ImageLoader 加载图像
imageLoader.enqueue(request)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
在 launchImageLoad
方法中,ImageLoader
的 enqueue
方法会处理图像加载请求,在加载过程中会进行缓存检查和更新操作。
7.2 矢量图优化
7.2.1 减少矢量图复杂度
复杂的矢量图会增加绘制的时间和内存消耗。可以通过以下方式减少矢量图的复杂度:
- 简化路径:避免使用过多的曲线和控制点,尽量使用简单的几何形状组合。例如,将复杂的图标分解为多个简单的形状。
- 减少图层:避免使用过多的图层,将不必要的图层合并或删除。
7.2.2 矢量图压缩
在 Android 中,可以使用 Vector Asset Studio
对矢量图进行压缩。Vector Asset Studio
会自动优化矢量图的 XML 文件,去除不必要的属性和标签,减小文件大小。
7.2.3 按需加载矢量图
在应用中,不是所有的矢量图都需要在应用启动时加载。可以根据实际需求,在需要显示矢量图时再进行加载。例如,在 RecyclerView 中,只有当某个 item 可见时,才加载该 item 所需的矢量图。
kotlin
import androidx.compose.foundation.Image
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.imageVectorResource
import androidx.compose.ui.tooling.preview.Preview
data class VectorItem(val id: Int, val description: String)
@Composable
fun LazyVectorList() {
val vectorItems = listOf(
VectorItem(R.drawable.sample_vector_1, "矢量图 1"),
VectorItem(R.drawable.sample_vector_2, "矢量图 2"),
VectorItem(R.drawable.sample_vector_3, "矢量图 3")
)
LazyColumn {
items(vectorItems) { item ->
Image(
imageVector = imageVectorResource(id = item.id), // 设置矢量图的 ImageVector
contentDescription = item.description, // 设置图像的描述,用于无障碍访问
modifier = Modifier
.width(200.dp) // 设置图像的宽度
.height(200.dp) // 设置图像的高度
)
}
}
}
@Preview
@Composable
fun LazyVectorListPreview() {
LazyVectorList()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
在上述代码中,使用 LazyColumn
来显示矢量图列表,LazyColumn
会根据列表项的可见性进行按需加载,提高性能。
7.3 图像与矢量图的内存管理
7.3.1 及时释放资源
在 Android 中,图像和矢量图占用的内存需要及时释放,避免内存泄漏。对于图像,可以在 Image
组件不再使用时,调用 ImageLoader
的 shutdown
方法来释放相关资源。对于矢量图,虽然 ImageVector
本身占用的内存相对较小,但在不再使用时,也应该避免持有引用。
kotlin
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import coil.ImageLoader
@Composable
fun ImageWithResourceRelease() {
val imageLoader = ImageLoader(LocalContext.current)
// 使用 DisposableEffect 在组件销毁时释放资源
DisposableEffect(Unit) {
onDispose {
imageLoader.shutdown()
}
}
// 其他图像加载和显示代码...
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
在上述代码中,使用 DisposableEffect
在组件销毁时调用 ImageLoader
的 shutdown
方法来释放资源。
7.3.2 控制图像尺寸
在加载图像时,根据显示的需求控制图像的尺寸,避免加载过大的图像导致内存占用过高。可以使用 ImageRequest
的 size
方法来设置加载图像的尺寸。
kotlin
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.tooling.preview.Preview
import coil.compose.rememberAsyncImagePainter
import coil.request.ImageRequest
import coil.size.Size
@Composable
fun SizedNetworkImage() {
// 使用 rememberAsyncImagePainter 函数从网络加载图像,并设置尺寸
val painter = rememberAsyncImagePainter(
model = ImageRequest.Builder(LocalContext.current)
.data("https://example.com/image.jpg") // 设置图像的 URL
.size(Size(200, 200)) // 设置加载图像的尺寸
.crossfade(true) // 启用淡入淡出效果
.build()
)
// 使用 Image 组件显示图像
Image(
painter = painter, // 设置图像的 Painter
contentDescription = "指定尺寸的网络图像", // 设置图像的描述,用于无障碍访问
modifier = Modifier
.width(200.dp) // 设置图像的宽度
.height(200.dp) // 设置图像的高度
contentScale = ContentScale.Crop // 设置内容缩放方式
)
}
@Preview
@Composable
fun SizedNetworkImagePreview() {
SizedNetworkImage()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
在上述代码中,使用 ImageRequest
的 size
方法设置加载图像的尺寸为 200x200 像素,减少内存占用。
八、图像与矢量图的交互处理
8.1 图像点击事件
在 Android Compose 中,可以为 Image
组件添加点击事件。以下是一个示例代码:
kotlin
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@Composable
fun ClickableImage() {
// 使用 painterResource 函数从资源中获取图像的 Painter
val painter = painterResource(id = R.drawable.sample_image)
// 使用 Image 组件显示图像,并添加点击事件
Image(
painter = painter, // 设置图像的 Painter
contentDescription = "可点击的图像", // 设置图像的描述,用于无障碍访问
modifier = Modifier
.width(200.dp) // 设置图像的宽度
.height(200.dp) // 设置图像的高度
.clickable {
// 点击事件处理逻辑
println("图像被点击了!")
}
)
}
@Preview
@Composable
fun ClickableImagePreview() {
ClickableImage()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
8.1.1 Modifier.clickable
源码分析
Modifier.clickable
方法的源码如下:
kotlin
fun Modifier.clickable(
enabled: Boolean = true,
onClickLabel: String? = null,
role: Role? = null,
onClick: () -> Unit
): Modifier = this.then(
ClickableModifier(
enabled = enabled,
onClickLabel = onClickLabel,
role = role,
onClick = onClick
)
)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
在这个方法中,使用 then
方法将 ClickableModifier
修饰符添加到当前修饰符链中。ClickableModifier
类的源码如下:
kotlin
private class ClickableModifier(
private val enabled: Boolean,
private val onClickLabel: String?,
private val role: Role?,
private val onClick: () -> Unit
) : PointerInputModifier {
override fun PointerInputScope.modifyPointerInput(
other: PointerInputChangeScope.() -> Unit
): PointerInputChangeScope.() -> Unit = {
other()
detectTapGestures(
enabled = enabled,
onTap = onClick
)
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
在 ClickableModifier
类中,实现了 PointerInputModifier
接口,使用 detectTapGestures
函数来检测点击手势,当检测到点击事件时,调用传入的 onClick
方法。
8.2 矢量图的交互动画
可以为矢量图添加交互动画,例如点击矢量图时进行缩放或旋转。以下是一个点击矢量图进行缩放的示例代码:
kotlin
import androidx.compose.animation.animateFloatAsState
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.imageVectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@Composable
fun InteractiveVectorImage() {
// 使用 imageVectorResource 函数从资源中获取矢量图的 ImageVector
val imageVector: ImageVector = imageVectorResource(id = R.drawable.sample_vector)
// 定义一个可变状态,用于控制缩放比例
var isClicked by mutableStateOf(false)
// 使用 animateFloatAsState 函数实现缩放动画
val scale: Float by animateFloatAsState(
targetValue = if (isClicked) 2f else 1f,
animationSpec = androidx.compose.animation.tween(durationMillis = 300)
)
// 使用 Image 组件显示矢量图,并添加点击事件和缩放动画
Image(
imageVector = imageVector, // 设置矢量图的 ImageVector
contentDescription = "可交互的矢量图", // 设置图像的描述,用于无障碍访问
modifier = Modifier
.width(200.dp) // 设置图像的宽度
.height(200.dp) // 设置图像的高度
.scale(scale) // 应用缩放动画
.clickable {
// 点击事件处理逻辑,切换点击状态
isClicked = !isClicked
}
)
}
@Preview
@Composable
fun InteractiveVectorImagePreview() {
InteractiveVectorImage()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
8.2.2 交互动画的实现原理
在上述代码中,使用 mutableStateOf
来管理点击状态 isClicked
,当点击矢量图时,切换 isClicked
的值。animateFloatAsState
函数会根据 isClicked
的值来改变缩放比例 scale
,并使用 tween
动画规范来实现动画效果。Modifier.scale
方法会应用这个缩放比例,从而实现矢量图的缩放动画。
8.3 图像与矢量图的手势交互
除了点击事件,还可以为图像和矢量图添加其他手势交互,如缩放、平移和旋转。可以使用 Modifier.pointerInput
来实现这些手势交互。以下是一个实现图像缩放和平移的示例代码:
kotlin
import androidx.compose.foundation.Image
import androidx.compose.foundation.gestures.detectTransformGestures
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@Composable
fun GestureInteractiveImage() {
// 使用 painterResource 函数从资源中获取图像的 Painter
val painter = painterResource(id = R.drawable.sample_image)
// 定义可变状态,用于控制缩放比例和偏移量
var scale by mutableStateOf(1f)
var offsetX by mutableStateOf(0f)
var offsetY by mutableStateOf(0f)
// 使用 Modifier.pointerInput 实现手势交互
val modifier = Modifier
.width(200.dp) // 设置图像的宽度
.height(200.dp) // 设置图像的高度
.graphicsLayer(
scaleX = scale,
scaleY = scale,
translationX = offsetX,
translationY = offsetY
)
.pointerInput(Unit) {
detectTransformGestures(
onGesture = { centroid, pan, zoom, rotation ->
// 更新缩放比例和偏移量
scale = (scale * zoom).coerceIn(0.1f, 5f)
offsetX += pan.x
offsetY += pan.y
}
)
}
// 使用 Image 组件显示图像,并应用手势交互
Image(
painter = painter, // 设置图像的 Painter
contentDescription = "手势交互的图像", // 设置图像的描述,用于无障碍访问
modifier = modifier
)
}
@Preview
@Composable
fun GestureInteractiveImagePreview() {
GestureInteractiveImage()
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
8.3.1 Modifier.pointerInput
源码分析
Modifier.pointerInput
方法的源码如下:
kotlin
fun Modifier.pointerInput(
key1: Any?,
block: suspend PointerInputScope.() -> Unit
): Modifier = this.then(
PointerInputModifier(
key1 = key1,
block = block
)
)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
在这个方法中,使用 then
方法将 PointerInputModifier
修饰符添加到当前修饰符链中。PointerInputModifier
类的源码如下:
kotlin
private class PointerInputModifier(
private val key1: Any?,
private val block: suspend PointerInputScope.() -> Unit
) : PointerInputModifier {
override fun PointerInputScope.modifyPointerInput(
other: PointerInputChangeScope.() -> Unit
): PointerInputChangeScope.() -> Unit = {
other()
launch {
block()
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
在 PointerInputModifier
类中,实现了 PointerInputModifier
接口,使用 launch
函数启动一个协程来执行传入的 block
函数,该函数用于处理手势事件。
8.3.2 detectTransformGestures
源码分析
detectTransformGestures
函数用于检测缩放、平移和旋转手势。其源码如下:
kotlin
suspend fun PointerInputScope.detectTransformGestures(
panZoomLock: Boolean = false,
onGesture: (centroid: Offset, pan: Offset, zoom: Float, rotation: Float) -> Unit
) {
forEachGesture {
awaitPointerEventScope {
var zoom = 1f
var rotation = 0f
var pan = Offset.Zero
var pastTouchSlop = false
val touchSlop = viewConfiguration.touchSlop
awaitFirstDown(requireUnconsumed = false)
do {
val event = awaitPointerEvent()
val canceled = event.changes.any { it.isConsumed }
if (!canceled) {
val zoomChange = event.calculateZoom()
val rotationChange = event.calculateRotation()
val panChange = event.calculatePan()
if (!pastTouchSlop) {
zoom *= zoomChange
rotation += rotationChange
pan += panChange
val centroidSize = event.calculateCentroidSize(useCurrent = false)
val zoomMotion = abs(1 - zoom) * centroidSize
val rotationMotion = abs(rotation * PI * centroidSize / 180f)
val panMotion = pan.getDistance()
if (zoomMotion > touchSlop ||
rotationMotion > touchSlop ||
panMotion > touchSlop
) {
pastTouchSlop = true
}
} else {
onGesture(
event.calculateCentroid(useCurrent = false),
panChange,
zoomChange,
rotationChange
)
}
}
} while (!canceled && event.changes.any { it.pressed })
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
在 detectTransformGestures
函数中,使用 forEachGesture
函数处理每个手势事件,使用 awaitPointerEventScope
函数等待指针事件。在事件处理过程中,计算缩放、平移和旋转的变化量,并根据触摸阈值判断是否开始处理手势,当满足条件时,调用 onGesture
方法传递手势信息。
九、图像与矢量图的适配与兼容性
9.1 不同屏幕密度的适配
在 Android 开发中,不同的设备具有不同的屏幕密度,为了确保图像和矢量图在各种设备上都能正常显示,需要进行屏幕密度的适配。
9.1.1 图像资源适配
对于图像资源,可以使用不同密度的文件夹来存放不同分辨率的图像。例如,drawable-mdpi
文件夹存放中等密度屏幕的图像,drawable-hdpi
文件夹存放高密度屏幕的图像,drawable-xhdpi
文件夹存放超高密度屏幕的图像等。Android 系统会根据设备的屏幕密度自动选择合适的图像资源。
9.1.2 矢量图的优势
矢量图在不同屏幕密度下具有天然的适配优势,因为矢量图是使用数学公式描述的,不会因为缩放而失真。在 Android Compose 中,使用 ImageVector
显示矢量图时,不需要为不同屏幕密度提供不同的资源。
9.2 不同 Android 版本的兼容性
9.2.1 图像加载库的兼容性
在使用第三方图像加载库(如 Coil 或 Glide)时,需要确保库的版本与目标 Android 版本兼容。大多数现代的图像加载库都支持较新的 Android 版本,但在使用时仍需注意检查文档。
9.2.2 矢量图的兼容性
Android 支持从 API 级别 21 开始使用矢量图。如果需要支持更低的 API 级别,可以使用 VectorDrawableCompat
来兼容旧版本的 Android 系统。在 Android Compose 中,imageVectorResource
函数内部会使用 VectorDrawableCompat
来加载矢量图,确保在不同 Android 版本上的兼容性。
kotlin
// VectorResourceLoader 中使用 VectorDrawableCompat 的部分源码
class VectorResourceLoader(private val resources: Resources) {
fun load(id: Int): ImageVector {
// 根据资源 ID 获取矢量图的 XML 文件
val xml = resources.getXml(id)
try {
// 使用 VectorDrawableCompat 解析 XML 文件
val vectorDrawable = VectorDrawableCompat.createFromXmlInner(resources, xml, null)
if (vectorDrawable != null) {
// 将 VectorDrawableCompat 转换为 ImageVector
return vectorDrawable.toImageVector()
}
} catch (e: Exception) {
throw IllegalArgumentException("Failed to load vector resource: $id", e)
}
throw IllegalArgumentException("Failed to load vector resource: $id")
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
在上述代码中,使用 VectorDrawableCompat.createFromXmlInner
方法解析矢量图的 XML 文件,确保在低版本 Android 系统上也能正常加载矢量图。



评论记录:
回复评论: