Compose基于mutableStateOf的数据驱动实践

666 阅读2分钟

一, 概念

由于 Compose 是声明式工具集,因此更新它的唯一方法是通过新参数调用同一可组合项。
这些参数是界面状态的表现形式。每当状态更新时,都会发生重组。

这是官方的解释,那么要刷新ui就只能通过管理state的形式,通过状态变更重组组件,其实就是响应式编程

响应式编程就是数据发生了变化,对应的界面就重新绘制,类似react,vue和flutter
传统的界面写法是命令式编程,都是主动告诉界面view变更

二, 示例

这里是官方示例,需要在Composable中可组合项中的状态remembermutableStateOf

mutableStateOf会创建可观察的 MutableState<T>,value如有任何更改,系统会安排重组读取value的所有可组合函数

remember将值存储起来,当界面发生了重新绘制,就会读之前存储的值

当onValueChange变化时,Text数据就会更新

@Composable
fun HelloContent() {
   Column(modifier = Modifier.padding(16.dp)) {
       var name by remember { mutableStateOf("") }
       if (name.isNotEmpty()) {
           Text(
               text = "Hello, $name!",
               modifier = Modifier.padding(bottom = 8.dp),
               style = MaterialTheme.typography.h5
           )
       }
       OutlinedTextField(
           value = name,
           onValueChange = { name = it },
           label = { Text("Name") }
       )
   }
}

三, 实践

实际项目中使用的时候,数据变更往往是由网络或者其他模块事件下驱动,所以我们创建Store管理这些状态, 通过对数据的更新,来变更ui

object Store {
    var logs = mutableStateOf(emptyList<Message>())    //必须采用emptyList.否则数据可能不更新
    val visible = mutableStateOf(true)
    val device = Device()

    private val bufferedImage: BufferedImage? = null
    val camera1bufferedImage = mutableStateOf(bufferedImage)
    val camera2bufferedImage = mutableStateOf(bufferedImage)
    val camera3bufferedImage = mutableStateOf(bufferedImage)

    init {
        for (i in 0..99999) {
            pushLogToUI { Message(Math.random().toString()) }
        }
    }
}

画边框和虚线

采用box,设置边框

Column(Modifier.padding(top = 5.dp).wrapContentWidth()) {
    Box(
        contentAlignment = Alignment.Center,
        modifier = Modifier
            .padding(top = 5.dp)
            .size(Dp(mvPreviewWidth.toFloat()), Dp(mvPreviewHeight.toFloat()))
            .border(1.dp, color = Color.Gray),
    ) {
        drawline(true)
    }
}

采用Canvas基于坐标画虚线

@Composable
fun drawline(offsetStart: (Size) -> Offset, offsetEnd: (Size) -> Offset) {
    Canvas(modifier = Modifier.fillMaxSize()) {
        //绘制直线
        drawLine(
            start = offsetStart.invoke(size),
            end = offsetEnd.invoke(size),
            color = Color.Black,
            strokeWidth = 1F, //设置直线宽度
            pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f)  //设置为虚线
        )
    }
}

效果如下所示: image.png

显示图片

这里是获取摄像头的每一帧BufferedImage,并将BufferedImage转换为Image支持的Bitmap,bufferedImage.toComposeImageBitmap()

bufferedImage.value?.let {
    Image(
        contentDescription = "",
        contentScale = ContentScale.Crop,
        bitmap = it.toComposeImageBitmap(),
        modifier = Modifier.fillMaxSize()
    )
}

效果如下: image.png

列表展示

起初对state理解不够深刻,@Composable里面使用mutableStateOf如何去驱动数据,数据怎么传进来,如下:

@Composable
fun messageList(more: Boolean, onClick: () -> Unit) {
    val messages = remember { mutableStateOf(emptyList<LogItem>()) }
}

其实remember必须在@Composable下使用,而mutableStateOf可以抽取到如Store,viewModel中,直接对mutableStateOf数据做修改,数据就可以变更了

列表中展示数据,对网络请求的数据刷新也是如此

@Composable
fun messageList(more: Boolean, onClick: () -> Unit) {

    val messages = remember { Store.logs }
    
    val state = rememberLazyListState()

    Box(Modifier.fillMaxSize()) {
        LazyColumn(Modifier.border(1.dp, color = Color.Gray).fillMaxSize().padding(10.dp), state) {
            items(messages.value.size) { x ->
                Text(messages.value[x].getMessage(), fontSize = 12.sp, color = messages.value[x].getColor())
                Spacer(modifier = Modifier.height(5.dp))
            }
        }
        VerticalScrollbar(
            modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight(),
            adapter = rememberScrollbarAdapter(scrollState = state)
        )
    }
}

通过对Store.logs添加数据

inline fun pushLogToUI(noinline log: () -> LogItem) {
    val logItem = log.invoke()
    Store.logs.value += listOf(logItem) 
}

image.png

仓库地址: github.com/luxiao0314/…