欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android Jetpack Compose总结

程序员文章站 2022-03-04 21:09:46
...

简介

Jetpack Compose 是用于构建native UI的新方式,写法跟Flutter非常相似,对Flutter有了解的同学可以很快上手。
官网:https://developer.android.com/jetpack/compose
官方demo :https://github.com/android/compose-samples
官方的介绍: https://developer.android.com/jetpack/compose/setup

环境及版本

最低支持Android API 21,即5.0版本,必须使用kotlin语言,最低使用Android Studio 4.0 版本。
Jetpack Compose 目前处于实验阶段,现在是0.1-dev2,到1.0正式版估计还要一年时间。
后续版本可能会加入更多kotlin的特性,丰富动画等其他性能问题。

关于如何在现有项目中使用:
https://developer.android.com/jetpack/compose/setup#add-compose

怎么使用?

在AS 4.0中直接新建一个空的Compose项目,会有一个示例代码:
Android Jetpack Compose总结
在函数前加@Compose注解,就可以返回一个类似Flutter中的Widget的UI
@Compose注解的函数可以相互调用,这些函数会被插件编译处理,所以如果一个函数不是生成UI的,那么不要用此注解。
@Preview注解,可以在右边实时预览,改动函数后,刷新一个预览即可,添加该注解的外层函数不能有参数,但是里面可以嵌套一个带参数的函数来预览。可以在@Preview后面添加一个名字,如:@Preview("Text preview")

关于Column 和Row 的概念跟Flutter中一样,包括主轴和副轴的概念大小如mainAxisSize和对齐方式如crossAxisAlignment,一段代码示例:

@Composable
fun MyScreenContent(
    names: List<String> = listOf("Android", "there"),
    counterState: CounterState = CounterState()
) {
    Column(crossAxisAlignment = CrossAxisAlignment.Center
      crossAxisSize = LayoutSize.Expand,
        mainAxisSize = LayoutSize.Expand) {
        for (name in names) {
            Greeting(name = name)
            Divider(color = Color.Black)
        }
        Divider(color = Color.Transparent, height = 32.dp)
        Counter(counterState)
    }
}

@Preview("MyScreen preview")
@Composable
fun DefaultPreview() {
    MyApp {
        MyScreenContent()
    }
}

可以使用HeightSpacer(24.dp)或者WeightSpacer(24.dp)来直接添加一个宽高间隔

按照官方的建议,我们可以把UI拆分成多个小的Compose函数,每个函数其实最终会被插件编译生成一个View,然后可以复用这些Compose函数,

@Composable
fun MyScreenContent(
    names: List<String> = listOf("Android", "there"),
    counterState: CounterState = CounterState()
) {
    Column(modifier = ExpandedHeight, crossAxisAlignment = CrossAxisAlignment.Center) {
        Column(modifier = Flexible(1f), crossAxisAlignment = CrossAxisAlignment.Center) {
            for (name in names) {
                Greeting(name = name)
                Divider(color = Color.Black)
            }
        }
        Counter(counterState)
    }
}

在Column中,可以对参数modifier设置ExpandedHeight,类似于设置高度match_parent的意思,宽度同理。

关于如何使用Theme和自定义Theme

MaterialTheme中有很多颜色和字体样式,在外层包裹上MaterialTheme后,可以在内部的Compose函数中使用主题数据,如:style = +themeTextStyle { h1 }

@Composable
fun Greeting(name: String) {
    Text (
        text = "Hello $name!",
        modifier = Spacing(24.dp),
        style = +themeTextStyle { h1 }
        color = +themeColor { surface }
    )
}

通过使用copy函数可以在现有的一个主题上修改某一个属性值,如:

textStyle = (+themeTextStyle { body1 }).copy(color = Color.Yellow)

自定义Theme

import androidx.compose.Composable

@Composable
fun CustomTheme(children: @Composable() () -> Unit) {
    // TODO 
}
import androidx.compose.Composable
import androidx.ui.core.CurrentTextStyleProvider
import androidx.ui.graphics.Color
import androidx.ui.material.MaterialColors
import androidx.ui.material.MaterialTheme
import androidx.ui.text.TextStyle

val green = Color(0xFF1EB980.toInt())
val grey = Color(0xFF26282F.toInt())
private val themeColors = MaterialColors(
    primary = green,
    surface = grey,
    onSurface = Color.White
)

@Composable
fun CustomTheme(children: @Composable() () -> Unit) {
    MaterialTheme(colors = themeColors) {
        val textStyle = TextStyle(color = Color.Red)
        CurrentTextStyleProvider(value = textStyle) {
            children()
        }
    }
}

Effects和memo

memo的作用:

1. 在recompositions(即该UI组件内部的Model数据变化时,该UI组件就会重新构建)的时候保存状态值,如下代码:

@Composable
fun MyScreenContent(
    names: List<String> = listOf("Android", "there"),
    counterState: CounterState = CounterState()
) { ... }

上面的代码有一个问题,再重新构建的时候,原来的counterState数值就会丢失,每次都是一个新的counterState对象。
按照下面使用memo修改后,就可以解决问题:

@Composable
fun MyScreenContent(
    names: List<String> = listOf("Android", "there"),
    counterState: CounterState = +memo { CounterState() }
) { ... }

2. 在重组时,记住内部的一些计算结果,防止多次重复计算

如果在合成的中间需要进行计算,而又不想在每次重新组合函数时都进行计算,则可以记住该计算,即使重新组合了Composable函数,该计算也不会再次执行。

