首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

Android Compose 框架图像与矢量图深入剖析(五十四)

  • 25-04-25 00:41
  • 3456
  • 12067
blog.csdn.net

上一期: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 系统上也能正常加载矢量图。

Android小码峰
微信公众号
专门提供Android,AI领域的知识
注:本文转载自blog.csdn.net的Android 小码蜂的文章"https://blog.csdn.net/qq_28540861/article/details/146991621"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top