如何在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
相关产品推荐
相关产品推荐

