Compose Multiplatform 下图片加载支持

早期由于 Android 主要的社区图片组件库(例如 Glide、Coil)在 Google Jetpack Compose 的支持上还不算好,因此当时基于 Glide 做了一个 akit 的 Compose 图片支持库,主要用于处理社区图片库一些在 Compose 上不支持的问题和 bugfix 可参考 《使用 Glide 在 Compose 中加载图片 》。 随着公司基建的发展,开始对 Compose Multiplatform 进行支持,其中图片库就是其中的一个挑战: CMP 的支持并非直接全迁移,而是迁移基础模块,业务模块保持纯 Android 模块和 iOS 模块,原生和 CMP 要求共用一套加载缓存与资源类型支持。Android 端还是使用 Glide 加载,iOS 侧主要使用 Coil,并预留打通 Kingfisher 缓存或直接接入 Kingfisher 的扩展能力。 虽然可以直接 expect 图片组件,由两端各自实现,但由于类似于 Modifier.asyncBackground 这类需要依赖 ModifierNode 测量和绘制逻辑的场景,expect 方案并无法直接套用原生 View 实现,因此最好的方式还是统一 Compose 节点定义与逻辑,仅抽象出平台加载图片的 engine(类似于 Ktor) 社交业务的图片加载需要支持许多网络图片类型,包括 ninepatch 背景、gif、lottie 动画等,因此也需要在 CMP 侧同样支持这些类型的加载。 从原本的纯 Android Akit 库迁移到 CMP 在早期的 Android 版本里,图片加载主要是围绕 Glide 做 Compose 适配,核心目标是“让业务能在 Compose 里继续复用已有图片能力”,所以当时的重心是 UI 侧封装(例如统一的 NetImage)和加载时机控制。 ...

February 14, 2026 · korilin

使用 Glide 在 Compose 中加载图片

在公司业务引入 Compose 到现在也有一年了,图片加载这一块一直是 UI 基础设施的常聊话题,这期聊一下这段时间我们在 Compose 加载图片上的一些方案尝试,以及这个过程遇到的一些问题。 Compose 基础图片加载 首先我们要先认识一下 Compose 基础库加载图片的方式,以及 Painter。 Painter 是 Compose 中一个可绘制内容的抽象类型,它可以通过一些机制去配置绘制内容的透明度、颜色过滤器、绘制内容的测量大小、以及绘制方向等。 常见的 Painter 类型有: ColorPainter 绘制纯颜色 BrushPainter 绘制渐变色 BitmapPainter 绘制 Bitmap VectorPainter 绘制矢量图 Compose 中绘制图片大多也是依靠 Painter(但不是强依赖),通过 painterResource 方法就可以将本地的资源 id 加载成 Painter 给 Image 控件绘制。例如下面的代码就可以在 Content 这个控件中显示一个 App 内的资源图片。 @Composable fun Content() { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text("Image Compare") Image( painter = painterResource(R.drawable.compose), modifier = Modifier.size(50.dp), contentScale = ContentScale.Crop, alignment = Alignment.Center, contentDescription = null ) } } 这里可以看出两个问题: ...

October 31, 2024 · korilin

Jetpack Compose 探索

Why compose Compose UI 的编写只需要 Kotlin,在遵循 Android 应用架构时,这样更有利于聚合 UI Elements 的代码,不需要去区分 Kotlin 代码和 xml 布局文件,在我看来这种方式更加容易采用 Android 架构指南去控制项目架构。 但从另一方面来讲,Compose 这种嵌套的 UI 组合方式会加深代码层次,因此开发过程中需要对 UI 上各个元素做更细的区分,以增加代码的可读性。另外如果状态使用没有处理好,也会对 Compose 的重组性能带来影响。 完善的声明式 UI Android View 系统设计的时候是遵循 OOP 的,虽然有 XML 可以帮我们减少下面这种命令式代码的使用,但这种声明式构建 + 命令式执行的缺点还是很明显,因为需要一个加载器把布局转化到业务逻辑代码中。 // 命令式 val parent = ViewGroup(); val node = View(); parent.addView(node); 如果按照理想的声明式 UI 编写方式去改造传统 View 系统,那呈现出的代码可能会包括下面两个特点: 节点的构建不应该有返回值 节点的连接不依赖于 API <LinearLayout> <TextView>Hello World</TextView> <MaterialButton android:onCLick="syaHi()">hi</MaterialButton> </LinearLayout> 将上面的布局代码转换为 Java/Kotlin 理想的声明式代码 LinearLayout { TextView("Hello World") MaterialButton("Hi") { syaHi() } } Compose 利用 Kotlin DSL 构建声明式 UI,一个 @Composable 相当于一个节点,在内部也可以调用其他 @Composable 函数构建子节点。 ...

July 8, 2022 · korilin

探索 Java & Kotlin 泛型

Kotlin 泛型基础 泛型可以让我们在代码中声明类型参数,Kotlin 泛型最基本的使用和 Java 一样,可以声明在类上和函数上,用法也都差不多。 声明在函数上时,可将类型参数作为参数或返回值的类型,该函数为泛型函数 声明在类上时,可以用在任意一处类型声明处,该类为泛型类 class GenericsDemo<T>(t: T) { val value = t } fun <T> invoke(t: T) : T { return t } 我们可以在声明了类型参数的类中,声明一个泛型方法,但如果内部方法所声明的类型参数名称和类上所声明的相同,那么会覆盖类上所声明的类型参数。下面的代码不会报错,并会打印 Hello 字符串。 class GenericsDemo<T>() { fun <T> invoke(t: T) : T { return t } } val demo = GenericsDemo<Int>() println(demo.invoke("Hello")) 此外,我们知道在类中可通过重载来定义同名方法,但这在泛型中并不起作用,如果类中拥有以下两个方法,那么将会报错。 class GenericsDemo<T>() { // 泛型来自类 fun invoke(t: T) : T { return t } // 泛型来自方法本身 fun <S> invoke(s: S) : S { return s } } 上诉代码报错原因是因为两个方法拥有相同的 signature,也就是在 JVM 看来这两个方法的方法名和参数都是一样的,报错信息如下: ...

November 6, 2021 · korilin