Compose - 使用 Coil

一、概念

相比 Glide、Picasso 而言,Coil 更专注于 Compose 且它的代码使用 Kotlin。

1.1 缩放 ContentScale

ContentScale.Crop
ContentScale.Inside
ContentScale.None
ContentScale.Fit
ContentScale.FillHeight
ContentScale.FillWidth
ContentScale.FillBounds

二、添加依赖

最新版本

val coilVersion = "3.3.0"
implementation("io.coil-kt.coil3:coil-compose:$coilVersion")
//可选,如果没有集成过 Retrofit 或 OKHttp 的话
implementation("io.coil-kt.coil3:coil-network-okhttp:$coilVersion")
//可选,支持GIF
implementation("io.coil-kt.coil3:coil-gif:$coilVersion")
//可选,支持SVG
implementation("io.coil-kt.coil3:coil-svg:$coilVersion")

三、简单使用

根据加载的状态,AsyncImage() 只能设置不同的图,SubcomposeAsyncImage() 可以设置不同界面。

单界面

@Composable
@NonRestartableComposable
fun AsyncImage(
    model: Any?,        //传入 ImageRequest 或其 deda 属性
    contentDescription: String?,        //无障碍描述
    modifier: Modifier = Modifier,
    placeholder: Painter? = null,        //加载中占位图
    error: Painter? = null,        //失败占位图
    fallback: Painter? = error,        //空数据占位图(参数model是null的时候)
    onLoading: ((State.Loading) -> Unit)? = null,        //加载中的回调
    onSuccess: ((State.Success) -> Unit)? = null,        //请求成功的回调
    onError: ((State.Error) -> Unit)? = null,                //请求失败的回调
    alignment: Alignment = Alignment.Center,        //对齐
    contentScale: ContentScale = ContentScale.Fit,        //缩放
    alpha: Float = DefaultAlpha,        //透明度
    colorFilter: ColorFilter? = null,        //滤镜颜色
    filterQuality: FilterQuality = DefaultFilterQuality,        //滤镜质量(采样算法)
    clipToBounds: Boolean = true,        //裁剪边界(true内容奖被裁剪至边界范围内)
)

@Composable
@NonRestartableComposable
fun AsyncImage(
    model: Any?,
    contentDescription: String?,
    modifier: Modifier = Modifier,
    transform: (State) -> State = DefaultTransform,
    onState: ((State) -> Unit)? = null,                                //状态被合并需要手动判断
    alignment: Alignment = Alignment.Center,
    contentScale: ContentScale = ContentScale.Fit,
    alpha: Float = DefaultAlpha,
    colorFilter: ColorFilter? = null,
    filterQuality: FilterQuality = DefaultFilterQuality,
    clipToBounds: Boolean = true,
)
多界面

@Composable
@NonRestartableComposable
fun SubcomposeAsyncImage(
    model: Any?,
    contentDescription: String?,
    modifier: Modifier = Modifier,
    transform: (State) -> State = DefaultTransform,

    //加载界面
    loading: @Composable (SubcomposeAsyncImageScope.(State.Loading) -> Unit)? = null,

    //成功界面
    success: @Composable (SubcomposeAsyncImageScope.(State.Success) -> Unit)? = null,

    //失败界面
    error: @Composable (SubcomposeAsyncImageScope.(State.Error) -> Unit)? = null,
    onLoading: ((State.Loading) -> Unit)? = null,
    onSuccess: ((State.Success) -> Unit)? = null,
    onError: ((State.Error) -> Unit)? = null,
    alignment: Alignment = Alignment.Center,
    contentScale: ContentScale = ContentScale.Fit,
    alpha: Float = DefaultAlpha,
    colorFilter: ColorFilter? = null,
    filterQuality: FilterQuality = DefaultFilterQuality,
    clipToBounds: Boolean = true,
)

