在xml文件中经过各种特点来描述View,在Compose中经过Modifier润饰符来界说UI组件的款式。
在Compose中,每个基础UI组件都有一个Modifier参数,经过界说Modifier来修改组件的款式。
一、常用的润饰符
API | 阐明 |
---|---|
align | 设置组件在父容器中的对齐办法。 |
alpha | 设置组件的透明度。 |
aspectRatio | 设置组件的宽高比。 |
background | 设置组件的布景款式。 |
border | 设置组件的边框款式。 |
clickable | 设置组件可点击。 |
clip | 设置组件的裁剪效果。 |
clipToBounds | 设置组件是否裁剪超出鸿沟的内容。 |
fillMaxHeight | 将组件的高度设置为最大可用空间。 |
fillMaxSize | 将组件的尺寸设置为最大可用空间。 |
fillMaxWidth | 将组件的宽度设置为最大可用空间。 |
focusRequester | 设置组件的焦点恳求器。 |
height | 设置组件的固定高度。 |
indication | 设置组件的接触反应效果。 |
layout | 设置组件的自界说布局规则。 |
offset | 设置组件的偏移量。 |
padding | 设置组件的内边距。 |
pointerInput | 设置组件的指针输入处理。 |
requiredHeight | 设置组件的最小高度。 |
requiredSize | 设置组件的最小尺寸。 |
requiredWidth | 设置组件的最小宽度。 |
rotate | 设置组件的旋转视点。 |
scale | 设置组件的缩放比例。 |
shadow | 设置组件的阴影效果。 |
size | 设置组件的尺寸。 |
swipeable | 设置组件可滑动。 |
testTag | 为组件设置测试标签。 |
weight | 设置组件在父容器中的权重。 |
width/height | 设置组件的固定宽高度。 |
zIndex | 设置组件的堆叠次序。 |
draggable | 获取组件单向的拖拽的偏移量 |
二、常用润饰符用法示例
1、Modifier.size
Image(
painter = painterResource(id = R.mipmap.rabit),
contentDescription = "图片描述",
modifier = Modifier
//.size(150.dp) <--------这个也行,下面是重载办法
.size(width = 150.dp, height = 150.dp)
.clip(CircleShape)
)
宽高150dp的圆角图片就完成了
假如想要设置宽度为屏幕宽度,高度为300dp,应该怎么写?
办法一:
Box(
modifier = Modifier
.fillMaxWidth()
.height(300.dp)
.background(Color.Blue)
) { }
办法二:
import androidx.compose.ui.platform.LocalConfiguration
//获取屏幕宽度的dp值
val screenWidth = LocalConfiguration.current.screenWidthDp.dp
Box(
modifier = Modifier
.size(height = 300.dp, width = screenWidth)
.background(Color.Blue)
) { }
2、Modifier.background
backgroud润饰符用来为被润饰组件增加布景色。布景色支持设置color的纯色布景,也能够运用brush设置渐变色布景。
Row {
//Box1
Box(
modifier = Modifier
.size(150.dp)
.background(color = Color.Blue) <--------纯色布景
) {
Text(text = "纯色", Modifier.align(Alignment.Center), color = Color.White)
}
// 增加水平间距
Spacer(modifier = Modifier.width(50.dp))
//Box2
Box(
modifier = Modifier
.size(150.dp)
.background( <--------渐变色布景
brush = Brush.horizontalGradient( //创立Brush水平方向的线性渐变色
listOf(
Color.Cyan,
Color.Blue,
Color.Green
)
)
)
) {
Text(text = "渐变色", Modifier.align(Alignment.Center), color = Color.White)
}
}
运转在手机上的效果
xml中View的background特点能够设置图片格式的布景,Compose的background润饰符只能设置色彩布景,图片布景需求运用其他组件完成。下面是一些示例:
办法一:
Box(
modifier = Modifier
.fillMaxWidth()
.height(300.dp)
.paint(painterResource(id = R.mipmap.rabit), contentScale = ContentScale.Fit) <----布景图
) { }
办法二:
@Composable
fun ShowImage() {
// 获取屏幕的参数
val density = LocalDensity.current.density //屏幕密度
val screenWidthDp = LocalConfiguration.current.screenWidthDp.dp //屏幕宽度的dp值
val screenHeightDp = LocalConfiguration.current.screenHeightDp.dp //屏幕高度的dp值
val screenWidthPx = LocalConfiguration.current.screenWidthDp.dp.value * density //屏幕宽度的px值
val screenHeightPx = LocalConfiguration.current.screenHeightDp.dp.value * density //屏幕高度的px值
//将资源图片转化为ImageBitmap
val option = BitmapFactory.Options().apply {
inPreferredConfig = Bitmap.Config.ARGB_8888
}
val imageBitmap =
BitmapFactory.decodeResource(LocalContext.current.resources, R.mipmap.rabit, option)
.asImageBitmap()
Box(
modifier = Modifier
.background(Color.Green)
.width(screenWidthDp)
.height(screenHeightDp) // 设置Box组件宽高为屏幕的宽高
.drawBehind {
drawImage( <-------制作布景
imageBitmap,
srcOffset = IntOffset.Zero,
srcSize = IntSize(imageBitmap.width, imageBitmap.height), //制作的图片巨细
dstOffset = IntOffset.Zero,
dstSize = IntSize( //图片宽度为屏幕宽度,高度为屏幕高度的一半
screenWidthPx.toInt(), <---------留意这儿简略错
(screenHeightPx / 2F).toInt() <---------留意这儿简略错
)
)
}
) { }
}
UI上显现的效果,图片被拉伸占满了一半的屏幕
Compose中提供了获取dp的办法,也把Dp作为参数传递,所以有些当地dp和px的运用简略混杂。
3、Modifier.fillMaxSize
Modifier.fillMaxSize
为占满父布局,占满父布局宽高度还有fillMaxWidth
和fillMaxHeight
。
4、Modifier.border、Modifier.padding
border用来为被润饰组件增加边框。边框能够指定色彩、粗细,以及经过Shape指定形状,比方圆角矩形等。padding用来为被润饰组件增加间隙。
@Composable
fun ShowImage() {
val avatarSize = 200.dp //头像的尺寸
Box(
modifier = Modifier
.border(
2.dp, //边框宽度
Color.Blue, //边框色彩
shape = RoundedCornerShape(avatarSize / 2)) //边框圆角,也能够给50表明50%
) {
Image(
painter = painterResource(id = R.mipmap.rabit2),
contentDescription = null,
modifier = Modifier
.size(avatarSize)
.padding(2.dp) //向外加一个padding的宽度,不遮挡头像
.clip(CircleShape)
)
}
}
UI效果
留意padding()办法不是覆盖联系,而是叠加联系,看下面的简略示例:
//用手机运转,试试修改下面的数字就能在手机上直接预览,而不用从头运转
Box(
modifier = Modifier
.background(Color.Black)
.padding(50.dp)
.border(10.dp, Color.Blue, shape = RoundedCornerShape(10.dp))
.padding(50.dp)
) {}
UI效果
同时要留意padding对background的影响。
相对于传统布局有Margin和Padding之分,Compose中只要padding这一种润饰符,概念愈加简洁。
5、Modifier.offset
组件偏移
Box(
modifier = Modifier
.size(150.dp)
.background(Color.Black) //布景黑色
.offset(15.dp,25.dp) //默许在原点,这儿设置一定的偏移量
.background(Color.Green) //布景绿色
) {}
UI效果
Modifier调用次序会影响最终UI出现的效果,先运用background设置布景,再运用offset润饰符偏移,再运用background制作布景,会发现出现的成果或许跟咱们想象中不一样。
6、Modifier.align
设置组件在父容器中的对齐办法。
Column(
modifier = Modifier
.background(Color.Green)
.fillMaxWidth()
.height(300.dp),
verticalArrangement = Arrangement.Center //父容器指定子组件的对齐办法为垂直方向上居中
) {
Text(
text = "Hello Android",
style = TextStyle(fontSize = 18.sp, color = Color.Black),
modifier = Modifier.align(Alignment.CenterHorizontally) //子组件设置在父容器中的对齐办法为水平居中
)
}
Tips: Compose中没有相似TextView的gravity="center"
的特点,假如想让文本居中只能包一层。
Box(
modifier = Modifier.size(200.dp),
contentAlignment = Alignment.Center
) {
Text(text = "Hello Android")
}
7、Modifier.aspectRatio
用于设置组件的宽高比。做图片适配就很简略,比方咱们想让图片宽度占满屏幕的宽度,宽高比为1:1,那么就有了如下的代码:
Image(
painter = painterResource(id = R.mipmap.rabit2),
contentDescription = null,
modifier = Modifier
.fillMaxWidth() //宽度为屏幕宽度
.aspectRatio(1 / 1F) //宽高比1:1
)
UI效果
8、Modifier.clickable
组件的点击事情
Box(
modifier = Modifier.clickable {
//点击事情
}
) { }
9、Modifier.clip
用于指定对组件进行取舍的形状。Modifier.clip
承受一个 Shape
参数,表明要取舍的形状。Compose
提供了多种内置的 Shape
类型,如 RoundedCornerShape(圆角矩形)
、CutCornerShape(裁剪角矩形)
、CircleShape(圆形)
等。
Modifier
.clip(RoundedCornerShape(15.dp)) //圆角
.clip(RoundedCornerShape(50)) //圆形,50为百分百
.clip(CircleShape) //上面的方便变量
.clip(CutCornerShape(topStart = 5.dp, topEnd = 5.dp, bottomStart = 2.dp, bottomEnd = 2.dp)) //指定每个角切开圆角
10、Modifier.focusRequester
用于在可交互的组件上恳求焦点。一般,这个润饰符用于处理键盘焦点和键盘输入的交互。能够经过创立一个 FocusRequester
目标来运用 Modifier.focusRequester
,然后将其传递给需求恳求焦点的组件上。
下面是一个有点复杂的比如,进入页面TextField(相当于View中的EditText)显现光标并弹出软键盘,点击按钮,去掉光标并躲藏软键盘。代码如下:
@OptIn(ExperimentalComposeUiApi::class)
@Composable
fun Test() {
//输入框输入的内容
var text by remember { mutableStateOf("") }
//切换焦点
val focusRequester = remember { FocusRequester() }
//焦点办理
val focusManager = LocalFocusManager.current
//键盘控制器
val keyboardController = LocalSoftwareKeyboardController.current
Column(modifier = Modifier.fillMaxSize()) {
//TextField:相当于View中的EditText
TextField(
value = text,
onValueChange = { text = it },
modifier = Modifier
.focusRequester(focusRequester) //获取焦点
.fillMaxWidth(),
keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
keyboardActions = KeyboardActions(
onDone = {
//点击键盘上承认按钮时躲藏键盘
keyboardController?.hide()
}
),
label = { Text(text = "进入页面获取焦点并弹出软键盘") }
)
//按钮
Button(
onClick = {
//铲除光标
focusManager.clearFocus()
//躲藏软键盘
keyboardController?.hide() //键盘上点击承认按钮时躲藏键盘
}, modifier = Modifier.padding(16.dp)
) {
Text("点击按钮铲除焦点并躲藏软键盘")
}
//界面重组异步回调
LaunchedEffect(key1 = Unit) {
focusRequester.requestFocus() //初次进入和重组页面恳求焦点
keyboardController?.show() //初次进入页面弹出键盘,留意必须先获取焦点才干弹出键盘成功
}
}
}
UI效果如下
11、Modifier.indication
用于指定组件(Clickable, Focusable, SemanticsPropertyProvider 等)的交互指示器。
交互指示器是一个视觉效果,当用户与组件进行交互(点击、获取焦点等)时,能够显现在组件周围,以提供反应和视觉指示。
看下面的一个比如:
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Blue)
.clickable { }
.indication(
interactionSource = remember { MutableInteractionSource() }, //创立一个交互源,用于盯梢用户与组件的交互
indication = rememberRipple() //用于界说组件的交互指示器效果
)
) {}
rememberRipple()
为系统提供的波纹指示器。点一下有水波纹效果,这个大家应该不生疏。
12、Modifier.layout
Modifier.layout
是一个很强壮的 Compose API,它能够以更精确的办法控制布局和方位。经过 Modifier.layout
,能够指定组件的方位和巨细,以及在父容器中的摆放办法。
Modifier.layout
承受一个 lambda 表达式,该表达式包含二个参数,其中measurable
包含了组件的测量信息。经过 Measurable
,能够获取组件的实践巨细并设置新的布局束缚。
val screenWidthDp = LocalConfiguration.current.screenWidthDp.dp //屏幕宽度的dp值
val screenHeightDp = LocalConfiguration.current.screenHeightDp.dp //屏幕高度的dp值
Box(
modifier = Modifier
.size(screenWidthDp / 2, screenHeightDp / 4)
.background(Color.LightGray)
) {
Text(
text = "Hello Android!",
style = TextStyle(fontSize = 18.sp, color = Color.White),
modifier = Modifier
.layout { measurable, constraints ->
// 获取组件的实践巨细
val placeable = measurable.measure(constraints)
// 设置新的布局束缚
layout(placeable.width, placeable.height) {
//指定组件在父布局中的方位x,y坐标
placeable.placeRelative(100, 100)
}
}
.background(Color.Blue)
)
}
UI效果
13、Modifier.draggable
单方向上的拖动手势。Google官网有个横向拖拽的比如(网址)
var offsetX by remember { mutableStateOf(0f) }
Text(
modifier = Modifier
.fillMaxSize()
.offset { IntOffset(offsetX.roundToInt(), 0) }
.draggable(
orientation = Orientation.Horizontal, //横向
state = rememberDraggableState { delta ->
offsetX += delta //一向重置偏移量的值
}
),
text = "Drag me!"
)
14、Modifier.pointerInput
用于监听组件的输入性事情,啥叫输入性事情,就是组件监听到的本身单击,双击,长按,拖拽等事情。detectTapGestures
监听点击类事情,detectDragGestures
监听拖拽类事情。
var tapped by remember { mutableStateOf(false) }
Box(
modifier = Modifier
.size(200.dp)
.background(if (tapped) Color.Blue else Color.Green)
.pointerInput(Unit) {
//监测接触事情
detectTapGestures(onTap = { offset -> //接触改变布景色
tapped = !tapped
})
//监测拖拽事情
detectDragGestures { pointerInputChange, offset ->
}
}
)
接触前
接触后
detectTapGestures
和detectDragGestures
的Api如下:
//按压事情
suspend fun PointerInputScope.detectTapGestures(
onDoubleTap: ((Offset) -> Unit)? = null, //双击
onLongPress: ((Offset) -> Unit)? = null, //长按
onPress: suspend PressGestureScope.(Offset) -> Unit = NoPressGesture, //按压
onTap: ((Offset) -> Unit)? = null //单击
) = coroutineScope {...}
//拖拽事情
suspend fun PointerInputScope.detectDragGestures(
onDragStart: (Offset) -> Unit = { }, //开始拖拽
onDragEnd: () -> Unit = { }, //结束拖拽
onDragCancel: () -> Unit = { }, //撤销拖拽
onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit //正在拖拽
) {...}
Google官网有个跟手拖拽的比如(网址)
Box(modifier = Modifier.fillMaxSize()) {
var offsetX by remember { mutableStateOf(0f) }
var offsetY by remember { mutableStateOf(0f) }
Box(
Modifier
.offset { IntOffset(offsetX.roundToInt(), offsetY.roundToInt()) }
.background(Color.Blue)
.size(50.dp)
.pointerInput(Unit) {
detectDragGestures { change, dragAmount ->
change.consume()
offsetX += dragAmount.x //修改偏移的x,y的量
offsetY += dragAmount.y
}
}
)
}
Gif效果图
15、Modifier.swiping
可滑动润饰符允许拖动元素,当释放时,这些元素一般会朝着一个方向中界说的两个或多个锚点移动。一个常见的用法是完成一个“滑动到闭幕”模式。
官网上有一个比如,我在这个比如上修改了一点代码,让它能在手机屏幕的宽度上左右滑动,也是为了进一步理解参数。
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun Greeting() {
val screenWidthDp = LocalConfiguration.current.screenWidthDp.dp
val squareSize = 48.dp
val swipeableState = rememberSwipeableState(0)
val screenWidthPx = with(LocalDensity.current) { screenWidthDp.toPx() } //核算像素值
val squareSizePx = with(LocalDensity.current) { squareSize.toPx() } //核算像素值
val anchors = mapOf(0f to 0, screenWidthPx - squareSizePx to 1) //界说二个锚点,滑块的最左最右的x坐标点
Box(
modifier = Modifier
.fillMaxWidth()
.swipeable(
state = swipeableState,
anchors = anchors,
thresholds = { _, _ -> FractionalThreshold(0.3f) }, //0.3为阈值,意思是不超越这个阈值就回弹,超越就滑向界说的下一个锚点
orientation = Orientation.Horizontal
)
.background(Color.LightGray)
) {
Box(
Modifier
.offset { IntOffset(swipeableState.offset.value.roundToInt(), 0) }
.size(squareSize)
.background(Color.DarkGray)
)
}
}
看GIF格式的UI图
16、Modifier.requiredSize、Modifier.requiredHeight、Modifier.requiredWidth
required
系列的Api表明必要的尺寸,中国风尺寸是强制性的,假如内容超越会被切断,不会自动适应扩展。
Box(
modifier = Modifier
.requiredHeight(38.dp)
.fillMaxWidth()
.background(Color.Green)
) {
Text(text = "测试超出高度".repeat(20))
}
UI效果
17、Modifier.rotate、Modifier.scale
rotate
旋转,scale
缩放
var rotationAngle by remember { mutableStateOf(0f) }
var scale by remember { mutableStateOf(1f) }
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier
.rotate(rotationAngle) //旋转
.scale(scale) //缩放
.clickable { //点击按钮旋转45度,缩小1倍
rotationAngle += 45f
scale /= 2
}
) {
Image(
painter = painterResource(id = R.mipmap.rabit2),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1 / 1F)
)
}
点击一次后的UI效果
18、Modifier.shadow
用于为 Compose 中的组件增加阴影效果。
Column(
modifier = Modifier
.fillMaxSize()
.padding(20.dp)
) {
Box(
modifier = Modifier
.size(200.dp)
.shadow(5.dp, shape = RoundedCornerShape(16.dp)) //阴影效果
.background(Color.White), //需求增加布景,不增加布景阴影效果会有问题
contentAlignment = Alignment.Center
) {
Text(
"Hello Android!",
style = TextStyle(fontSize = 16.sp, color = Color.Black)
)
}
}
增加布景的UI效果(图一)和不增加布景的UI效果(图二)
19、Modifier.weight
子组件在父组件的权重
Row(
modifier = Modifier
.fillMaxWidth()
.height(100.dp)
) {
Box(
modifier = Modifier
.background(Color.Blue)
.fillMaxHeight()
.weight(2f)
) {}
Box(
modifier = Modifier
.background(Color.Green)
.fillMaxHeight()
.weight(1f)
) {}
}
UI效果图
20、Modifier.zIndex
设置组件的层级次序,较大值的组件会制作在较小值的组件之上。下面的代码假如没有设置zIndex
,Button2
应该制作在Button1
之上,可是因为设置了zIndex
,Button1
制作在了Button2
之上。
Box(
modifier = Modifier.fillMaxWidth()
) {
Button(
onClick = { },
modifier = Modifier
.zIndex(2f)
) {
Text("Button1")
}
Button(
onClick = { },
modifier = Modifier
.zIndex(1f)
) {
Text("Button2")
}
}
UI效果
三、效果域限制Modifier润饰符
Compose
充分发挥了Kotlin
的语法特性,让某些Modifier
润饰符只能在特定效果域中运用,有利于类型安全地调用它们。所谓的“效果域”,在Kotlin
中就是一个带有Receiver
的代码块。例如Box
组件参数中的conent
就是一个Reciever
类型为BoxScope
的代码块,因此其子组件都处于BoxScope
效果域中。
@Composable
inline fun Box(
modifier: Modifier = Modifier,
contentAlignment: Alignment = Alignment.TopStart,
propagateMinConstraints: Boolean = false,
content: @Composable BoxScope.() -> Unit //BoxScope效果域
) {...}
需求留意Reciever
类型默许能够跨层级拜访。例如下面的比如中,bScope{...}
处于aScope{...}
内部,能够在bScope{...}
中拜访到属于aScope{...}
的办法methodFromAScope()
。
aScope{
bScope{
methodFromAScope() //aScope效果域的办法
}
}
在Compose
的DSL
中,一般只需求调用当时效果域的办法,像上面这样的Receiver
跨级拜访会成为写代码时的“噪声”,加大犯错的概率。Compose
考虑到了这个问题,能够经过@LayoutScopeMarker
注解来躲避Receiver
的跨级拜访。常用组件Receivier
效果域类型均已运用@LayoutScopeMarker
注解进行了声明。
//例如Column的效果域ColumnScope
Column() {...}
@LayoutScopeMarker <------注解
@Immutable
@JvmDefaultWithCompatibility
interface ColumnScope {...}
四、Modifier完成原理浅析
Modifier
会因为调用次序不同而产生出不同的Modifier
链,Compose
会依照Modifier
链来次序完成页面测量布局与烘托。那么Modifier
链是怎么被构建并解析的呢?
从源码中咱们发现Modifier
实践是一个接口。它有三个详细完成,分别是一个Modifier
伴生目标,Modifier. Element
以及CombinedModifier
。
@Suppress("ModifierFactoryExtensionFunction")
@Stable
@JvmDefaultWithCompatibility
interface Modifier {
fun <R> foldIn(initial: R, operation: (R, Element) -> R): R
fun <R> foldOut(initial: R, operation: (Element, R) -> R): R
fun any(predicate: (Element) -> Boolean): Boolean
fun all(predicate: (Element) -> Boolean): Boolean
infix fun then(other: Modifier): Modifier =
if (other === Modifier) this else CombinedModifier(this, other)
/**
* A single element contained within a [Modifier] chain.
*/
@JvmDefaultWithCompatibility
interface Element : Modifier {...} //Modifier. Element
...略...
// The companion object implements `Modifier` so that it may be used as the start of a
// modifier extension factory expression. //Modifier伴生目标,调用的起点
companion object : Modifier {
override fun <R> foldIn(initial: R, operation: (R, Element) -> R): R = initial
override fun <R> foldOut(initial: R, operation: (Element, R) -> R): R = initial
override fun any(predicate: (Element) -> Boolean): Boolean = false
override fun all(predicate: (Element) -> Boolean): Boolean = true
override infix fun then(other: Modifier): Modifier = other
override fun toString() = "Modifier"
}
}
class CombinedModifier( //CombinedModifier
internal val outer: Modifier,
internal val inner: Modifier
) : Modifier {...}
Modifier
伴生目标是咱们对Modifier
润饰符进行链式调用的起点,即Modifier.xxx()
中最初的那个Modifier
。CombinedModifier
用于衔接Modifier
链中的每个Modifier
目标。Modifier. Element
代表详细的润饰符。当咱们运用Modifier.xxx()
时,其内部实践上会创立一个Modifier
实例。以size
为例,其内部会创立SizeModifier
实例,并运用then
进行衔接。
@Stable
fun Modifier.size(size: Dp) = this.then(
//创立SizeModifier目标
SizeModifier(...)
)
//衔接不同的Modifier
infix fun then(other: Modifier): Modifier =
if (other === Modifier) this else CombinedModifier(this, other)
咱们创立的各种Modifier
本质上都是一个Modifier. Element
。像LayoutModifier
这类直接承继自Modifier. Element
的接口。
@JvmDefaultWithCompatibility
interface LayoutModifier : Modifier.Element {...}
其他的还有DrawModifier
、FocusEventModifier
、PointerInputModifier
等等,正是经过它们的组合形成了咱们的UI界面要素。
Compose
在制作UI
时,会遍历Modifier
链获取装备信息。Compose
运用foldOut()
与foldIn()
遍历Modifier
链,链上的一切节点被“折叠”成一个成果后,传入视图树用于烘托。
fun <R> foldIn(initial: R, operation: (R, Element) -> R): R
fun <R> foldOut(initial: R, operation: (Element, R) -> R): R
foldIn
和foldOut
的办法相同:initial
是折叠核算的初始值,operation
是详细核算办法。Element
参数表明当时遍历到的Modifier
,返回值也是R
类型,表明本轮核算的成果,会作为下一轮R
类型参数传入。folIn
和foldOut
的遍历次序有所不同,foldIn()
代表从正向遍历,而foldOut
是反向遍历。
学习笔记
作为初学者,不免有疏漏或错误,欢迎批评指正。文中部分内容参考了以下资料:
Jetpack Compose博物馆
实体书 Jetpack Compose从入门到实战