在本笔记中,将结合实例介绍produceState和derivedStateOf两个可组合函数。它们分别实现状态的转换。
(1)produceState将非Compose状态转换虫Compose状态
(2)derivedStateOf将多个状态转换成其他状态。
一、produceState
produceState可将非 Compose 状态转换为 Compose 状态,它会在没有定义数据源的情况下随时间生成值。produceState 会启动一个协程,该协程将作用域限定为可将值推送到返回的 State 的组合。
produceState可组合函数返回一个可观察的快照状态,当produceState函数进入组合会启动producer的操作,创建一个状态值。如果key1和key2的值发生了变化,正在运行的producer会被取消并重新加载创建新的状态源。producer使用ProducerStateScope.value为返回的状态设置新的值:
如果ProducerStateScope.value混淆了返回状态的新旧值,使得ProducerStateScope.value返回的状态的值仍为状态旧值,则不会观察到任何的更改。
如果在频繁设置多个新值,那么观察者只会观察到最新的值。
在下列中访问并显示网络中的在线图片,因为网络请求到缓存到本地存在时间差,因此考虑将这样的处理使用produceState来完成。只要状态值更新,就刷新界面。
(1)定义请求结果的类
请求资源结果的类OpResult是密封类,封装了两种处理方式:请求图片资源成功和请求图片资源失败两种子类。
sealed class OpResult<T>(){
object Loading:OpResult<ImageBitmap>()
object Error:OpResult<ImageBitmap>()
data class Success(val image:ImageBitmap):OpResult<ImageBitmap>()
}
(2)定义图片库ImageRepository
class ImageRepository constructor(context:Context){
val context:Context = context
var id = 0
//https://wome.com网站不存在,网站的图片链接均为假,调试时请自行设置
val imageLst =listOf("https://some.com.1.jpg",
"https://some.com.2.jpg",
"https://some.com.3.jpg",
"https://some.com.4.jpg")
fun findByUrl(url:String) = imageLst.indexOf(url)
//请求下一个图片资源的url字符串
suspend fun next():String{
id = (id+1)%imageLst.size
return imageLst.get(id)
}
//请求上一个图片资源的url字符串
suspend fun prev():String{
id = (id-1+imageLst.size)%imageLst.size
return imageLst.get(id)
}
/加载在线图片资源,返回ImageBitmap
suspend fun loadImageByUrl(url:String): ImageBitmap {
//请求在线图片
val request = ImageRequest.Builder(context).data(url).build()
val imageLoader by lazy{ ImageLoader(context) }
return (imageLoader.execute(request).drawable as BitmapDrawable)
.bitmap.asImageBitmap().apply{
//延迟两秒加载图片
delay(1000)
}
}
}
因为要访问网络在线图片。因此,需要在应用的AndroidManifest.xml中设置网络访问权限:
<uses-permission android:name="android.permission.INTERNET" />
在上述请求网络在线的图片资源,需要coil库,因此在项目模块的build.gradle.kt中设置为:
implementation(“io.coil-kt:coil-compose:2.4.0”)
(3)定义可组合函数请求在线图片
/**
* 将非Composable状态转换成Composable状态
* @param imageRepository ImageRepository 图片仓库
* @return State<OpResult<ImageBitmap>>
*/
@Composable
fun loadNetworkImages(imageRepository: ImageRepository):State<OpResult<ImageBitmap>>{
return produceState<OpResult<ImageBitmap>>(initialValue = OpResult.Loading){
for(i in 0 until imageRepository.imageLst.size) {
var image = imageRepository.loadImageByUrl(imageRepository.imageLst[i])
value = if (image == null) {
OpResult.Error
} else {
OpResult.Success(image)
}
}
}
}
在此处,返回一个包含请求资源结果的状态,根据循环,使得图片列表的索引值发生变换,从图片仓库对象imageRepository中要加载的图片。如果图片请求成功,将image图片封装到状态的值为OpResult.Success(image)中。
(4)定义界面ImageScreen
@Composable
fun ImageScreen(imageRepository: ImageRepository) {
val result by loadNetworkImages(imageRepository = imageRepository) //从图片仓库中请求资源,返回包含请求结果的状态
Box(
modifier = Modifier.fillMaxSize().background(Color.Black),
contentAlignment = Alignment.Center
) {
Column(
modifier = Modifier.fillMaxWidth().height(300.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
when(result) {
is OpResult.Success -> {
Image(modifier = Modifier.size(400.dp,300.dp),
bitmap = (result as OpResult.Success).image,
contentDescription = null
)
}
is OpResult.Error -> {
Image(
imageVector = Icons.Filled.Warning,
contentDescription = null,
modifier = Modifier.size(200.dp, 200.dp)
)
}
else -> {//等待加载图片时,显示圆形进度条
CircularProgressIndicator()
}
}
}
}
}
(5)定义主活动MainActivity
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val imageRepository = ImageRepository(this)
setContent {
Ch06_DemoTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
ImageScreen(imageRepository)
}
}
}
}
}
运行结果就是图片动态显示直至最后一张图片为止,如下图所示:
模拟器中图片资源来自于网络。如有侵权,会主动删除。
二、derivedStateOf
derivedStateOf函数将一个或多个状态对象转换为其他状态,使用此函数可确保仅当计算中使用的状态之一发生变化时才会进行计算。首先,从一个简单的例子来初步了解:
@Composable
fun DisplayScreen(){
val yearState = remember{mutableStateOf(2023)}
val monthState = remember{mutableStateOf(11)}
val calendarState = remember {
derivedStateOf {
"${yearState.value}年-${monthState.value}月"
}
}
Box(contentAlignment = Alignment.Center){
Column(modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally){
Text("${calendarState.value}",fontSize=30.sp)
Button(onClick ={
monthState.value = (monthState.value)%12
if(monthState.value == 0 ){
yearState.value++
}
monthState.value++
}){
Text("修改月份",fontSize = 24.sp)
}
}
}
}
在上述的代码中,定义了两个状态值分别为yearState和monthState,通过derivedStateOf函数将两个状态的值连接生成一个新的状态CalendarState,而这个CalendarState的值为字符串。通过点击按钮,修改monthState和yearState的值,在界面中显示的CalendarState的值也发生相应的变化。
参考文献
Compose 中的附带效应
https://developer.android.google.cn/jetpack/compose/side-effects?hl=zh-cn