Jetpack (七)Android Compose 如何使用<
要启动新的Compose项目,请打开Android Studio Arctic Fox,然后选择启动新的Android Studio项目,如下所示:
如果没有出现以上屏幕,请转至 File > New > New Project. 我们老版本的创建项目模式
API level 21 是最低的要求
打开根目录 build.gradle
-
compose_version
to the [latest version]1.0.0-beta07
: - Add the
kotlin_version
buildscript {
ext {
compose_version = '1.0.0-beta07'
kotlin_version = '1.4.32'
}
...
}
会帮助你自动生成代码
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicsCodelabTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
Greeting("Android")
}
}
}
}
}
@Composable
fun Greeting(name: String) {
Text(text = "Hello $name!")
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
BasicsCodelabTheme {
Greeting("Android")
}
}
setContent中使用的应用程序主题取决于项目的命名方式。 该代码实验假定该项目名为BasicsCodelab。 如果从代码实验复制粘贴代码,请不要忘记使用ui / Theme.kt文件中可用的主题名称来更新BasicsCodelabTheme。
请注意,MainActivity.kt中的可组合函数是在MainActivity类之外的,它们被声明为顶级函数。 您在活动之外拥有的代码越多,您可以共享和重用的代码就越多。
首先,重构代码以使其更可重用,并创建一个新的@Composable MyApp函数,该函数包含特定于此Activity的Compose UI逻辑。
其次,将应用程序的背景色放置在可重用的Greeting Composable中是没有意义的。 该配置应应用于此屏幕上的每个用户界面,因此将Surface从Greeting移至新的MyApp函数:
class MainActivityDemo : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp()
}
}
}
@Composable
fun Greeting1(name: String) {
Surface(color = Color.Yellow) {
Text(text = "Hello $name!",modifier = Modifier.padding(24.dp))
}
}
@Composable
fun MyApp(){
MyDemoTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
Greeting1("Android2")
}
}
}
@Preview
@Composable
fun DefaultPreview1() {
MyApp()
}
使容器起作用
如果要创建一个包含应用程序所有常用配置的容器怎么办?
要创建通用容器,请创建一个Composable函数,该函数将返回Unit的Composable函数(此处称为content)作为参数。 您之所以返回Unit是因为,您可能已经注意到,可组合函数不返回UI组件,而是发出它们。 这就是为什么他们必须返回Unit的原因:
@Composable
fun MyApp(content: @Composable () -> Unit) {
BasicsCodelabTheme {
Surface(color = Color.Yellow) {
content()
}
}
}
注意:使用Composable函数作为参数时,请注意@Composable()中的多余括号。 由于注释是在函数上应用的,因此需要它们!
fun MyApp(content: @Composable () -> Unit) { ... }
在函数内部,定义要容器提供的所有共享配置,然后调用传递的子代Composable。 在这种情况下,您要应用MaterialTheme和黄色,然后调用content()
class MainActivityDemo : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp{
Greeting1(name = "Android3")
}
}
}
}
@Composable
fun Greeting1(name: String) {
Text(text = "Hello $name!",modifier = Modifier.padding(24.dp))
}
@Composable
fun MyApp(content: @Composable ()-> Unit){
MyDemoTheme {
Surface(color = Color.Yellow) {
content()
}
}
}
@Preview("Text preview")
@Composable
fun DefaultPreview1() {
MyApp{
Greeting1("Android3")
}
}
代码等效,但现在更加灵活。 使容器可组合的功能是一种良好的做法,可以提高可读性并鼓励重用代码。把Theme交给MyApp,内容传入
使用布局多次调用Composable函数
@Composable
fun Greeting1(name: String) {
Text(text = "Hello $name!",modifier = Modifier.padding(24.dp))
}
@Composable
fun MyApp(content: @Composable ()-> Unit){
MyDemoTheme {
Surface(color = Color.Yellow) {
content()
}
}
}
@Composable
fun MyScreenContent() {
Column {
Greeting1("Android")
Divider(color = Color.Black)
Greeting1("there")
}
}
@Preview("Text preview")
@Composable
fun DefaultPreview1() {
MyApp{
MyScreenContent()
}
}
注意:调用Composable函数时,它会将元素添加到Compose UI层次结构中。 您可以从代码的多个部分调用相同的函数(具有可能不同的参数)以添加新元素。 您可以认为这就像通过调用composable函数发出UI元素一样。
Compose 和 Kotlin
可以像Kotlin中的任何其他函数一样调用compose函数。 由于可以添加语句来影响UI的显示方式,因此构建UI非常强大。
例如 可以使用 for循环添加元素到 MyScreenContent Column:
@Composable
fun MyScreenContent(names: List<String> = listOf("Android", "there")) {
Column {
for (name in names) {
Greeting(name = name)
Divider(color = Color.Black)
}
}
}
Compose中的State 状态值
对状态更改做出反应是Compose的核心。 通过调用可组合函数,组合应用可将数据转换为UI。 如果您的数据发生更改,则可以使用新数据调用这些功能,从而创建更新的UI。 Compose提供了用于观察应用程序数据中的更改的工具,这些工具将自动调用您的功能-这称为重组recompose
。 Compose还查看单个可组合物需要哪些数据,以便它仅需要重新组合其数据已更改的组件,并可以跳过不受影响的组件。
在幕后,Compose使用了自定义的Kotlin编译器插件,因此,当基础数据发生更改时,可重新调用可组合函数以更新UI层次结构。
例如,当您在MyScreenContent Composable函数中调用Greeting(“ Android”)时,正在对输入(“ Android123...”)进行硬编码,因此Greeting将被添加到UI树一次,即使更改, MyScreenContent的正文将重新组成。
PS: 就是实时更新
要向可组合对象添加内部状态,请使用mutableStateOf函数,该函数提供可组合的可变内存。 要使每个重组都有不同的状态,请使用remember
记住可变状态。 并且,如果在屏幕上的不同位置有可组合对象的多个实例,则每个副本将获得其自己的状态版本。 可以将内部状态视为类中的私有变量
。
@Composable
fun Counter(){
val count = remember{mutableStateOf(0)}
Button(onClick = {count.value++}){
Text("click ${count.value} times",
modifier = Modifier.background(Color.Red))
}
// TextButton(onClick ={count.value++}) {
// Text("click ${count.value} times",
// modifier = Modifier.background(Color.White))
// }
// OutlinedButton(onClick ={count.value++}) {
// Text("click ${count.value} times",
// modifier = Modifier.background(Color.Green))
//
// }
}
注意:Compose根据“Material Design Button”规范提供了不同类型的按钮,即Button,OutlinedButton和TextButton。 在您的情况下,使用一个具有文本的按钮作为按钮内容,以显示单击了多少次。
真实的业务原理......
在可组合函数中,应该公开对调用函数有用的状态,因为这是可以使用或控制的唯一方法,该过程称为状态提升。
状态提升是通过调用它的函数使内部状态可控的方法。 为此,您可以通过可控制的可组合函数的参数公开状态,并从可控制的可组合函数外部实例化该状态。
使状态可悬挂可避免重复状态和引入错误,有助于重用可组合对象,并使可组合对象实质上更易于测试。 对于可组合调用者不感兴趣的状态应该是内部的。
在某些情况下,使用者可能不关心某个状态(例如,在滚动条中,scrollerPosition状态处于暴露状态,而maxPosition则不在此状态)。 这个属于创造和控制该状态的人。
在该示例中,由于Counter的使用者可能对状态感兴趣,因此可以通过引入(count,updateCount)对作为Counter的参数,将状态完全推迟给调用者。 这样,Counter提升了其状态:
@Composable
fun MyScreenContent(names: List<String> = listOf("Android3", "there2")) {
val countState = remember { mutableStateOf(0) }
Column {
for (name in names) {
Greeting1(name = name)
Divider(color = Color.Black)
}
Divider(color = Color.Black, thickness = 32.dp)
Counter(count = countState.value,
updateCount = { newCount -> countState.value = newCount })
}
}
@Composable
fun Counter(count: Int, updateCount: (Int) -> Unit) {
Button(onClick = { updateCount(count + 1) }) {
Text("click $count times", modifier = Modifier.background(Color.Red))
}
}
灵活的布局( Flexible layouts)
接触了Column 布局用于以垂直顺序放置项目。 同样,可以使用Row水平放置项目。
Column和Row
将它们的项目一个接一个地放置。 如果要使某些项目具有弹性,以便它们以一定的比重占据屏幕,则可以使用weight
修改器。
比如,把按钮放在底部,其他内容在屏幕顶部
- 用weight 将灵活的items包裹在另一column中。由于此column是灵活的,其余内容都不灵活,因此将占用尽可能多的空间。确定外部Column的大小后,它将能够使用所有剩余的高度。
- 将Counter保留在默认情况下不灵活的外部列中。
@Composable
fun MyScreenContent(names: List<String> = listOf("Android", "there")) {
val countState = remember { mutableStateOf(0) }
Column(modifier = Modifier.fillMaxHeight()) {
Column (modifier = Modifier.weight(1f)){
for (name in names) {
Greeting1(name = name)
Divider(color = Color.Black)
}
}
Counter(count = countState.value,
updateCount = { newCount -> countState.value = newCount })
}
}
您可以在外部Column上使用fillMaxHeight()修饰符,以使其占据尽可能多的屏幕(fillMaxSize()和fillMaxWidth()修饰符也可用)。
作为在Compose中利用Kotlin的另一个示例,您可以根据用户使用if ... else语句敲击Button的次数来更改Button的背景颜色:
@Composable
fun Counter(count: Int, updateCount: (Int) -> Unit) {
Button(
onClick = { updateCount(count + 1) },
colors = ButtonDefaults.buttonColors(
backgroundColor = if (count > 3) Color.Green else Color.White
)
) {
Text("click $count times")
}
}
让列表更加真实。 到目前为止,已经在“列”中显示了两个项目,但它可以处理数千个项目吗? 在更改列表项之前,将“名称”列提取到专用的Composable:
@Composable
fun nameList(names:List<String>,modifier :Modifier = Modifier){
Column(modifier = modifier) {
for (name in names) {
Greeting1(name = name)
Divider(color = Color.Black)
}
}
}
更改MyScreenContent参数中的默认列表值以使用另一个列表构造函数,该构造函数允许设置列表大小并用其lambda中包含的值填充(此处$ it表示列表索引):
names: List<String> = List(1000) { "Hello Android #$it" }
无论是以交互方式呈现它还是将其部署在设备/仿真器上,您都将无法滚动浏览这数千行,因为默认情况下“列”是不可滚动的。
为了显示可滚动的列,我们使用LazyColumn。 LazyColumn仅渲染屏幕上的可见项,从而在渲染大列表时提高性能。 它等效于Android视图中的RecyclerView。
由于列表中包含成千上万的项目,因此渲染时会影响应用程序的流畅性,因此请使用LazyColumn仅在屏幕上渲染可见元素,而不是所有元素。
在其基本用法中,LazyColumn API在其作用域内提供了一个items元素,其中编写了单独的项目呈现逻辑:
@Composable
fun Greeting1(name: String) {
Text(text = "Hello $name!", modifier = Modifier.padding(24.dp))
}
@Composable
fun MyApp(content: @Composable () -> Unit) {
MyDemoTheme {
Surface(color = Color.Yellow) {
content()
}
}
}
@Composable
fun nameList(names: List<String>, modifier: Modifier = Modifier) {
LazyColumn(modifier = modifier) {
items(count = names.size) { index ->
Greeting1(name = names.get(index))
Divider(color = Color.Black)
}
}
}
@Composable
fun MyScreenContent(names: List<String> = List(1000) { "Hello Android #$it" }) {
val countState = remember { mutableStateOf(0) }
Column(modifier = Modifier.fillMaxHeight()) {
nameList(names = names, Modifier.weight(1f))
Counter(count = countState.value,
updateCount = { newCount -> countState.value = newCount })
}
}
@Composable
fun Counter(count: Int, updateCount: (Int) -> Unit) {
Button(
onClick = { updateCount(count + 1) },
colors = ButtonDefaults.buttonColors(
backgroundColor = if (count > 3) Color.Green else Color.White
)
) {
Text("click $count times")
}
}
@Preview("Text preview")
@Composable
fun DefaultPreview1() {
Column {
MyApp {
MyScreenContent()
}
}
}
动画列表
现在,让列表更具交互性。
假设您要在单击列表项后更改其背景色。 您已经使用按钮完成了此操作,但是这次,从一种背景色到另一种背景色的过渡将是动态的,而不是瞬间的,如下:
为此,您将使用
animateColorAsState API
,但首先需要更新Greeting Composable以添加isSelected状态(将其记住为false初始化)和单击处理程序以切换该状态
@Composable
fun Greeting1(name: String) {
// 记录选中状态
var isSelected by remember { mutableStateOf(false) }
val backgroundColor by animateColorAsState(if (isSelected) Color.Red else Color.Transparent)
Text(text = "Hello $name!", modifier = Modifier
.padding(24.dp)
.background(color=backgroundColor)
.clickable(onClick = {isSelected = !isSelected}))
}
注意:由于在问候语可组合物中悬挂了isSelected状态,因此NameList将不会跟踪其列表项是否被选中。 一旦项目滚动出屏幕,它们的状态将被设置为false。 此行为旨在作为此练习的目标,是保留一个简单列表。 为了跟踪列表中的选定项目,应将其isSelected状态提升到NameList级别。
App 主题
自定义App Theme 在其实现中使用MaterialTheme。 MaterialTheme是可组合的功能,反映了Material Design规范中的样式原则。 样式信息会向下层叠到其中的组件,这些组件可能会读取该信息以对其进行样式设置。 在原始的简单UI中,可以按以下方式使用:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyDemoTheme {
Greeting(name = "Android")
}
}
}
}
因为MyDemoTheme在内部包装了MaterialTheme,所以Greeting用主题中定义的属性设置样式。 您可以通过以下方式检索MaterialTheme的属性并使用它们来定义Text的样式:
@Composable
fun Greeting(name: String) {
Text (
text = "Hello $name!",
modifier = Modifier.padding(24.dp),
style = MaterialTheme.typography.h1
)
}
上面的示例中可组合的Text设置了三个参数,要显示的字符串,modifiers和TextStyle。 您可以创建自己的TextStyle,也可以使用MaterialTheme.typography检索主题定义的样式。 此结构使您可以访问Material定义的文本样式,例如h1,body1或subtitle1。 在您的示例中,使用了在主题中定义的h1样式。
注意:您可以使用复制功能来修改预定义的样式。
例如,style = MaterialTheme.typography.body1.copy(color= Color.Yellow)
创建你自己的app 主题
主题是可组合函数,它接受其他子级可组合函数。 为了使其可重用,可以像在“声明式UI”部分中那样创建一个容器Composable函数:
@Composable
fun BasicsCodelabTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
// TODO
}
MaterialTheme拥有颜色和版式的配置。 您只需在此时更改一些颜色即可实现所需的设计。
private val DarkColors = darkColors(
primary = purple200,
primaryVariant = purple700,
secondary = teal200
)
private val LightColors = lightColors(
primary = purple500,
primaryVariant = purple700,
secondary = teal200
)
@Composable
fun BasicsCodelabTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colors = if (darkTheme) {
DarkColors
} else {
LightColors
}
MaterialTheme(colors = colors) {
content()
}
}
您提供的自定义颜色将覆盖lightColors和darkColors方法中的自定义颜色,除非另行提供,否则它们将默认颜色为浅色和深色“材质”基线主题。 如您所见,这将传递给MaterialTheme的构造函数,该构造函数实现Material设计规范中的样式原则。
以相同的方式,您可以通过将它们传递给MaterialTheme函数来覆盖应用程序中使用的版式和形状。
我们用到的微件元素
-
Surface
-
Modifiers
-
Preview
-
@Composable
@Composable批注仅对于发出UI或调用其他可组合函数的函数是必需的。 他们可以调用常规函数和其他可组合函数。 如果函数不满足这些要求,则不应使用@Composable对其进行注释。 -
Divider
-
remember
-
mutableStateOf
-
Text
-
Button
-
Column 布局
-
LazyColumn ----相当RecyclerView
-
MaterialTheme
注意点:
请注意,MainActivity.kt中的可组合函数是在MainActivity类之外的,它们被声明为顶级函数。 您在Activity之外拥有的代码越多,您可以共享和重用的代码就越多。