AsyncImage(
    model = "https://example.com/image.jpg",    //网络资源
//    model = R.drawable.bkg_naruto,    //本地资源(没必要,用系统自带的Image)
    //需要手动判断状态的重载
    onState = { state ->
        when(state) {
            AsyncImagePainter.State.Empty -> TODO()
            is AsyncImagePainter.State.Error -> TODO()
            is AsyncImagePainter.State.Loading -> TODO()
            is AsyncImagePainter.State.Success -> TODO()
        }
    }
)

四、全局配置 ImageLoader

非必须,不自定义会使用默认的。

4.1 初始化方式(选一个)

4.1.1 通过 Application 实现(推荐)

class MyApplication : Application(), SingletonImageLoader.Factory {
    override fun newImageLoader(context: PlatformContext): ImageLoader {
        return ImageLoader.Builder(context)
            .build()
    }
}

4.1.2 通过 Application#onCreate() 调用

确保不会覆盖已创建的现有图像加载器。如果是单 Activity 也可以在 Activity#onCreate() 中调用。

class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        SingletonImageLoader.setSafe { context ->
            ImageLoader.Builder(context)
                .build()
        }
    }
}

4.1.3 通过 Compose 根入口调用

适合 Compose Multiplatform。

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            setSingletonImageLoaderFactory { context ->
                ImageLoader.Builder(context)
                    .build()
            }
            Screen()
        }
    }
}

4.1.4 通过 StartUp 配置

class CoilInitializer : Initializer<SingletonImageLoader> {
    override fun create(context: Context): SingletonImageLoader {
        SingletonImageLoader.setSafe { context ->
            ImageLoader.Builder(context)
                .build()
        }
        return SingletonImageLoader
    }

    override fun dependencies(): List<Class<out Initializer<*>?>?> {
        return mutableListOf()
    }
}
<provider
    android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge"
    >
    <meta-data
        android:name="com.momentum.ui.core.startup.CoilInitializer"
        android:value="androidx.startup"
        />
</provider>

4.2 功能配置

ImageLoader.Builder(context)
    //淡入淡出
    .crossfade(true)    //传入Int多少毫秒,或true为200
    //添加日志(会降低性能,且不要用于生产环境!)
    .logger(DebugLogger())
    //内存缓存
    .memoryCache {
        MemoryCache.Builder()
            .maxSizePercent(context, 0.25)  //此APP可用内存的百分比
            .build()
    }
    //磁盘缓存
    .diskCache {
        DiskCache.Builder()
            .directory(context.cacheDir.resolve("coil_cache"))
            .maxSizePercent(0.2)    //设备可用磁盘空间的百分比
            .build()
    }
    //更多特性支持
    .components {
        //支持SVG
        add(SvgDecoder.Factory())
        //支持GIF
        if (SDK_INT >= 28) {
            add(AnimatedImageDecoder.Factory())
        } else {
            add(GifDecoder.Factory())
        }
    }
    .build()
//清除缓存
SingletonImageLoader.get(context).memoryCache?.clear()
SingletonImageLoader.get(context).diskCache?.clear()

五、请求配置 ImageRequest

单独对可组合项设置,会覆盖全局设置。

AsyncImage(
    model = ImageRequest.Builder(LocalContext.current)
        .data("https://example.com/image.jpg")
        .crossfade(true)
        .size(30)    //限制图片的像素大小
        .build()
)

六、返回 Painter 而不使用可组合项

当不方便使用 AsyncImage() 或 SubcomposeAsyncImage() 可组合项时,返回一个 Painter 可用于自带组件 Image()。

