Jetpack Compose(第二趴)——Compose 基础知识(上)

一、准备工作

JetPack Compose是一款新型工具包,旨在帮助简化界面开发。该工具包将响应式编程模型与简洁易用的Kotlin编程语言相结合,并采用完全声明式的代码编写方式,让您可以通过调用一些列函数来描述界面,这些函数会将数据转换为界面层次结构。当底层数据发生变化时,框架会自动重新执行这些函数,为您更新界面结构。

Compose应用由可组合函数构成。可组合函数即带有@Composable标记的常规函数,这些函数可以调用其他可组合函数。使用一个函数就可以创建一个新的界面组件。该注解会告知Compose为函数添加特殊支持,以便后续更新和维护界面。借助Compose,您可以将代码设计成多个小代码块。可组合函数通常简称为“可组合项”。

通过创建可重用的小型可组合项,您可以轻松构建应用中的所用界面元素的库。每个可组合项对应屏幕的一部分,可以单独修改。

通过这篇博客你将学习:

  • 什么是Compose
  • 如何使用Compose构建界面
  • 如何在可组合函数中管理状态
  • 如何创建高效列表
  • 如何添加动画
  • 如何为应用设置样式和主题
    您将构建一个包含初始配置屏幕和一系列动画展开项的应用:

11.gif

2、启动新的Compose项目

如需启动新的Compose项目,请打开Android Studio,然后选择Start a new Android Studio project,如图所示:
image.png
如果系统未显示上述界面,请依次进入File>New>New Project

创建新项目时,请从可用模板中选择Empty Compose Activity(Material3)

image.png
点击Next,然后照常配置项目,并将其命名为Basics Codelab。 请确保您选择minmumSdkVersion至少API级别21,这是Compose支持的最低API级别。

选择Empty Compose Activity(Material3) 模版后,系统会在项目中为您生成以下代码:

  • 该项目已配置为使用Compose
  • 已创建AndroidManifest.xml文件
  • build.gradleapp/build.gradle文件包含Compose所需的选项和依赖项。

同步项目后,打开MainActivity.kt并查看代码。

class MainActivity: ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstamceState)
        setContent {

            BasicsCodelabTheme {

                // A surface container using the 'background' color from the theme

                Surface(

                    modifier = Modifier.fillMaxSize(),

                    color = MaterialTheme.colorScheme.background

                ) {

                    Greeting("Android")

                }

            }



        }


    }


}




@Composable
fun Greeting(name: String) {
    Text(text = "Hello $name!")
}



@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
    BasicsCodelabTheme {
        Greeting("Android")
    }
}

3、Compose使用入门

了解Android Studio为您生成的与Compose相关的各种类和方法。

3.1、可组合函数

可组合函数是带有@Composable注解的常规函数。这类函数自身可以调用其他的@Composable函数。我们会展示如何为Greeting函数添加@Composable标记。此函数会生成一段显示给定输入String的界面层次结构。Text是由库提供的可组合函数。

@Composable







private fun Greeting(name: String) {


    Text(text = "Hello $name!")
}

注意:可组合函数是带有@Composable注解的Kotlin函数,如上述代码所示。

3.2、Android应用中的Compose

使用Compose时,Activity仍然是Android应用的入口点。在我们的项目中,用户打开应用时会启动MainActivity(如AndroidManifest.xml文件中所指定)。您可以使用setContent来定义布局,但不同于在传统View系统中使用XML文件,您将在该函数中调用可组合函数。

class MainActivity: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState) 
        setContent {

            BasicsCodelabTheme {

                // A surface container using the 'background' color from the theme

                Surface(

                    modifier = Modifier.fillMaxSize(),

                    color = MaterialTheme.colorScheme.background

                ) {

                    Greeting("Android")

                }

            }



        }


    }


}


BasicsCodelabTheme是为可组合函数设置样式的一种方式。如需查看文本在屏幕上的效果,您可以在模拟器或设备上运行应用,或使用Android Studio预览进行查看。

若要使用Android Studio预览,您只需使用@Preview注解标记所有五参数可组合函数或采用默认形参的函数,然后构建那您的项目即可。现在MainActivity.kt文件中已经包含了一个Preview Composable函数。您可以在同一个文件中包含多个预览,并为它们指定名称:

@Preview(showBackground = true, name = "Text preview")
@Composable



fun DefaultPreview() {

    BasicsCodelabTheme {



        Greeting(name = "Android")
    }



}



image.png

4、微调界面

