Android Compose UI 学习笔记

拜读郭神写的 Jetpack Compose 教程的读书笔记:blog.csdn.net/guolin_blog...

Jetpack Compose 是近几年里 Android 开发领域最大的一次更新,摒弃了以前的 View 系统,采用了全新的声明式 UI 构建方式。它不仅仅是一个 UI 框架,更是一种全新的开发理念,能够让开发者以更简洁、更高效的方式构建 Android 应用。

其实严格来说,Compose 并不是一项新技术,早在 2019 年 Google I/O 上就已经发布了 Compose 的第一个 alpha 版本。2021 年发布了 Compose 1.0,标志着它已经进入了稳定阶段。到如今也有 5 个年头了。

为什么要学 Compose?

先回答一个问题:为什么要学 Compose,它的优势有:

  • Android UI 开发的新方向
  • 用声明式方式描述界面
  • 减少 XML 和 View 之间的切换成本
  • 更容易维护和复用 UI
  • 和 Kotlin 结合紧密

它不是简单替代 XML,而是带来一种更自然的 UI 构建方式,尤其适合 Kotlin 开发。

传统 View 开发属于命令式,开发者需要手动更新控件。Compose 则更像是在描述当前状态下界面应该如何渲染。

  • 命令式 UI:先找到控件,再一步一步修改它
  • 声明式 UI:告诉框架"界面应该长什么样"

用一个函数表达的话,就是 UI = f(state)。当状态发生变化时,Compose 会自动重新计算 UI,开发者无需手动更新控件。这种声明式的方式可以让 UI 的构建更加直观和简洁。

我们来看一个例子,比如我们写一个 LinearLayout,里面有两个 TextView,展示 date 和 weather 两个信息。传统的 View 开发方式是:定义 xml,再在 Activity 或 Fragment 中通过 findViewById 获取控件,然后设置文本内容。

xml 复制代码
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:id="@+id/tvDate"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="date"/>

    <TextView
        android:id="@+id/tvWeather"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="weather"/>
</LinearLayout>
Kotlin 复制代码
findViewById<TextView>(R.id.tvDate).text = date
findViewById<TextView>(R.id.tvWeather).text = weather

在 Compose 当中,我们是通过一个函数来描述这个界面,把内容通过函数参数传递进去。

Kotlin 复制代码
@Composable
fun HelloCompose(date: String,weather: String) {
    Column {
        Text("$date")
        Text("$weather")
    }
}

如果我们需要修改页面内容,就修改传进去的参数就行了。

再看另一个例子,比如用户进入我们App的首页,首页内容的加载是需要一些时间的,通常这个时候我们会先显示一个加载框或者是占位图给用户,等首页内容加载完成之后,再把加载框隐藏掉,将正常的首页呈现给用户。而如果加载过程中遇到了一些问题,比如说用户的手机没网,这个时候就展示一个错误页面给用户。

这是一个非常常见的需求,传统的写法是这样写的,判断页面状态,控制控件的显示和隐藏。

Kotlin 复制代码
fun refreshHomePageVisibility(Status status) {
    when (status.current) {
        Status.SUCCESS -> {
            homePageView.visibility = View.VISIBLE
            loadingView.visibility = View.GONG
            errorView.visibility = View.GONG
        }
        Status.LOADING -> {
            loadingView.visibility = View.VISIBLE
            homePageView.visibility = View.GONG
            errorView.visibility = View.GONG
        }
        Status.ERROR -> {
            errorView.visibility = View.VISIBLE
            loadingView.visibility = View.GONG
            homePageView.visibility = View.GONG
        }
    }
}

Compose 的写法是,判断页面的状态,绘制对应的 UI。

Kotlin 复制代码
@Composable
fun HomePage(Status status) {
    when (status.current) {
        Status.SUCCESS -> {
            HomePageContent()
        }
        Status.LOADING -> {
            LoadingContent()
        }
        Status.ERROR -> {
            ErrorContent()
        }
    }
}

不用再手动控制控件的可见性了。

总结一下,命令式 UI 的思想是把控件找出来,命令它显示、隐藏、命令它展示指定的文字、图片等。

而声明式 UI 的思想是,开发者负责准备好数据,然后把这个数据对应的 UI 声明清楚即可。之后 UI 就能自动根据数据的变化而变化了。有点类似于 DataBinding 的思想。

Compose Basics

当我们新建一个使用 Compose 的 Android 项目后,默认的 MainActivity 长这样。

