从0开始学Compose

371 阅读2分钟

Compose 编程思想

总结

  1. navigation+ViewModel+hilt
  2. 所有和耗时相关的操作放到ViewModel里
  3. 在@Composable的方法里调用ViewModel的方法来更新mutableStateOf相关的持久化数据的字段,此字段在回传给@Composable进行UI的更新
  4. box组件相当于FramLayout, 可以设置叠加效果和圆角等属性
  5. 使用modifier的lambda方法一些列方法,它会跳过重组并在组合阶段之外执行动画

这会跳过重组并在组合阶段之外执行动画

声明性编程

  • Compose是使用声明式来更新UI而不是命令式
  • 就是数据和UI的单项数据流,数据改变UI自动改变。 命令式编程很容易数据改变,忘记对某处UI的修改,或是多个地方同时修改UI而造成异常的状态
  • Compose会智能的选择只对数据改变的地方进行UI的重新绘制,而不是刷新整个界面

简单的组合函数

  • 使用Compose来写UI就是定义各种带有@Composable的方法(方法名应大写好区分),他不需要返回值,可以传递各种数据参数和点击的回调
  • 定义这些参数应该保持幂等性,就是传入参数相关得到的结果是一致,从而保证高效、安全的更新界面。
  • 这个函数不应该修改全局变量、修改任何共享资源
  • 将UI界面纯粹描述一个函数的结果,不依赖于外部状态或修改外部的状态

动态生成内容

  • 因为你使用的是kotlin来编写UI,所以你可以动态写逻辑判断是否显示、如何显示 拥有了kotlin底层语言逻辑的全部灵活性
@Composable
fun Greeting(names: List<String>) {
    for (name in names) {
        Text("Hello $name")
    }
}

重组

  • 在命令式编程中。当需要更新UI,需要手动的找View重新设置,而Compose中只要数据发生改变他会自动调用方法进行更新
每次点击button,会执行count++ 都重新调用ClickCounter方法并会打印println log ClickCounter:
var count by remember { mutableStateOf(0) }

ClickCounter(count) {
    count++
}

@Composable
fun ClickCounter(clicks: Int, onClick: () -> Unit) {
    println("ClickCounter: $clicks")
    Button(onClick = onClick) {
        Text("I've been clicked $clicks times")
    }
}

  • 所以这些函数可能会很频繁执行;不按顺序的执行;或是并发执行
  • 所以下面的代码 你不能通过StartScreen()改变全局变量,然后在MiddleScreen()去获取改变变量的值,因为他们的执行顺序不一定保证一致,Compose来控制优先绘制哪些组件,应该让每个函数都保存独立。

@Composable
fun ButtonRow() {
    MyFancyNavigation {
        StartScreen() 
        MiddleScreen()
        EndScreen()
    }
}
  • 所有的附带效应的状态,应该通过onClick在UI线程来触发
  • 重组操作是永远保持最新的状态,如果连续2次重组进行界面更新,他可能会只保留最新的一次,所以如果你的函数带附带效果,可能会只执行了最后一次,导致非预期的结果。应保持函数独立。

状态管理

  • 如果想在配置更改(语言、屏幕旋转)依然可以保留数据,使用rememberSaveable来持久化数据 var name by rememberSaveable { mutableStateOf("") }
虽然是在方法里创建了ViewModel,但他是复用的。当前界面任何一个方法创建都是同一个对象。
@Composable
fun ClickCounter(clicks: Int, onClick: () -> Unit) {
    println("ClickCounter: $clicks")

    val vm = viewModel(MainViewModel::class.java)

    Button(onClick = onClick) {
        Text("I've been ${vm.hashCode()} clicked $clicks times")
    }
}

有状态与无状态

  • 这个 HelloContent是有状态的方法,他内部会自动修改Name这个持久化数据字段,调用方不需要处理状态的管理。但这个方法不便于复用
@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.bodyMedium
            )
        }
        OutlinedTextField(
            value = name,
            onValueChange = { name = it },
            label = { Text("Name") }
        )
    }
}
  • 通过把方法可变和不可变部分分离出来。把状态数据部分抽取出来让调用方来处理,把方法变为无状态形式,这样提高可复用性。
@Composable
fun HelloScreen() {
    var name by rememberSaveable { mutableStateOf("") }

    HelloContent(name = name, onNameChange = { name = it })
}

@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Hello, $name",
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.bodyMedium
        )
        OutlinedTextField(value = name, onValueChange = onNameChange, label = { Text("Name") })
    }
}

动画