首先,为Greeting设置不同的颜色背景。为此,您可以用Surface包围Text可组合项。Surface会采用一种颜色,因此请使用MaterialTheme.colorScheme.primary

注意:`Surface`和`MaterialTheme`相关的概念。Material Design是Google提供的一个设计系统,旨在帮助您构建界面和体验。
@Composable







private fun Greeting(name: String) {


    Surface(color = MaterialTheme.colorScheme.primary) {
        Text (text = "Hello $name!")
    }
}

将上述代码添加到项目后,您会在Android Studio的右上角看到Build & Refresh按钮。点按该按钮或构建项目即可在预览中查看新更改

image.png

您可以在预览中查看新更改:

image.png

您可能忽略了一个重要的细节:为现在是白色的。 我们是如何对此进行定义的?

我们并没有对此进行定义!Material组件(例如 androidx.compose.material3.Surface)旨在提供应用中可能需要的额常见功能(例如为文本选择适当的颜色),让您获得更好的体验。我们之所以说Material很实用,是因为它提供在大多数应用中都会用到在使用默认值和模式。Compose中的Material组件是在其他基础组件(位于androidx.compose.foundation中)的基础上构建的。如果您需要更高的灵活性,也可以从您的应用组件中访问这些组件。

在这种情况下,Surface会了解,当该背景设为primary颜色后,其上的任何文本都应使用onPrimary颜色,此颜色也在主题中进行了定义。

4.1、修饰符

大多数Compose界面元素(例如SurfaceText都接受可选的modifier参数。修饰会指示界面元素如何在其父布局中放置、显示或表现)。

例如,padding修饰符会在其修饰的元素周围应用一定的空间。您可以使用Modifier.padding()创建内边距修饰符。

现在,为屏幕上的Text添加内边框。

import androidx.compose.foundation.layout.padding
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
...


@Composable

private fun Greeting(name: String) {
    Surface(color: MaterialTheme.colorScheme.primary) {
        Text(text = "Hello $name!", modifier = Modifier.padding(24.dp))
    }


}


点击Build&Refresh即可查看新更改

image.png

5、重复使用可组合项

您添加到界面的组件越多,创建的嵌套层级就越多。如果函数变得非常大,可能会影响可读性。通过创建可重用的小型组件,可以轻松构建应用中所用界面元素的库。每个组件对应于屏幕的一个部分,可以单独修改。

最佳实践是,您的函数应包含一个修饰符参数,系统默认为该参数分配空修饰符。将此修饰符转发到您在函数内联调用的第一个可组合项。这样,调用点就可以在组合函数之外调整布局指令和行为了。

创建一个名为MyApp的可组合项,该组合项中包含问候语.

@Composable







private fun MyApp(modifier: Modifier = Modifier) {
    Surface(
        modifier = modifier,
        color = MaterialTheme.colorScheme.background
    ) {
        Greeting("Android")
    }
}

这样一来,由于现在可以重复使用MyApp可组合项,您就可以省去onCreate回调和预览,从而避免重复编写代码。您的MainActivity.kt文件应如下所示:

package com.example.basicscodelab

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.io.unit.dp
import com.example.basocscodelab.ui.theme.BasicsCodelabTheme

class MainActivity: ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            BasicsCodelabTheme {
                MyApp(modifier = Modifier.fillMaxSize())
            }
        }
    }
}

@Composable

private fun MyApp(modifier: Modifier = Modifier) {
    Surface(
        modifier = modifier,
        color = MaterialTheme.colorScheme.backgrounf
    ) {
        Greeting("Android")
    }
}

@Composable
private fun Greeting(name: String) {
    Sueface(color = MaterialTheme.colorScheme.primary
    ) {
        Text(text = "Hello $name!", modifier = Modifier.padding(24.dp))
    }
}

@Previer(showBackground = true)
@Composable
private fun DefaultPreview() {
    BasicsCodelabTheme {
        MyApp()
    }
}

6、创建列和行

Compose中的三个基本标准布局元素是ColumnRowBox可组合项。

image.png
它们是接受可组合内容的可组合函数,因此您可以在其中放置项目。例如,Column中的每个子级都将垂直放置。

// Don't copy over
Column {
    Text("First row")
    Text("Second row")
}



现在尝试更改Greeting,使其显示包含两个文本元素的列,如以下示例中所示:

image.png

请注意,您可能需要移动周围的内边距。

将您的结果与此解决方案进行比较:

import androidx.compose.foundation.layout.Column
...