Kotlin 复制代码
class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent{
            ComposeDemoTheme {
                Scaffold(modifier = Modifier.fillMaxSize()){ innerPadding->
                    Greeting(
                        name = "Android",
                        modifier = Modifier.padding(innerPadding)
                    )
                }
            }
        }
    }
}

@Composable
funGreeting(name: String,modifier: Modifier = Modifier) {
    Text(
        text = "Hello$name!",
        modifier = modifier
    )
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    ComposeDemoTheme {
        Greeting("Android")
    }
}

这里自动生成的代码涉及到了一些Compose的基础知识,我们得把这些基础知识了解清楚了,才能开始后续Compose的学习。

首先看一下 onCreate 函数中的代码,这里调用了一个 setContent 函数。过去我们使用View来编写界面的时候,调用的是 setContentView 函数来设置界面。而用了 Compose 之后,由于不再使用 View 了,所以就改用这个新的 setContent 函数来设置界面了。setContent 函数会提供一个 Composable 作用域,在它的闭包中我们可以调用 Composable 函数。

那么什么是 Composable 函数呢?简单来说,就是一个函数的上方,使用了 @Composable 注解,那么它就是一个 Composable 函数。这里的 Greeting 函数就是一个 Composable 函数。

所有的 Composable 函数还有一个约定俗成的习惯,就是函数的命名首字母需要大写。大多数情况下,我们能够直观地通过函数名称来判断这是不是一个 Composable 函数。

ComposeDemoTheme 是 Android Studio 自动为我们创建的一个主题函数,用于切换应用的主题。

Scaffold 是 Material 库中提供的一个函数,用于让应用适配 Material Design。

Greeting 函数是我们自己定义的一个 Composable 函数,它接收一个 name 参数,并在屏幕上显示一段文本。Text 函数是 Compose 提供的一个 Composable 函数,用于在屏幕上显示文本内容。

@Preview 修饰的函数用于在 Android Studio 中实时预览 Composable 函数的效果。

接下来我们就来学习 Compose 的基础控件。

Text

首先,一个非常常用的控件是 Text,它用于在屏幕上显示文本内容。Text 函数接收一个字符串参数,并将其显示在屏幕上。

Kotlin 复制代码
@Composable
fun SimpleWidgetColumn() {
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(text = "This is Text")
    }
}

通过 color 参数,我们可以设置文本的颜色;通过 fontSize 参数,我们可以设置文本的大小。

Kotlin 复制代码
@Composable
fun SimpleWidgetColumn() {
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            text = "This is Text",
            color = Color.Blue,
            fontSize = 26.sp
        )
    }
}

它可以定制的东西是很多的,可以看一下它的函数定义。

Kotlin 复制代码
@Composable
fun Text(
    text: String,
    modifier: Modifier = Modifier,
    color: Color = Color.Unspecified,
    autoSize: TextAutoSize? = null,
    fontSize: TextUnit = TextUnit.Unspecified,
    fontStyle: FontStyle? = null,
    fontWeight: FontWeight? = null,
    fontFamily: FontFamily? = null,
    letterSpacing: TextUnit = TextUnit.Unspecified,
    textDecoration: TextDecoration? =null,
    textAlign: TextAlign? = null,
    lineHeight: TextUnit = TextUnit.Unspecified,
    overflow: TextOverflow = TextOverflow.Clip,
    softWrap: Boolean = true,
    maxLines: Int = Int.MAX_VALUE,
    minLines: Int = 1,
    onTextLayout: ((TextLayoutResult) -> Unit)? = null,
    style: TextStyle = LocalTextStyle.current,
)

比如通过 fontWeight 参数,我们可以设置文本的粗细。通过 fontFamily 可以设置文本的字体。通过 textAlign 可以设置文本的对齐方式。通过 maxLines 可以设置文本的最大行数。通过 overflow 可以设置文本溢出时的处理方式。通过 lineHeight 可以设置文本的行高。通过 letterSpacing 可以设置文本的字间距。通过 style 可以设置文本的样式。通过 modifier 可以设置文本的修饰符。通过 softWrap 可以设置文本是否自动换行。通过 onTextLayout 可以获取文本布局信息。通过 inlineContent 可以在文本中插入内联内容。

Button

下一个常用的控件是 Button,它用于在屏幕上显示一个按钮。Button 函数接收一个 onClick 参数,当用户点击按钮时,会触发这个回调函数。

