加载数据在Android开发中应该算是非常频繁的操作了,因此简单在Jetpack Compose中实现一个通用的加载微件
效果如下:
加载中(转圈圈)
加载中 | 加载完成 |
---|---|
另外加载失败后显示失败并可以点击重试
实现
实现这个微件其实非常简单,无非就是根据不同的状态加载不同页面
加载状态Bean
首先把加载的状态抽象出来,写个数据类
sealed class LoadingState<out R> {
object Loading : LoadingState<Nothing>()
data class Failure(val error : Throwable) : LoadingState<Nothing>()
data class Success<T>(val data : T) : LoadingState<T>()
val isLoading
get() = this is Loading
val isSuccess
get() = this is Success<*>
}
此处借鉴了朱江大佬写的玩安卓Compose版本,在此感谢
微件
然后就是加载了。考虑到加载一般耗时,所以用协程。
写成微件大概就是下面这个样子
private const val TAG = "LoadingWidget"
/**
* 通用加载微件
* @author [FunnySaltyFish](https://blog.funnysaltyfish.fun/)
* @param modifier Modifier 整个微件包围在Box中,此处修饰此Box
* @param loader 加载函数,返回值为正常加载出的结果
* @param loading 加载中显示的页面,默认为转圈圈
* @param failure 加载失败显示的页面,默认为文本,点击可以重新加载(retry即为重新加载的函数)
* @param success 加载成功后的页面,参数[T]即为返回的结果
*/
@Composable
fun <T> LoadingContent(
modifier: Modifier = Modifier,
loader : suspend ()->T,
loading : @Composable ()->Unit = { DefaultLoading() },
failure : @Composable (error : Throwable, retry : ()->Unit)->Unit = { error, retry->
DefaultFailure(error, retry)
},
success : @Composable (data : T?)->Unit
) {
var key by remember {
mutableStateOf(false)
}
val state : LoadingState<T> by produceState<LoadingState<T>>(initialValue = LoadingState.Loading, key){
value = try {
Log.d(TAG, "LoadingContent: loading...")
LoadingState.Success(loader())
}catch (e: Exception){
LoadingState.Failure(e)
}
}
Box(modifier = modifier){
when(state){
is LoadingState.Loading -> loading()
is LoadingState.Success<T> -> success((state as LoadingState.Success<T>).data)
is LoadingState.Failure -> failure((state as LoadingState.Failure).error){
key = !key
Log.d(TAG, "LoadingContent: newKey:$key")
}
}
}
}
基于produceState加载并保存数据,然后根据不同的加载状态显示不同的页面。
官方对此函数的翻译如下:
/**
* Return an observable [snapshot][androidx.compose.runtime.snapshots.Snapshot] [State] that
* produces values over time from [key1].
*
* [producer] is launched when [produceState] enters the composition and is cancelled when
* [produceState] leaves the composition. If [key1] changes, a running [producer] will be
* cancelled and re-launched for the new source. [producer] should use [ProduceStateScope.value]
* to set new values on the returned [State].
*
* The returned [State] conflates values; no change will be observable if
* [ProduceStateScope.value] is used to set a value that is [equal][Any.equals] to its old value,
* and observers may only see the latest value if several values are set in rapid succession.
*
* [produceState] may be used to observe either suspending or non-suspending sources of external
* data, for example:
*
* @sample androidx.compose.runtime.samples.ProduceState
*
* @sample androidx.compose.runtime.samples.ProduceStateAwaitDispose
*/
翻译过来就是:
/**
* 返回一个可观察的[snapshot][androidx.compose.runtime.snapshots.Snapshot] [State] 对象,它的值由[key1]随时间变化产生.
*
* [producer] 在 [produceState] 进入 composition 后会被启动,当[produceState] 离开 composition 时被取消. 如果 [key1] 改变, 当前正在运行的 [producer] 将被取消并根据新来源重启.
* [producer] 应当使用 [ProduceStateScope.value] ,在返回的 [State] 中设置 value 的值.
*
* 返回的 [State] 与 value 一致; 如若新的值与旧value相等[Any.equals] ,则此变化不会被观察到
* 如果在短时间内多个新值被赋予,则观察着可能仅能观察到最新的值
*
* [produceState] 可被用在 suspending / non-suspending 的外部数据来源中,如:
*
* @sample androidx.compose.runtime.samples.ProduceState
*
* @sample androidx.compose.runtime.samples.ProduceStateAwaitDispose
*/
除开数据加载外,上面的代码也给出了几个默认页面。分别有默认的加载页面(转圈圈)和默认的错误页面(点击重试)
@Composable
fun DefaultLoading() {
CircularProgressIndicator()
}
@Composable
fun DefaultFailure(error: Throwable, retry : ()->Unit) {
Text(text = stringResource(id = R.string.loading_error), modifier = Modifier.clickable(onClick = retry))
}
此微件使用起来也很简单直接
LoadingContent(
modifier = Modifier.align(CenterHorizontally) ,
loader = (vm.sponsorService::getAllSponsor)
) { list ->
list?.let{
Column {
SponsorList(it)
Text("加载完成")
}
}
}
完整代码在这里:
加载微件:FunnyTranslation/LoadingWidget.kt
使用示例:FunnyTranslation/ThanksScreen.kt
这只是一个简单方案,由衷欢迎各位探讨。
最后再自荐一下我的开源项目(就是上面那个链接),Jetpack Compose实现的翻译软件。不妨点个star,万一什么地方有用呢(逃~