@Composable
private dun Greeting(name: String) {
    Surface(color = MaterialTheme.colorScheme.primary) {
        Column(modifier = Modifier.padding(24.dp)) {
            Text(text = "Hello,")
            Text(text = name)
        }


    }


}


6.1、Compose和Kotlin

可组合函数可以像Kotlin中的其他函数一样使用。这会使界面构建变得非常有效,因为您可以添加语句来影响界面的显示方式。
例如,您可以使用for循环向Column中添加元素:

@Composable







fun MyApp(
    modifier: Modifier = Modifier,
    name: List<String> = listOf("World", "Compose")
) {

    Column(modifier) {
        for (name in names) {
            Greeting(name = name)
        }

    }


}


image.png

您尚未设置可组合项的尺寸,也未对可组合项的大小添加任何限制,因此每一行仅占用可能得最小空间,预览时的效果也是如此。让我们更改预览效果,以模拟器小屏幕手机的常见宽度320dp。按如下所示向@Preview注解添加widthDp参数:

@Preview(showBackground = true, widthDp = 320)
@Composable



fun DefaultPreview() {

    BasicsCodelabTheme {



        MyApp()
    }



}



image.png

修饰符在Compose中使用非常广泛,现在我们来练习更高级的用法:尝试使用fillMaxWidthpadding修饰符复制以下布局。

image.png

现在,将您的代码与解决方案进行比较:

@Composable



fun MyApp(
    modifier: Modifier = Modifier,
    names: List<String> = listOf("World", "Compose")
) {

    Column(modifier = modifier.padding(vertical = 4.dp)) {
        for (name in names) {
            Greeting(name = name)
        }

    }


}



@Composable
private fun Greeting(name: String) {
    Surface(
        color = MaterialTheme.colorScheme.primary,
        modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
    ) {
        Column(modifier = Modifier.fillMaxWidth().padding(24.dp)) {
            Text(text = "Hello,")
            Text(text = name)
        }
    }

}

请注意:

  • 修饰符可以包含重载,因而具有相应的优势,例如您可以指定不同的方式来创建内边距。
  • 若要向一个元素添加多个修饰符,您只需要将它们链接起来即可。

有多种方式可以实现此结果,因此,如果您的代码榆次代码不同,并不表示您的代码就是错的。

6.2、添加按钮

接下来,您将添加一个用于展开Greeting的可点击元素,因此需要先添加对应的按钮。您的目标是要创建以下布局:

image.png

Button是material3软件包提供的一种可组合项,它采用可组合项作为最后一个参数。由于尾随lambda可以移到括号之外,因此您可以向按钮添加任何内容作为子级,例如Text:

Button(
    onClick = { }
) {

    Text("Show less")
}



注意:Compose根据Material Design按钮规范提供了不同类型的Button:ButtonElevatedButtonFilledTonalButtonOutlinedButtonTextButton

为了实现这一点,您需要学习如何在尾行放置可组合项。由于没有alignEnd修饰符,因此您需要在开始时为该组合项赋予一定的weightweight修饰符会让元素填满所有可用空间,使其“具有弹性”也就是会推开起他们有权重的元素。该修饰符还会使fillMaxWidth修饰符变得多余。

下面列出了对应的解决方案代码:

import androidx.compose.foundation.layout.Row
import andtoidx.compose.material3.ElevatedButton
...


@Composable
private fun Greeting(name: String) {
    Surface(
        color = MaterialTheme.colorScheme.primary,
        modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp
    ) {
        Row(modifier = Modifier.padding(24.dp)) {
            Column(modifier = Modifier.weight(1f)) {
                Text(text = "Hello,")
                Text(text = name)
            }
            ElevatedButton(
                onClick = { /* TODO */}
            ) {
                Text("Show more")
            }
        }
    }
}

7、Compose中的状态

在本部分中,您将向屏幕中添加一些互动。到目前为止,您已经创建了一些静态布局,但现在要让她们相应用户更改,以达到下面的效果。

22.gif
在开始了解如何使用按钮可点击以及如何调整内容大小之前,您需要再某个位置存储某个值,用于指示每项内容是否展开(即内容的状态)。由于我们需要为每条问候语设定这两个值之一,因此其逻辑位置位于Greeting可组合项中。我们来看看此expanded布尔值及其代码中的使用方式:

@Composable