Kotlin 复制代码
@Composable
fun SimpleWidgetColumn() {
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            text = "This is Text",
            color = Color.Blue,
            fontSize = 26.sp
        )
        Button(onClick = { /* Handle button click */ }) {
            Text(text = "This is Button")
        }
    }
}

需要注意的是,在 View 系统中,Button 继承自 TextView,所以它本身就有一个文本内容。而在 Compose 中,Button 并没有文本内容,它只是一个容器,我们需要在 Button 的闭包中放置一个 Text 函数来显示按钮的文本内容。

我们可以看一下 Button 的函数定义。

Kotlin 复制代码
@Composable
fun Button(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    shape: Shape = ButtonDefaults.shape,
    colors: ButtonColors = ButtonDefaults.buttonColors(),
    elevation: ButtonElevation? = ButtonDefaults.buttonElevation(),
    border: BorderStroke? = null,
    contentPadding: PaddingValues = ButtonDefaults.ContentPadding,
    interactionSource: MutableInteractionSource? = null,
    content: @ComposableRowScope.() -> Unit,
)

通过 onClick 参数,我们可以设置按钮的点击事件。通过 modifier 参数,我们可以设置按钮的修饰符。通过 enabled 参数,我们可以设置按钮是否可用。通过 elevation 参数,我们可以设置按钮的阴影效果。通过 shape 参数,我们可以设置按钮的形状。通过 border 参数,我们可以设置按钮的边框。通过 colors 参数,我们可以设置按钮的颜色。通过 contentPadding 参数,我们可以设置按钮的内容内边距。

View 系统下,定制边框需要自定义 shape xml,比较麻烦。而在 Compose 中,定制边框只需要设置 border 参数即可,非常方便。我们可以尝试一下。

Kotlin 复制代码
@Composable
fun SimpleWidgetColumn() {
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            text = "This is Text",
            color = Color.Blue,
            fontSize = 26.sp
        )
        Button(
            onClick = { /* Handle button click */ },
            modifier = Modifier.shadow(
                elevation = 8.dp,
                shape = RoundedCornerShape(12.dp),
                clip = false
            ),border = BorderStroke(2.dp,Color.Blue)
        ) {
            Text(text = "This is Button")
        }
    }
}

Image

下一个常用的控件是 Image,它用于在屏幕上显示一张图片。Image 函数接收一个 painter 参数,用于设置图片的内容;接收一个 contentDescription 参数,用于设置图片的描述信息。

Kotlin 复制代码
Image(
    painter = painterResource(id = R.drawable.picture),
    contentDescription = "This is Image"
)

TextField

下一个常用的控件是 TextField,它用于在屏幕上显示一个文本输入框。TextField 函数接收一个 value 参数,用于设置文本输入框的初始内容;接收一个 onValueChange 参数,当用户输入内容时,会触发这个回调函数。

Kotlin 复制代码
TextField(
    value = "",
    onValueChange = { /* Handle text change */ },
    label = {
        Text("This is TextField")
    }
)

还有一些常用的控件,比如:

  • Column:对应垂直排列的 LinearLayout
  • Row:对应水平排列的 LinearLayout
  • Box:对应 FrameLayout
  • ProgressIndicator:对应 ProgressBar

不再一一演示了。

State Management

接下来我们看一下 Compose 的状态管理。

前面我们介绍了 TextField 控件:

Kotlin 复制代码
TextField(
    value = "",
    onValueChange = { /* Handle text change */ },
    label = {
        Text("This is TextField")
    }
)

但我们会发现,无论键盘输入什么,它的内容都不会发生变化。为什么呢?

在 View 系统中,没有这样的问题。我们写一个 EditText,键盘就能直接输入了。需要获取值的时候,直接调用 getText() 方法就行了。

而在 Compose 中,TextField 的内容是由 value 参数控制的。value 参数是我们传给这个控件的。所以这里的逻辑需要我们自己控制才行。

Kotlin 复制代码
@Composable
fun SimpleWidgetColumn() {
    var userInputbyremember { mutableStateOf("")}
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        TextField(
            value = userInput,
            onValueChange = {
                userInput = it
            },
            label = {
                Text("This is TextField")
            }
        )
    }
}

首先定义一个 userInput 变量,通过 by remember { mutableStateOf("") } 来初始化它。然后把 userInput 传给 TextField 的 value 参数。接着在 onValueChange 回调中,把用户输入的内容赋值给 userInput 变量。这样就能实现键盘输入了。