@Composable
@NonRestartableComposable
fun rememberAsyncImagePainter(
    model: Any?,
    imageLoader: ImageLoader,
    placeholder: Painter? = null,
    error: Painter? = null,
    fallback: Painter? = error,
    onLoading: ((State.Loading) -> Unit)? = null,
    onSuccess: ((State.Success) -> Unit)? = null,
    onError: ((State.Error) -> Unit)? = null,
    contentScale: ContentScale = ContentScale.Fit,
    filterQuality: FilterQuality = DefaultFilterQuality,
)
@Composable
@NonRestartableComposable
fun rememberAsyncImagePainter(
    model: Any?,
    transform: (State) -> State = DefaultTransform,
    onState: ((State) -> Unit)? = null,                        //状态合并版本
    contentScale: ContentScale = ContentScale.Fit,
    filterQuality: FilterQuality = DefaultFilterQuality,
)
val painter = rememberAsyncImagePainter(
    model = ""
)
//设置给自带控件
Image(
    painter = painter,
    contentDescription = ""
)
//收集状态加载不同界面
val state by painter.state.collectAsState()
when(state) {
    AsyncImagePainter.State.Empty -> TODO()
    is AsyncImagePainter.State.Error -> TODO()
    is AsyncImagePainter.State.Loading -> TODO()
    is AsyncImagePainter.State.Success -> TODO()
}

七、网图预览处理

由于预览环境禁用了网络访问,因此网络 URL 的请求将始终失败。在 Android Studio 预览中的行为由 LocalAsyncImagePreviewHandler 控制。

val previewHandler = AsyncImagePreviewHandler {
    ColorImage(Color.Red.toArgb())
}

CompositionLocalProvider(LocalAsyncImagePreviewHandler provides previewHandler) {
    AsyncImage(
        model = "https://example.com/image.jpg",
        contentDescription = null,
    )
}