private fun Greeting(name: String) {

    var expanded = false // Don't do this!
    



    Surface(

        color = MaterialTheme.colorScheme.primary,

        modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
    ) {

        Row(modifier = Modifier.padding(24.dp)) {

            Column(modifier = Modifier.weight(1f)) {

                Text(text = "Hello,")

                Text(text = name)

            }



            ElevatedButton(

                onClick = { expanded = !expanded}
            ) {

                Text(if (expanded) "Show less" else "Show more")
            }

        }

    }

}


请注意,我们还添加了onClick操作和动态按钮文本。

但是,此设置无法按照预期发挥作用。为expanded变量设置不同的值不会使Compose将其检测为状态更改,因此不会产生任何效果。

Compose应用通过调用可组合函数将数据转换为界面。如果您的数据发生变化,Compose会使用新数据重新执行这些函数,从而创建更新后的界面,此过程称为重组。Compose还会查看各个可组合项需要哪些数据,以便只需重组数据发生了变化的组件,而避免重组未影响的组件。
可组合函数可以按任意顺序频繁执行,因此您不能以代码的执行顺序或该函数的重组次数为判断依据。

更改此变量不会触发充足的原因是Compose并未跟踪此更改。此外,每次调用Greeting时,都会将该变量重置为false。

如需向可组合项添加内部状态,可以使用mutableStateOf函数,该函数可让Compose重组读取该State的函数。

State和MutableState是两个接口,它们具有特定的值,当该值发生变化时,它们就会触发界面更新(重组)。

import androidx.compose.runtime.mutableStateOf

...

@Composable
fun Greeting() {
    val expanded = mutableStateOf(false) // Don't do this!
}

但是,不能只是将mutableStateOf分配给可组合项中的某个变量。如前所述,重组可能会随时发生,这会再次调用可组合项,从而将状态重置为值为false的新可变状态。

如需在重组后保留状态,请使用remember记住可变状态。

import androidx.compose.runtime.mutableStateOf

import androidx.compose.runtime.remember
...

@Composable
fun Greeting() {
    val expanded = remember { mutableStateOF(false) }
    ...
}

remember可以起到保护作用,防止状态在重组时被重置。

请注意,如果从屏幕的不同部分调用同一可组合项,则会创建不同的界面元素,且每个元素都会拥有自己的状态版本。您可以将内部状态视为类中的私有变量

可组合函数会自动“订阅”状态。如果状态发生变化,读取这些字段的可组合项将会重组以显示更新。

7.1、更改状态和响应状态更改

您可能已经注意到,为了更改状态,Button具有一个名为onClick的形参,但它不接受值,而接受函数

您可能不熟悉以这种方式使用的函数,这其实就是一种Compose中广泛使用的非常强大的Kotlin功能。函数是Kotlin中的首要元素,您可以将它们分配给某个变量,传递给其他函数,甚至可以从它们自身返回函数。

您可以通过为”onClick”指定lambda表达式,定义点击时将执行的操作。例如,切换展开状态的值,并根据该值显示不同的文本。

ElevatedButton(
    onClick = { expanded.value = !expanded.value },
) {

    Text(if (expanded.value) "Show less" else "Show more")
}



如果在模拟器中运行应用,您会看到点击该按钮时,expanded会切换,从而触发重组该按钮的文本。每个Greeting都具有自己的展开状态,因为它们属于不同的界面元素。

33.gif
到目前为止的代码:

@Composable



private fun Greeting(name: String) {

    val expanded = remember { mutableStateOf(false) }
    



    Surface(

        color = MaterialTheme.colorScheme.primary,

        modifier = Modifier.padding(vertical = 4/dp, horizontal = 8.dp)
    ) {

        Row(modifier = Modifier.padding(24.dp)) {

            Column(modifier = Modifier.weight(1f)) {

                Text(text = "Hello,")

                Text(text = name)

            }



            ElevatedButton(

                onClick = { expanded.value = !expanded.value }
            ) {

                Text(if (expanded.value) "Show less" else "Show more")
            }

        }

    }

}


7.2、展开内容

现在,我们来根据请求实际展开内容。添加一个依赖于状态的额外变量:

@Composable







private fun Greeting(name: String) {


    val expanded  = remember { mutableStateOf(false) }
    val extraPadding = if (expanded.value) 48.dp else 0.dp
}



您无需在重组后记住extraPadding,因为它仅执行简单的计算。

现在我们可以将新的内边距修饰符应用于Column:

@Composable
private fun Greeting(name: String) {
    val expanded = remember { mutableStateOf(false) }
    val extraPadding = if (expanded.value) 48.dp else 0.dp
    
    Surface(
        color = MaterialTheme.colorScheme.primary,
        modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp
    ) {
        Row(modifier = Modifier.padding(24.dp)) {
            Column(
                modifier = Modifier.weight(1f)
                .padding(bottom = extraPadding)
            ) {
                Text(text = "Hello, ")
                Text(text = name)
            }
            ElevatedButton(
                onClick = { expanded.value = !expanded.value }
            ) {
                Text(if (expanded.value) "Show less" else "Show more")
            }
        }

    }

}

如果在模拟器上运行,您应该会看到每项内容均可单独展开。

4.gif

8、状态提升

可这函数中,被多个函数读取或修改的状态位于共同祖先实体中,此过程称为状态提升。“提升”意味“提高”或“升级”。

使状态可提升,可以避免复制状态和引入bug,有助于重复使用可组合项,并大大降低可组合项的测试难度。相反,不需要有可组合项的父级控制的状态则不应该被提升。可信来源属于该状态的创建者和控制者。

例如,让我们来为应用创建一个初始配置屏幕

image.png
将以下代码添加到MainActivity.kt:

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.material3.Button
import androidx.compose.runtime.getValue
import androidx.compose.runtimw.setValue
import androidx.compose.ui.Alignment
...
@Composable
fun OnboardingScreen(modifier: Modifier = Modifier) {
    // TODO: This state should be hoisted
    var shouldShowOnboarding by remember { mutableStateOf(true) }
    
    Column(
        modifier = modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("Welcome to the Basics Codelab!")
        Button(
            modifier = Modifier.padding(vertical = 24.dp),
            onClick = { shouldShowOnboarding = false }
        ) {
            Text("Continue")
        }

    }

}


@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable

fun OnboardingPreview() {
    BasicsCodelabTheme {
        onboardingScreen()
    }
}

此代码包含多个新功能:

  • 您已经添加了一个名为OnboardingScreen的新可组合项以及一个新的预览。构建项目时,您会发现您可以同时拥有多个预览。我们还添加了一个固定高度,以验证内容是否正确对齐。
  • 可以配置Column,使其在屏幕中心显示其内容。
  • shouldShowOnboarding使用的是by关键字,而不是=。这是一个属性委托,可让您无需每次都输入.value
  • 点击该按钮时,会将shouldShowOnboarding设为false,尽管您并未从任何位置读取该状态。

现在,我们即可将这个新的初始配置屏幕添加到应用。我们希望该屏幕在应用启动时显示,然后在用户按“继续”时隐藏。

在Compose中,您不会隐藏界面元素,因为不会将它们添加到组合中,因此它们也不会添加到Compose生成的界面数中。您只需要使用简单的Kotlin条件逻辑就可以做到这一点。例如,如需显示初始配置屏幕或问候语列表,您需要执行以下操作:

@Composable



fun MyApp(modifier: Modifier = Modifier) {
    Surface(modifier) {
        if (shouldShowOnboarding) {// Where does this come from?
            OnboardingScreen()
        } else {
            Greetings()
        }
    }
}

但是,我们无法访问shouldShowOnboarding。很明显,我们需要与MyApp可组合项共享在OnboardingScreen中创建的状态。

我们不会以某种方式与状态的父级共享状态值,而是会提升该状态,也就是将状态转移到需要访问它的共同祖先实体中。

首先,将MyApp的内容移到名为Greetings的新可组合项中。此外,调整预览以改为调用Greetings方法:

@Composable







fun MyApp(modifier: Modifier = Modifier) {


    Greetings()
}



@Composable

private fun Greeting(
    modifier: Modifier = Modifier,
    name: List<String> = listOf("World", "Compose")
) {
    Column(modifier = modifer.padding(vertical = 4.dp)) {
        for (name in names) {
            Greeting(name = name)
        }


    }


}




@Preview(showBackground = true, widthDp = 320)
@Composable
private fun GreetingsPreview() {
    BasicsCodelabTheme {
        Greetings()
    }

}

为新的顶级MyApp可组合项添加预览,以便测试其行为:

@Preview
@Composable



fun MyAppPreview() {
    BasicsCodelabTheme {



        MyApp(Modifier.fillMaxSize())
    }



}



现在,添加相应的逻辑来显示MyApp中的不同屏幕,并提升状态。

@Composable







fun MyApp(modifier: Modifier = Modifier) {


    var shouldShowOnboarding by remember { mutableStateOf(true) }

    



    Surface(modifier) {

        if (shouldShowOnboarding) {

            OnboardingScreen(/* TODO */)
        } else {

            Greetings()

        }


    }


}