这是什么原理呢?其实很简单,remember 函数会在 Composable 函数第一次执行时,记住它的值,并在后续的重组中保持这个值不变。mutableStateOf 函数会创建一个可变的状态对象,当它的值发生变化时,会触发 Composable 函数的重组,从而更新 UI。

需要注意的是,这里通过 by 关键字实现了委托的效果,也就是 userInput 变量的值会自动绑定到状态对象上。当我们修改 userInput 的值时,状态对象的值也会随之改变,从而触发 UI 的更新。这里的 State 对象由于用 remember 包裹了,所以这个 State 对象的地址是不会变的,只是它里面包裹的值在发生变化。

除了 remember,还有一个 rememberSaveable 函数,它和 remember 的区别是,rememberSaveable 会在配置更改(比如屏幕旋转)时保存状态,而 remember 不会。

Kotlin 复制代码
var userInputbyrememberSaveable  { mutableStateOf("")}

这样的 TextField 控件就能实现键盘输入了,但是这样的 Compose 函数并不算写得很好。在 Compose 中,状态应该尽量提升到 Composable 函数的外部,这样可以让状态在不同的 Composable 函数之间共享。

这个过程称之为状态提升。

State hoisting

State hoisting:状态向下传递、事件向上传递

状态提升有什么好处呢,我们通过一个例子来对比一下。

首先,我们编写一个 Counter 函数,用于实现点击计数。

Kotlin 复制代码
@Composable
fun Counter(modifier: Modifier = Modifier) {
    var countbyremember { mutableStateOf(0)}
    Column(
        modifier = modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            text = "$count",
            fontSize = 50.sp
        )
        Button(
            onClick = { count++}
        ) {
            Text(
                text = "Click me",
                fontSize = 26.sp
            )
        }
    }
}

现在来了一个新需求,我需要两个计数器,一个每次加1,一个每次加 2。

我们可以把代码拷贝一份,然后把加1的改成加2的。这样就能实现两个计数器了。

但是,这样的代码并不优雅,而且如果我们需要增加更多的计数器,就需要不断地拷贝代码,维护起来非常麻烦。

我们可以通过状态提升来解决这个问题。我们把 count 变量提升到 Counter 函数的外部,然后把它作为参数传递给 Counter 函数。这样就能实现多个计数器共享同一个状态了。

Kotlin 复制代码
@Composable
fun Counter(count: Int,onIncrement: () -> Unit,modifier: Modifier = Modifier) {
    Column(
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text(
            text = "$count",
            fontSize = 50.sp
        )
        Button(
            onClick = { onIncrement()}
        ) {
            Text(
                text = "Click me",
                fontSize = 26.sp
            )
        }
    }
}

状态提升的原则是,状态应该尽量提升到 Composable 函数的外部,这样可以让状态在不同的 Composable 函数之间共享。

现在,我们就能用同一套逻辑实现两个计数器了。

Kotlin 复制代码
@Composable
fun CallCounter(modifier: Modifier = Modifier) {
    var countbyrememberSaveable { mutableStateOf(0)}
    var doubleCountbyrememberSaveable { mutableStateOf(0)}
    Column(
        modifier = modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Counter(
            count = count,
            onIncrement = { count++},
            modifier.fillMaxWidth()
        )
        Counter(
            count = doubleCount,
            onIncrement = { doubleCount += 2},
            modifier.fillMaxWidth()
        )
    }
}

Lazy Layouts

接下来我们学习 LazyColumn 和 LazyRow,它们是 Compose 提供的用于实现列表的控件。LazyColumn 用于实现垂直列表,LazyRow 用于实现水平列表。它们是替代 RecyclerView 的控件,使用起来更加简单和高效。

用 RecyclerView 实现列表,需要自定义 layout_item,创建 Adapter。较为繁琐。

用 Compose 实现一个简单的列表,只需要这一小段:

Kotlin 复制代码
@Composable
fun ScrollableList() {
    LazyColumn {
        items(20){ i->
            Text(
                text = "Item$i",
                modifier = Modifier
                    .fillMaxWidth()
                    .height(60.dp)
            )
        }
    }
}

两者的差异如此明显,以至于 RecyclerView 的开发者为此还发了一条 Twitter:

接下来我们写一个漂亮一点的列表。首先定义两个渐变色组成的 header 和 footer,然后用 Card 包装 list 的 item,最后用 LazyColumn 来展示整个列表。

