jetpackAndroid 知识JetpackPack 知识

Jetpack (七)Android Compose 如何使用<

2021-05-25  本文已影响0人  zcwfeng

要启动新的Compose项目,请打开Android Studio Arctic Fox,然后选择启动新的Android Studio项目,如下所示:

如果没有出现以上屏幕,请转至 File > New > New Project. 我们老版本的创建项目模式

API level 21 是最低的要求

打开根目录 build.gradle

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修改器。

比如,把按钮放在底部,其他内容在屏幕顶部

  1. 用weight 将灵活的items包裹在另一column中。由于此column是灵活的,其余内容都不灵活,因此将占用尽可能多的空间。确定外部Column的大小后,它将能够使用所有剩余的高度。
  2. 将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函数来覆盖应用程序中使用的版式和形状。

我们用到的微件元素

注意点:

请注意,MainActivity.kt中的可组合函数是在MainActivity类之外的,它们被声明为顶级函数。 您在Activity之外拥有的代码越多,您可以共享和重用的代码就越多。

上一篇下一篇

猜你喜欢

热点阅读