@Composable
fun Greeting(name: String) {

    val formattedName = +memo { name.substringBefore(" ").toUpperCase() }

    Text (
        text = "Hello $formattedName!",
        modifier = Spacing(24.dp),
        style = +themeTextStyle { h3 }
    )
}

@Preview
@Composable
fun DefaultPreview() {
    MaterialTheme {
        Greeting("Android 10")
    }
}

比如这里的formattedName计算过程,在使用memo后,就不会重复计算,但是这样写有个bug,如果第二次调用时传入来了另外一个参数,那么由于memo复用原来的结果,就会导致bug,所以,对于需要修改的参数,可以以如下的方式来使用memo:

@Composable
fun Greeting(name: String) {

    val formattedName = +memo(name) { name.substringBefore(" ").toUpperCase() }

    Text (
        text = "Hello $formattedName!",
        modifier = Spacing(24.dp),
        style = +themeTextStyle { h3 }
    )
}

@Model注解

model注解标记一个数据类之后,在Compose函数中可以直接监听到数据变化,自动更新显示,
如:
定义:

@Model 
class CounterState(var count: Int = 0)

使用:

@Composable 
fun Counter(state: CounterState) {
    Button(
        text = "I've been clicked ${state.count} times",
        onClick = {
            state.count++
        }
    )
}

状态提升、 数据流向下传递、事件流向上传递

@Model
class FormState(var optionChecked: Boolean)

@Composable
fun Form(formState: FormState) {
    Checkbox(
        checked = formState.optionChecked,
        onCheckedChange = { newState -> formState.optionChecked = newState })
}

在上面代码中,Checkbox的选中状态,在Checkbox和Form中都不保存,而改为由外部传入,原因是此时外部可能需要使用当前的状态值,那么由外部来创建并传递该参数到Compose函数中,这使得外部调用者提升了状态

⚠️注意:在可组合函数中,应该公开可能对调用函数有用的状态,因为这是可以使用或控制的唯一方法,称为状态提升。

状态提升的概念跟Flutter一样,后续应该也会像Flutter中的Provider、BLOC、或者Redux一样,推出相关的状态管理库,因为Compose + Model注解的方式,就是一种MVVM的思想,需要一种方便的数据状态管理的三方库来做这个事情。

关于数据流向: 父Composable函数可以控制其子数据。 子Compose UI不应从全局变量或全局数据存储中读取。Composable函数应仅接收所需信息,因此它们应尽可能简单,而不是调用父Composable函数可以提供的所有内容。

@Composable
fun MultipleGreetings(user: User = +memo { User("name", "surname") }) {
    Column {
        Greeting("${user.firstName} ${user.lastName}")
        Greeting("Android 10")
        Button(text = "Change name", onClick = {
            user.firstName = "Changed"
            user.lastName = "New surname"
        })
    }
}

@Composable
fun Greeting(name: String) {

    val formattedName = +memo(name) { name.substringBefore(" ").toUpperCase() }

    Text (
        text = "Hello $formattedName!",
        modifier = Spacing(24.dp),
        style = +themeTextStyle { h3 }
    )
}

比如上面代码中,Greeting从调用方Compose函数(MultipleGreetings)获取数据,作为参数传入,且Greeting只接收一个String,并不是整个User对象。

事件向上传递

事件通过lambda回调而不断往上。 当子Composable函数收到事件时,更改应传播回至关心该信息的Composable。

在我们的示例中,我们可以通过将Greeting的内容包装在以onClick侦听器为参数的Clickable函数(可在库中使用)中来使其可点击。 但是,Greeting是一个可重用的功能,它本身并不知道如何处理用户交互。 应该使用lambda将该信息从层次结构的底部(Greeting中的Clickable composable)传播到顶部的Composable函数,这些函数知道如何处理该信息,如以下示例所示:

@Composable
fun MultipleGreetings(user: User = +memo { User("name", "surname") }) {

    val onClick = {
        user.firstName = "Changed"
    }

    Column {
        Greeting("${user.firstName} ${user.lastName}", onClick)
        Greeting("Android 10", onClick)
        Button(text = "Change name", onClick = onClick)
    }
}

@Composable
fun Greeting(name: String, onClick: () -> Unit) {

    val formattedName = +memo(name) { name.substringBefore(" ").toUpperCase() }

    Clickable(onClick = onClick) {
        Text (
            text = "Hello $formattedName!",
            modifier = Spacing(24.dp),
            style = +themeTextStyle { h3 }
        )
    }
}

Greeting通过调用父级作为参数传递的lambda告诉MultipleGreetings它被单击了。 如果您运行该应用程序,则可以看到在任何问候语文本上进行点击都会传播更改,并且顶部的Greeting实例将重新组合。

Android Jetpack Compose总结
Android Jetpack Compose总结
Data flow in Compose apps. Data flows down with parameters, events flow up with lambdas.


Compose和现有的View互操作

Compose写的函数可以用在xml中,Android现有的View也可以用Compose的方式来写,如:
Android Jetpack Compose总结
Android Jetpack Compose总结
总结: Compose借鉴了Flutter和Swift UI的编写方式,代码简洁,可以实时预览效果,截止到2019年11月19日,目前版本才为0.1,预计正式发布1.0后,会有更多功能更新,日常的一个小demo可以先使用Compose熟悉起来。

参考:
https://codelabs.developers.google.com/codelabs/jetpack-compose-basics/#0

牛逼!Android Jetpack Compose UI组件库最新进展,写法完全类似Flutter

Android Studio 4.0 最新进展,这几个新功能可太牛逼了!

相关标签: Jetpack Compose