Kotlin 复制代码
@Composable
fun ColorHeader() {
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .height(140.dp)
            .padding(horizontal = 12.dp,vertical = 8.dp)
            .clip(RoundedCornerShape(20.dp))
            .background(
                brush = Brush.verticalGradient(
                    colors = listOf(
                        Color(0xFF6A11CB),
                        Color(0xFF2575FC)
                    )
                )
            ),
        contentAlignment = Alignment.CenterStart
    ) {
        Text(
            text = "Welcome",
            color = Color.White,
            fontSize = 28.sp,
            fontWeight = FontWeight.Bold,
            modifier = Modifier.padding(start = 20.dp)
        )
    }
}
Kotlin 复制代码
@Composable
fun ColorFooter() {
    Box(
        modifier = Modifier
            .fillMaxWidth()
            .height(100.dp)
            .padding(horizontal = 12.dp,vertical = 8.dp)
            .clip(RoundedCornerShape(20.dp))
            .background(
                brush = Brush.horizontalGradient(
                    colors = listOf(
                        Color(0xFFFF9966),
                        Color(0xFFFF5E62)
                    )
                )
            ),
        contentAlignment = Alignment.Center
    ) {
        Text(
            text = "Thanks for scrolling ✨",
            color = Color.White,
            fontSize = 16.sp,
            fontWeight = FontWeight.Medium
        )
    }
}
Kotlin 复制代码
@Composable
fun ScrollableList() {
    val state = rememberLazyListState()
    val list = (1..10).map { it.toString() }
    LazyColumn(state = state){
        item{
            ColorHeader()
        }
        items(list,key = { it }){ letter->
            Card(
                modifier = Modifier
                    .fillMaxWidth()
                    .height(120.dp)
                    .padding(10.dp),
                shape = RoundedCornerShape(16.dp)
            ) {
                Text(
                    text = letter,
                    textAlign = TextAlign.Center,
                    fontSize = 20.sp,
                    modifier = Modifier
                        .fillMaxSize()
                        .wrapContentHeight(Alignment.CenterVertically)
                )
            }
        }
        item{
            ColorFooter()
        }
    }
}

我们可以看到,整个列表的代码非常简洁直观,而且效果也非常漂亮。

这里需要注意一点,最好给列表的每个 item 设置一个独一无二的 key,这样在 item 增删改的时候,才不至于重绘所有 items,可以提升性能。

Compose Animation Basics

接下来我们学习 Compose 动画。

有时候做业务做久了,都快忘了自己是一个画 UI 的前端仔了。其实在 Android 里,动画是非常重要的一个环节。它不仅仅是为了好看,更是为了让用户有更好的体验。

Compose 提供了丰富的动画 API,能够让我们轻松地实现各种动画效果。

先来看一个简单的例子,通过 animateColorAsState 函数来实现颜色的渐变动画。我们定义一个 selected 变量,当用户点击按钮时,切换 selected 的值,从而触发颜色的渐变动画。

Kotlin 复制代码
@Composable
fun ColorAnimationDemo() {
    var selectedbyremember { mutableStateOf(false)}
    val bgColorbyanimateColorAsState(
        targetValue = if(selected)Color(0xFF4CAF50)elseColor(0xFFFFC107),
        label = "bgColor"
    )
    Box(
        modifier = Modifier
            .size(120.dp)
            .background(bgColor)
            .clickable { selected = !selected },
        contentAlignment = Alignment.Center
    ) {
        Text("Tap")
    }
}

再看一个例子,通过 animateDpAsState 函数来实现大小的渐变动画,我们定义一个 expanded 变量,当用户点击按钮时,切换 expanded 的值,从而触发大小的渐变动画。

Kotlin 复制代码
@Composable
fun SizeAnimationDemo() {
    var expandedbyremember { mutableStateOf(false)}
    val sizebyanimateDpAsState(
        targetValue = if(expanded)180.dp else100.dp,
        label = "size"
    )

    Box(
        modifier = Modifier
            .size(size)
            .background(Color(0xFF2196F3))
            .clickable { expanded = !expanded },
        contentAlignment = Alignment.Center
    ) {
        Text("Scale",color = Color.White)
    }
}

再看一个透明度动画的例子,通过 animateFloatAsState 函数来实现透明度的渐变动画,我们定义一个 visible 变量,当用户点击按钮时,切换 visible 的值,从而触发透明度的渐变动画。