[versions] agp = "8.9.1" kotlin = "2.1.10" ksp = "2.1.10-1.0.30" compose-bom = "2025.02.00" lifecycle = "2.8.7" navigation = "2.8.7" activity-compose = "1.10.0" kotlinx-coroutines = "1.10.1" coil-compose = "2.7.0" compose-destination = "2.1.0-beta16" sheets-compose-dialogs = "1.3.0" markdown = "4.6.2" webkit = "1.12.1" appiconloader-coil = "1.5.0" parcelablelist = "2.0.1" libsu = "6.0.0" apksign = "1.4" cmaker = "1.2" compose-material = "1.7.8" compose-material3 = "1.3.1" compose-ui = "1.7.8" compose-foundation = "1.7.8" documentfile = "1.0.1" protobuf = "3.22.1" protobuf-gradle-plugin = "0.9.1" material = "1.10.0" androidxLifecycle = "2.8.7" squareRetrofit = "2.11.0" squareOkhttp = "4.12.0" kotlinxSerialization = "1.7.1" navigationFragmentKtx = "2.6.0" navigationUiKtx = "2.6.0" core-ktx = "1.13.0" gson = "2.10.1" room = "2.6.1" datastore-preferences = "1.1.1" serialization-protobuf = "1.6.0" work-manager = "2.10.0" zip4j = "2.11.5" apache-commons = "3.10.0" apache-commons-codec = "1.16.1" kotlinx-coroutines-core-jvm = "1.8.0" pickyou = "3.0.4" smbj = "0.14.0" smbj-rpc = "0.12.9" sshj = "0.38.0" guava-compat = "9999.0-empty-to-avoid-conflict-with-guava" sardine-next = "1.0.2" refine = "4.4.0" appcompat = "1.7.0" hilt-android = "2.49" hilt-android-compiler = "2.48" hilt-navigation-compose = "1.2.0" hilt-work = "1.2.0" hilt = "2.48" hilt-work-compiler = "1.2.0" mlkit-text = "16.0.1" mlkit-common-version = "18.11.0" activity = "1.9.1" savedstate = "1.2.1" espresso-core = "3.6.1" ktor-version = "2.0.3" quickie-bundled = "1.10.0" [plugins] agp-app = { id = "com.android.application", version.ref = "agp" } agp-lib = { id = "com.android.library", version.ref = "agp" } kotlin = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } lsplugin-apksign = { id = "org.lsposed.lsplugin.apksign", version.ref = "apksign" } lsplugin-cmaker = { id = "org.lsposed.lsplugin.cmaker", version.ref = "cmaker" } butterknife = { id = "com.jakewharton", version = "10.2.3" } com-google-protobuf = { id = "com.google.protobuf", version.ref = "protobuf-gradle-plugin" } hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } refine = { id = "dev.rikka.tools.refine", version.ref = "refine" } serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } application-common = { id = "application.common", version = "unspecified" } application-compose = { id = "application.compose", version = "unspecified" } application-hilt = { id = "application.hilt", version = "unspecified" } application-hilt-work = { id = "application.hilt.work", version = "unspecified" } library-common = { id = "library.common", version = "unspecified" } library-hilt = { id = "library.hilt", version = "unspecified" } library-hilt-work = { id = "library.hilt.work", version = "unspecified" } library-room = { id = "library.room", version = "unspecified" } library-compose = { id = "library.compose", version = "unspecified" } library-protobuf = { id = "library.protobuf", version = "unspecified" } library-test = { id = "library.test", version = "unspecified" } library-androidTest = { id = "library.androidTest", version = "unspecified" } library-firebase = { id = "library.firebase", version = "unspecified" } [libraries] androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" } androidx-foundation = { module = "androidx.compose.foundation:foundation" } androidx-material3 = { module = "androidx.compose.material3:material3" } androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } androidx-compose-material = { group = "androidx.compose.material", name = "material", version.ref = "compose-material" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "compose-material3" } androidx-compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "compose-ui" } androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview", version.ref = "compose-ui" } androidx-compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose-foundation" } androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" } androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" } androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" } androidx-ui = { module = "androidx.compose.ui:ui" } androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" } com-github-topjohnwu-libsu-core = { group = "com.github.topjohnwu.libsu", name = "core", version.ref = "libsu" } com-github-topjohnwu-libsu-service = { group = "com.github.topjohnwu.libsu", name = "service", version.ref = "libsu" } com-github-topjohnwu-libsu-io = { group = "com.github.topjohnwu.libsu", name = "io", version.ref = "libsu" } dev-rikka-rikkax-parcelablelist = { module = "dev.rikka.rikkax.parcelablelist:parcelablelist", version.ref = "parcelablelist" } io-coil-kt-coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil-compose" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } me-zhanghai-android-appiconloader-coil = { group = "me.zhanghai.android.appiconloader", name = "appiconloader-coil", version.ref = "appiconloader-coil" } compose-destinations-core = { group = "io.github.raamcosta.compose-destinations", name = "core", version.ref = "compose-destination" } compose-destinations-ksp = { group = "io.github.raamcosta.compose-destinations", name = "ksp", version.ref = "compose-destination" } sheet-compose-dialogs-core = { group = "com.maxkeppeler.sheets-compose-dialogs", name = "core", version.ref = "sheets-compose-dialogs" } sheet-compose-dialogs-list = { group = "com.maxkeppeler.sheets-compose-dialogs", name = "list", version.ref = "sheets-compose-dialogs" } sheet-compose-dialogs-input = { group = "com.maxkeppeler.sheets-compose-dialogs", name = "input", version.ref = "sheets-compose-dialogs" } markdown = { group = "io.noties.markwon", name = "core", version.ref = "markdown" } lsposed-cxx = { module = "org.lsposed.libcxx:libcxx", version = "27.0.12077973" } androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" } androidx-lifecycle-service = { module = "androidx.lifecycle:lifecycle-service", version.ref = "androidxLifecycle" } androidx-core = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" } # Koin Inject # https://insert-koin.io/docs/setup/koin koin-core = { group = "io.insert-koin", name = "koin-core", version = "3.4.0" } koin-android = { group = "io.insert-koin", name = "koin-android", version = "3.4.0" } koin-compose = { group = "io.insert-koin", name = "koin-androidx-compose", version = "3.4.4" } protoc = { group = "com.google.protobuf", name = "protoc", version.ref = "protobuf" } protobuf-javalite = { group = "com.google.protobuf", name = "protobuf-javalite", version.ref = "protobuf" } material = { group = "com.google.android.material", name = "material", version.ref = "material" } kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" } kotlinx-serialization-protobuf = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-protobuf", version.ref = "kotlinxSerialization" } square-okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "squareOkhttp" } square-logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "squareOkhttp" } square-okhttp-dnsoverhttps = { group = "com.squareup.okhttp3", name = "okhttp-dnsoverhttps", version.ref = "squareOkhttp" } square-retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "squareRetrofit" } square-retrofit-kotlinxSerialization = { group = "com.squareup.retrofit2", name = "converter-kotlinx-serialization", version.ref = "squareRetrofit" } retrofit-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "squareRetrofit" } smbj = { module = "com.hierynomus:smbj", version.ref = "smbj" } smbj-rpc = { module = "com.rapid7.client:dcerpc", version.ref = "smbj-rpc" } sshj = { module = "com.hierynomus:sshj", version.ref = "sshj" } guava-compat = { module = "com.google.guava:listenablefuture", version.ref = "guava-compat" } sardine-next = { module = "com.github.xayahsususu:sardine-next", version.ref = "sardine-next" } # rhino mozilla-rhino = "org.mozilla:rhino:1.8.0" mozilla-rhino-xml = "org.mozilla:rhino-xml:1.8.0" mozilla-rhino-tools = "org.mozilla:rhino-tools:1.8.0" #navigation androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationFragmentKtx" } androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" } lifecycle-compiler = { group = "androidx.lifecycle", name = "lifecycle-compiler", version.ref = "lifecycle"} gson = { module = "com.google.code.gson:gson", version.ref = "gson" } serialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "serialization-protobuf" } androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastore-preferences" } # Dependencies of the included build-logic android-build-logic = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" } kotlin-build-logic = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } ksp-build-logic = { group = "com.google.devtools.ksp", name = "com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } #firebase-crashlytics-build-logic = { group = "com.google.firebase", name = "firebase-crashlytics-gradle", version.ref = "firebase-crashlytics-gradle" } pickyou = { module = "com.github.xayahsususu:libpickyou", version.ref = "pickyou" } zip4j = { module = "net.lingala.zip4j:zip4j", version.ref = "zip4j" } androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "work-manager" } apache-commons-net = { module = "commons-net:commons-net", version.ref = "apache-commons" } apache-commons-codec = { module = "commons-codec:commons-codec", version.ref = "apache-commons-codec" } kotlinx-coroutines-core-jvm = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm", version.ref = "kotlinx-coroutines-core-jvm" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } refine-annotation = { module = "dev.rikka.tools.refine:annotation", version.ref = "refine" } refine-annotation-processor = { module = "dev.rikka.tools.refine:annotation-processor", version.ref = "refine" } hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt-android" } hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt-android-compiler" } hilt-work = { module = "androidx.hilt:hilt-work", version.ref = "hilt-work" } hilt-work-compiler = { module = "androidx.hilt:hilt-compiler", version.ref = "hilt-work-compiler" } androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" } okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "squareOkhttp" } # commons commons-exec = "org.apache.commons:commons-exec:1.3" commons-io = "commons-io:commons-io:2.6" timscriptov-apksigner = "com.github.TimScriptov:apksigner:1.2.0" core-ktx = { module = "androidx.core:core-ktx", version.ref = "core-ktx" } google-gson = { module = "com.google.code.gson:gson", version.ref = "gson" } # androidx compose compose-bom = "androidx.compose:compose-bom:2025.02.00" compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" } compose-ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4" } compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } compose-material = { module = "androidx.compose.material:material" } compose-ui = { module = "androidx.compose.ui:ui" } # mlkit mlkit-common = { group = "com.google.mlkit", name = "common", version.ref = "mlkit-common-version" } mlkit-text-recognition = { module = "com.google.mlkit:text-recognition", version.ref = "mlkit-text" } mlkit-text-recognition-chinese = { module = "com.google.mlkit:text-recognition-chinese", version.ref = "mlkit-text" } mlkit-text-recognition-devanagari = { module = "com.google.mlkit:text-recognition-devanagari", version.ref = "mlkit-text" } mlkit-text-recognition-japanese = { module = "com.google.mlkit:text-recognition-japanese", version.ref = "mlkit-text" } mlkit-text-recognition-korean = { module = "com.google.mlkit:text-recognition-korean", version.ref = "mlkit-text" } androidx-viewpager2 = "androidx.viewpager2:viewpager2:1.1.0" androidx-work = "androidx.work:work-runtime:2.9.1" androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version = "2.2.0" } androidx-activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activity" } documentfile = "androidx.documentfile:documentfile:1.0.1" androidx-savedstate = { module = "androidx.savedstate:savedstate", version.ref = "savedstate" } androidx-savedstate-ktx = { module = "androidx.savedstate:savedstate-ktx", version.ref = "savedstate" } preference-ktx = "androidx.preference:preference-ktx:1.2.1" androidx-annotation = "androidx.annotation:annotation:1.9.1" activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activity" } espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" } # ktor ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor-version" } ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor-version" } ktor-server-websockets = { module = "io.ktor:ktor-server-websockets", version.ref = "ktor-version" } ktor-client-websockets = { module = "io.ktor:ktor-client-websockets", version.ref = "ktor-version" } ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor-version" } ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor-version" } #shizuku shizuku-api = "dev.rikka.shizuku:api:13.1.5" shizuku-provider = "dev.rikka.shizuku:provider:13.1.5" kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" } appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } compose-material3 = "androidx.compose.material3:material3:1.3.1" compose-material3-window-size = "androidx.compose.material3:material3-window-size-class:1.3.1" compose-material3-adaptive-navigation-suite = "androidx.compose.material3:material3-adaptive-navigation-suite:1.3.1" #rxjava quickie-bundled = { module = "io.github.g00fy2.quickie:quickie-bundled", version.ref = "quickie-bundled" } rxjava2 = "io.reactivex.rxjava2:rxjava:2.2.21" rxjava2-rxandroid = "io.reactivex.rxjava2:rxandroid:2.1.1" rxjava3 = "io.reactivex.rxjava3:rxjava:3.1.5" rxjava3-rxandroid = "io.reactivex.rxjava3:rxandroid:3.0.2" javet-android-node = "com.caoccao.javet:javet-android-node:3.1.4" leakcanary-android = "com.squareup.leakcanary:leakcanary-android:2.13" leakcanary-objectinfo-watcher-android = "com.squareup.leakcanary:leakcanary-object-watcher-android:2.13" retrofit2-retrofit = "com.squareup.retrofit2:retrofit:2.11.0" retrofit2-converter-gson = "com.squareup.retrofit2:converter-gson:2.11.0" #Glide glide = "com.github.bumptech.glide:glide:4.8.0" glide-ksp = "com.github.bumptech.glide:ksp:4.14.2" glide-compiler = "com.github.bumptech.glide:compiler:4.12.0" #lifecycle lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" } lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycle" } lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime", version.ref = "lifecycle" } lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle" } lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycle" } lifecycle-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "lifecycle" } lifecycle-service = { module = "androidx.lifecycle:lifecycle-service", version.ref = "lifecycle" } androidx-swiperefreshlayout = "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" ktsh = "com.jaredrummler:ktsh:1.0.0" coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil-compose" } [bundles] ktor = ["ktor-server-core", "ktor-server-netty", "ktor-server-websockets", "ktor-client-websockets", "ktor-client-okhttp", "ktor-client-core"] mlkit = [ "mlkit-text-recognition", "mlkit-text-recognition-chinese", "mlkit-text-recognition-devanagari", "mlkit-text-recognition-japanese", "mlkit-text-recognition-korean" ] shizuku = ["shizuku-api", "shizuku-provider"] 这个是一个Android Studio 的 libs.versions.toml 文件的代码,请帮我重构,去掉重复的代码,类似于ktsh = "com.jaredrummler:ktsh:1.0.0"这样的代码,修改为 module 或在 group 格式,并且在[versions]中定义版本号
08-19
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值