You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Jetpack Compose中实现带进度条的本地文件上传功能

Coil 实现带进度条的文件/图片加载上传方案

你现有代码无法实现进度条的核心原因有两个:一是默认的Listener仅回调加载开始、成功、失败、取消的全量状态,不返回实时字节传输进度;二是你把UI组件Box写在了rememberImagePainter的builder作用域内,这个lambda仅用于配置图片请求参数,写在这里的UI代码不会被渲染。


实现步骤

1. 自定义可获取实时进度的Coil拦截器

Coil 2.x及以上版本原生支持进度回调,只需要通过拦截器把进度暴露成可观察的状态即可:

import coil.intercept.Interceptor
import coil.request.ImageResult
import coil.request.Progress
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

class CoilProgressInterceptor : Interceptor {
    private val _progressState = MutableStateFlow(0f)
    // 对外暴露的进度值,区间0f~1f
    val progressState: StateFlow<Float> = _progressState

    override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
        // 每次新请求重置进度
        _progressState.value = 0f
        val newRequest = chain.request.newBuilder()
            .addOnProgressListener { progress: Progress ->
                _progressState.value = if (progress.totalBytes > 0) {
                    progress.bytesTransferred.toFloat() / progress.totalBytes
                } else 0f
            }
            .build()
        return chain.proceed(newRequest)
    }
}

2. 注册拦截器到Coil的ImageLoader

初始化ImageLoader时把自定义拦截器加入组件链,单页面使用可以单独传ImageLoader实例,全局使用可以设为Coil默认加载器:

// 单页面使用可以直接在Composable内初始化,多任务场景请为每个任务创建单独的拦截器实例
val progressInterceptor = remember { CoilProgressInterceptor() }
val imageLoader = remember {
    ImageLoader.Builder(context)
        .components { add(progressInterceptor) }
        .build()
}

3. 叠层布局实现进度条展示

用Box作为容器把图片和进度条叠放,收集进度状态驱动进度条渲染,加载完成后自动隐藏进度条:

// 收集实时进度
val currentProgress by progressInterceptor.progressState.collectAsState()

Box(
    modifier = Modifier
        .fillMaxSize()
        .clip(RoundedCornerShape(16.dp))
) {
    Image(
        painter = rememberImagePainter(
            data = getContentActivityResult.uri,
            imageLoader = imageLoader,
            builder = {
                placeholder(R.drawable.ic_launcher_background)
                // 不要在builder里写UI组件和额外文件操作逻辑
                listener(
                    onError = { _, throwable ->
                        Log.e("TAG", "加载失败: ${throwable.message}")
                    },
                    onStart = { Log.e("TAG", "开始加载资源") },
                    onSuccess = { _, metaData ->
                        Log.e("TAG", "加载完成,数据源:${metaData.dataSource.name}")
                    },
                    onCancel = { Log.e("TAG", "加载已取消") }
                )
            }
        ),
        contentScale = ContentScale.Crop,
        contentDescription = "上传图片",
        modifier = Modifier.fillMaxSize()
    )

    // 加载过程中显示进度条
    if (currentProgress < 1f) {
        LinearProgressIndicator(
            progress = currentProgress,
            modifier = Modifier
                .align(Alignment.BottomCenter)
                .fillMaxWidth()
                .padding(horizontal = 12.dp, vertical = 8.dp),
            color = Color(0xFF1677FF),
            trackColor = Color(0xFFF0F0F0)
        )
    }
}

额外说明

  • 上述方案针对Coil的图片加载场景,如果是纯文件上传(非图片加载展示),不建议用Coil处理请求,可以给OkHttp自定义上传请求体的进度拦截器,核心逻辑和上述实现一致:统计请求体写出的字节数占总字节数的比例,再把进度同步给UI层即可。
  • 同页面存在多个并行上传/加载任务时,不要复用同一个拦截器实例,否则会出现进度串扰的问题,给每个任务单独创建拦截器实例绑定对应进度条即可。
  • 你原有代码中getFile(getContentActivityResult)的逻辑不要写在图片请求的builder里,这个lambda在重组过程中可能被反复执行,容易引发重复文件操作的问题,文件处理逻辑请放到LaunchedEffect这类副作用API里执行。

内容的提问来源于stack exchange,提问作者azizconi1

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 07:27:22