我们还需要与初始化配置屏幕共享shouldShowOnboarding,但我们不会直接传递它。与其让OnboardingScreen更改状态,不如让它在用户点击”Continue”按钮时通知我们。

如何向上传递时间?通过向上传递回调来传递。回调是这样一类函数,它们以实参的形式传递给其他函数,并在事件发生时执行。

尝试向初始配置屏幕添加定义为onContinueClicked: () -> Unit的函数参数,以便您可以从MyApp更改状态。

解决方案:

@Composable







fun MyApp(modifier: Modifier = Modifier) {


    var shouldShowOnboarding by remember { mutableStateOf(true) }

    



    Surface(modifier) {

        if (shouldShowOnboarding) {

            OnboardingScreen(onContinueClicked = { shouldShowOnboarding = flase })
        } else {

            Greetings()

        }


    }


}



@Composable
fun OnboardingScreen(
    onContinueClicked: () -> Unit,
    modifier: Modifier = Modifier
) {
    Column(
        modifier = modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("Welcome to the Basics Codelab!")
        Button(
            modifier = Modifier.padding(vertical = 24.dp),
            onClick = onContinueClicked
        ) {
            Text("Continue")
        }
    }
}

通过向OnboardingScreen传递函数而不是状态,可以提高该组合项的可重用性,并防止状态被其他可组合项更改。一般而言,这可以让事情变得简单。一个很好的例子就是,现在需要如何修改初始配置屏幕预览来调用OnboardingScreen:

@Preview(shouldBackground = true, widthDp = 320, heightDp = 320)
@Composable



fun OnboardingPreview() {
    BasicsCodelabTheme {



        OnboardingScreen(onContinueClicked = {}) // Do nothing on click.
    }



}



onContinueClicked分配给空lambda表达式就等于“什么也不做”,这非常适合于预览。

看起来已经越来越像一个真正的应用了。

2.gif

到目前为止的完整代码:

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.ElevatedButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.codelab.basics.ui.theme.BasicsCodelabTheme

class MainActivity: ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ComposePractiseTheme {
                MyApp(modifier = Modifier.fillMaxSize())
            }
        }
    }
}

@Composable
fun MyApp(modifier: Modifier = Modifier) {
    var shouldShowOnboarding by remember { mutableStateOf(true) }
    
    Surface(modifier) {
        if (shouldShowOnboarding) {
            OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
        } else {
            Greetings()
        }
    }
}

@Composable
fun OnboardingScreen(
    onContinueClicked: () -> Unit,
    modifier: Modifier = Modifier
) {
    Column(
        modifier = modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("Welcome to the Basics Codelab!")
        Button(
            modifier = Modifier.padding(vertical = 24.dp),
            onClick = onContinueClicked
        ) {
            Text("Coutinue")
        }
    }
}

@Composable
private fun Greetings(
    modifier: Modifier = Modifier,
    names: List<String> = listOf("World", "Compose")
) {
    Column(modifier = modifier.padding(vertical = 4.dp)) {
        for (name in names) {
            Greeting(name = name)
        }
    }
}

@Preview(showBackground = true, widthDp = 320, heightDp = 320)
@Composable
fun OnboardingPreview() {
    ComposePractiseTheme {
        OnboardingScreen(onContinueClicked = {})
    }
}

@Composable
private fun Greeting(name: String) {
    val expanded = remember { mutableStateOf(false) }
    val extraPadding = if (expanded.value) 48.dp else 0.dp
    
    Surface(
        color = MaterialTheme.colorScheme.primary,
        modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
    ) {
        Row(modifier = Modifier.padding(24.dp)) {
            Column(
                modifier = Modifier.weight(1f).padding(bottom = extraPadding)
            ) {
                Text(text = "Hello,")
                Text(text = name)
            }
            ElevatedButton(
                onClick = { expanded.value = !expanded.value }
            ) {
                Text(if (expanded.value) "Show less" else "Show more")
            }
        }
    }
}

@Preview(showBackground = true, widthDp = 320)
@Composable
fun DefaultPreview() {
    ComposePractiseTheme {
        Greetings()
    }
}

@Preview
@Composable
fun MyAppPreview() {
    ComposePractiseTheme {
       MyApp(Modifier.fillMaxSize())
    }
}

翻译原文:Compose 基础知识

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYjYvrOb' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片