Kotlin 复制代码
@Composable
fun AlphaAnimationDemo() {
    var visiblebyremember { mutableStateOf(true)}
    val alphabyanimateFloatAsState(
        targetValue = if(visible)1felse0.2f,
        label = "alpha"
    )

    Image(
        painter = painterResource(id = R.drawable.picture),
        contentDescription = "Logo",
        modifier = Modifier
            .size(100.dp)
            .alpha(alpha)
            .clickable { visible = !visible }
    )
}

再看一个位移动画,通过 animatedDpAsState 函数来实现位移的渐变动画,我们定义一个 move 变量,当用户点击按钮时,切换 move 的值,从而触发位移的渐变动画。

Kotlin 复制代码
@Composable
fun OffsetAnimationDemo() {
    var movedbyremember { mutableStateOf(false)}
    val offsetXbyanimateDpAsState(
        targetValue = if(moved)120.dp else0.dp,
        label = "offsetX"
    )

    Box(
        modifier = Modifier
            .offset(x = offsetX)
            .size(80.dp)
            .background(Color.Magenta)
            .clickable { moved = !moved }
    )
}

再看一个综合的例子,叠加大小渐变、颜色渐变的动画效果。

Kotlin 复制代码
@Composable
fun ExpandCardDemo() {
    var expandedbyremember { mutableStateOf(false)}

    val cardHeightbyanimateDpAsState(
        targetValue = if(expanded)180.dp else100.dp,
        label = "cardHeight"
    )

    val bgColorbyanimateColorAsState(
        targetValue = if(expanded)Color(0xFF8BC34A)elseColor(0xFF03A9F4),
        label = "bgColor"
    )

    Card(
        modifier = Modifier.height(cardHeight).fillMaxWidth().clickable { expanded = !expanded }
    ) {
        Box(modifier = Modifier.fillMaxSize().background(bgColor),
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = if(expanded)"Expanded"else"Collapsed",
                color = Color.White
            )
        }
    }
}

简单来说,Compose 做动画的方式是把需要动态变化的值包装成一个 State 对象,然后通过 animate*AsState 函数来实现动画效果。当 State 对象的值发生变化时,Compose 会自动计算出中间值,从而实现动画效果。

之前 Google 为了推广 Compose,举行了 Compose 挑战赛,我也参加了一下,就是用 Compose 做了一个倒计时的动画效果:github.com/Vic-wkx/Cou...

最后也拿到了 Google 寄来的奖品。这个奖品是从国外寄过来的,当时还给我打电话让我办了一个手续,叫做清关手续。还挺有意思的,体验了一下收国外快递的感觉。

Introduction to Compose Multiplatform

最后讲一下 Compose 可以用来跨平台。现在的很多新框架,新语言基本都会考虑跨平台,都有一个一统前端的雄心壮志。我也很期待前端大一统的一天,我想象中的未来的前端应该是到处都有,都运行一套逻辑。比如我们的桌子、墙壁上都能展示出一个屏幕,可以展示资讯,交互等等。

Compose 也不例外,它可以用来编写 Web 程序,也可以用来编写桌面程序。不过目前还是用 Compose 做 Android 比较多。这里就不过多介绍了,只需要知道它可以跨平台就可以了。

相关推荐
天空之城--9 小时前
过去一周Android行业动态:编码技术与综合趋势精选
android·flutter·性能优化·kotlin·android jetpack
我命由我123452 天前
Linux - Linux/POSIX 路径斜杠折叠规则
linux·运维·服务器·android studio·android jetpack·android-studio·android runtime
XiaoLeisj2 天前
Jetpack Compose 知识点
android·kotlin·android jetpack·compose
我命由我123452 天前
Android 设备的日志缓冲区被写满,logcat: Unexpected EOF!
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
alexhilton5 天前
让架构边界变成可执行的测试
android·kotlin·android jetpack
我命由我123457 天前
Android Camera - 获取当前设备屏幕的旋转角度、保存帧到外部存储的私有空间
android·java·开发语言·java-ee·android jetpack·android-studio·android runtime
我命由我123458 天前
Android 开发 - 获取当前设备屏幕的旋转角度
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
我命由我123459 天前
人脸识别 - 判断人脸是否在画面中央
java·人工智能·python·java-ee·人脸识别·android jetpack·android runtime
一个用户名i11 天前
【Compose 系列】第 4 篇:布局与 Modifier,写界面不走弯路
android jetpack