Compose 学习记录

142 阅读10分钟

要求

studio版本不能太低,要求是 Android Studio Arctic Fox

然后最简单的就是创建一个新的工程,模板选择Empty Compose Activity

如果是旧的工程,需要添加的依赖如下所示 参考:developer.android.com/jetpack/com…

dependencies {
    implementation("androidx.compose.ui:ui:1.0.5")
    // Tooling support (Previews, etc.)
    implementation("androidx.compose.ui:ui-tooling:1.0.5")
    // Foundation (Border, Background, Box, Image, Scroll, shapes, animations, etc.)
    implementation("androidx.compose.foundation:foundation:1.0.5")
    // Material Design
    implementation("androidx.compose.material:material:1.0.5")
    // Material design icons
    implementation("androidx.compose.material:material-icons-core:1.0.5")
    implementation("androidx.compose.material:material-icons-extended:1.0.5")
    // Integration with observables
    implementation("androidx.compose.runtime:runtime-livedata:1.0.5")
    implementation("androidx.compose.runtime:runtime-rxjava2:1.0.5")

    // UI Tests
    androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.0.5")
}

详细的需求可以看这里,developer.android.com/jetpack/com… 其实最简单的就是新建个工程了。。。

注意这个库

implementation 'androidx.activity:activity-compose:1.3.0-alpha06'

demo里的setContent用到这个库了。。。

简单记录下gradle里的需求

android {
    defaultConfig {
        ...
        minSdkVersion 21
    }

    buildFeatures {
        // Enables Jetpack Compose for this module
        compose true
    }
    ...

    // Set both the Java and Kotlin compilers to target Java 8.
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }

    composeOptions {
        kotlinCompilerExtensionVersion '1.0.5'
    }
}

这里只研究纯component的用法,不研究那种混合用法了 继承ComponentActivity 然后setContent里就可以写要显示的控件了

class ComponentLearnActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
//        setContent {
//            MessageCard(name = "card...card")
//        }
    }

预览有下边两种

方法不带参数的话,直接添加@composable和@preview就行了。 如果方法带参数,参数有默认值,那么直接@preview也可以了 如果参数没有默认值,如果想预览,必须添加@previewparameter的注解来给定默认参数,否则无法预览,如下

@Preview(showBackground = true)
@Composable
fun MessageCard(name: String="xxxxxxx") {
//    Text(text = "Hello $name!")
    LableText(lable = "11111111",lable2 = "world")
}


@Preview(showBackground = false)
@Composable
fun LableText(@PreviewParameter(provider = Provider::class) lable:String,lable2:String="222222"){
    Text(text = "$lable&&&$lable2")
}

class Provider(override val values: Sequence<String> = sequenceOf("hello")) :PreviewParameterProvider<String>{}

变量改变自动刷新的问题

我们需要一个变量,带状态的变量,通过by remember 返回一个mutableStateOf 如下点击column txtColor颜色变化以后,Text的颜色自动就变了。

import androidx.compose.runtime.remember
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue

var txtColor  by remember { mutableStateOf(Color.Black) }
Column(Modifier.clickable {
    txtColor = if (txtColor == Color.Black) Color.Red else Color.Black
}) {

    Text(text = "Hello $name!", color = txtColor,)
  
}

这个remember在页面旋转或者配置改变的时候,还是会销毁的,那么咋办? 还有一个可用的rememberSaveable

var expand by rememberSaveable {
    mutableStateOf(false)
}

编程思想

可组合函数可以按任何顺序执行

动态变量

如下,当select变化的时候,card的圆角会动态的从15变成0

val radius by animateDpAsState(targetValue =if(selected) 15.dp else 0.dp )
Card (shape = RoundedCornerShape(topStart = radius)){

当然了还有一些类似的animateIntAsState,animateSizeAsState... 简单看下源码

fun animateDpAsState(
    targetValue: Dp,
    animationSpec: AnimationSpec<Dp> = dpDefaultSpring,
    finishedListener: ((Dp) -> Unit)? = null
): State<Dp> {
    return animateValueAsState(
        targetValue,
        Dp.VectorConverter,
        animationSpec,
        finishedListener = finishedListener
    )
}

可以看到还有另外两个默认参数

animationSpec:可以自定义动画效果

finishedListener:动画结束的回调 看下第二个参数

fun <T> spring(
    dampingRatio: Float = Spring.DampingRatioNoBouncy,
    stiffness: Float = Spring.StiffnessMedium,
    visibilityThreshold: T? = null
): SpringSpec<T> =
    SpringSpec(dampingRatio, stiffness, visibilityThreshold)

可以看下Spring定义的几个默认值,无回弹,回弹幅度小,中,大。 以及阻尼系数

自定义布局

简单概括下流程 就是返回一个Layout,我们用这个

@Composable inline fun Layout(
    content: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    measurePolicy: MeasurePolicy
)

measurePolicy是个接口, 如下

Layout(content,modifier,measurePolicy = object :MeasurePolicy{
    override fun MeasureScope.measure(
        measurables: List<Measurable>,
        constraints: Constraints
    ): MeasureResult {

    }
})

可以简化写成如下的

Layout(content,modifier,measurePolicy = MeasurePolicy { measurables, constraints -> })

其实我们就是要返回一个MeasureResult

可能用到的方法

val placeable=measurable.measure(constraints) //获取测量后的信息,可以拿到child的宽高等信息

最后返回一个

layout(width ,height){//整个容器的宽高
    val rowX=IntArray(rows){0}
    placeables.forEachIndexed { index, placeable ->
        
        placeable.placeRelative(x,y)//循环放置每个child
        
    }
}

wrap效果

Row(modifier = modifier.height(IntrinsicSize.Min)) 

//上边的row限制,下边这个divier就不会铺满屏幕了。
Divider(color = Color.Black, modifier = Modifier.fillMaxHeight().width(1.dp))

key可组合项

@Composable
fun MoviesScreen(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            key(movie.id) { // Unique ID for this movie
                MovieOverview(movie)
            }
        }
    }
}
@Composable
fun MoviesScreen(movies: List<Movie>) {
    LazyColumn {
        items(movies, key = { movie -> movie.id }) { movie ->
            MovieOverview(movie)
        }
    }
}

如果您希望可组合项的尺寸固定不变,而不考虑传入的约束条件,请使用 requiredSize 修饰符:

固有特性测量

Row(modifier = modifier.height(IntrinsicSize.Min))

image.png

tabrow

image.png 源码

@Composable
fun TabRow(
    selectedTabIndex: Int,
    modifier: Modifier = Modifier,
    backgroundColor: Color = MaterialTheme.colors.primarySurface,
    contentColor: Color = contentColorFor(backgroundColor),
    indicator: @Composable (tabPositions: List<TabPosition>) -> Unit = @Composable { tabPositions ->
        TabRowDefaults.Indicator(
            Modifier.tabIndicatorOffset(tabPositions[selectedTabIndex])
        )
    },
    divider: @Composable () -> Unit = @Composable {
        TabRowDefaults.Divider()
    },
    tabs: @Composable () -> Unit
) 

参数说明:

selectedTabIndex :选中的tab索引

backgroundColor:tab的背景颜色

contentColor:内容颜色,也是默认indicator的颜色,

indicator:指示器,就是选中的tab上的图标,如图上的红圈

divider:最下边那条黄线,用来区分tab和tab下边的内容的

indicator和divider都有默认值,我们可以自定义修改

TabRow(
    selectedTabIndex = selectIndex,
    indicator = { tabPositions ->
        Box (Modifier
            .tabIndicatorOffset(tabPositions[selectIndex])
            .fillMaxSize()
            .padding(4.dp)
            .border(BorderStroke(2.dp, Color.Red), CircleShape)
        )
    },
    divider = {
        androidx.compose.material.Divider(modifier = Modifier, thickness = 5.dp, color = Color.Yellow)
    }
) {
    listOf("first", "sec", "third").forEachIndexed { index, s ->

        Tab(selected = selectIndex == index,
            onClick = { selectIndex = index },
            modifier = Modifier
                .padding(4.dp)
                .clip(CircleShape)) {
            Text(text = s, modifier = Modifier.padding(8.dp, 16.dp))
        }
    }
}

顺道复习下

val CircleShape = RoundedCornerShape(50)

RoundedCornerShape 参数可以是dp,float的像素,或者int类型表示百分比(0到100) 当然也可以单独设置4个角

简单说明下上边的contentColor为啥会影响默认的indicator线条的颜色 TabRow点进去可以看到如下的代码
里边设置了LocalContentColor的值为contentColor

CompositionLocalProvider(
    LocalContentColor provides contentColor,
    LocalAbsoluteElevation provides absoluteElevation
) {
Box(){
content()}

然后我们看下默认的TabRowDefaults.Indicator的代码,可以看到background用的就是LocalContentColor.current

@Composable
fun Indicator(
    modifier: Modifier = Modifier,
    height: Dp = IndicatorHeight,
    color: Color = LocalContentColor.current
) {
    Box(
        modifier
            .fillMaxWidth()
            .height(height)
            .background(color = color)
    )
}

BackdropScaffold

就是android view里的bottomsheet那个玩意,抽屉效果那种

fun BackdropScaffold(
    appBar: @Composable () -> Unit,
    backLayerContent: @Composable () -> Unit,
    frontLayerContent: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    scaffoldState: BackdropScaffoldState = rememberBackdropScaffoldState(Concealed),
    gesturesEnabled: Boolean = true,
    peekHeight: Dp = BackdropScaffoldDefaults.PeekHeight,
    headerHeight: Dp = BackdropScaffoldDefaults.HeaderHeight,
    persistentAppBar: Boolean = true,
    stickyFrontLayer: Boolean = true,
    backLayerBackgroundColor: Color = MaterialTheme.colors.primary,
    backLayerContentColor: Color = contentColorFor(backLayerBackgroundColor),
    frontLayerShape: Shape = BackdropScaffoldDefaults.frontLayerShape,
    frontLayerElevation: Dp = BackdropScaffoldDefaults.FrontLayerElevation,
    frontLayerBackgroundColor: Color = MaterialTheme.colors.surface,
    frontLayerContentColor: Color = contentColorFor(frontLayerBackgroundColor),
    frontLayerScrimColor: Color = BackdropScaffoldDefaults.frontLayerScrimColor,
    snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) }
)

appbar:这个算底层内容,显示在底层顶部

backLayerContent:底层的内容,显示在appbar下边的

frontLayerContent:这个是最上层显示用的

scaffoldState:底层显示与否,枚举类,这里设置的是默认值,可以默认显示底层或者默认不显示底层,后边可以滑动改变状态

peekHeight:这个是底层内容最小显示的高度,防止上层滑动把底层全部盖住

headerHeight:这个是上层内容显示的最小高度,你要是弄个0,生成可能会完全划出屏幕看不见了。

snackbarHost:就是之前android里的那个底部提示框,这里可以看到已经设置了默认值了,如果你对颜色形状啥的不喜欢可以自定义

比如,如下自定义snackbarhost

{

    SnackbarHost(hostState = it){
        Snackbar(snackbarData = it, backgroundColor = Color.Blue, contentColor = contentColorFor(backgroundColor = Color.Blue))
    }
}

使用如下,我们只需要点击某个item的时候修改下showContent的内容就行了

val state=rememberBackdropScaffoldState(BackdropValue.Revealed)
var showContent by remember {
    mutableStateOf("")
}
LaunchedEffect(showContent ){
    launch {
        if(showContent.isEmpty())return@launch
        state.snackbarHostState.showSnackbar(showContent)
    }
}

canvas

这个东西,如果modifier你不设置高度,那么它是不占高度的,画出来的东西在底层, 比如

column{
canvas// 画个圆
text//你会发现text改在canvad那个圆上边
}

textfiled

文本输入框 下边是个sample,里边添加了自动显示输入法,并且按下done键的时候取消焦点以便隐藏输入法

val (text, setText) = remember { mutableStateOf("") }

val keyboardController = LocalSoftwareKeyboardController.current//这个可以调用show,hidden显示隐藏输入法,此处没有用到。

val focusRequest= remember {
    FocusRequester()
}
val focusManager= LocalFocusManager.current//焦点管理器
OutlinedTextField(

    value = text,
    onValueChange = setText,

    colors = TextFieldDefaults.textFieldColors(backgroundColor = Color.Transparent),
    maxLines = 1,
    singleLine=true,
    keyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
    keyboardActions = KeyboardActions(onDone = {
        focusManager.clearFocus()//清除焦点,输入法自动隐藏
    }),
    modifier = Modifier
        .focusRequester(focusRequest)
        .fillMaxWidth()
        .height(58.dp)
        .constrainAs(edit) {
            top.linkTo(con.bottom, 10.dp)
        },
    label = { Text(text = "lable") },
    placeholder = { Text(text = "place holder..",color= Color.Blue) },
    leadingIcon = {
        Icon(painter = painterResource(id = R.drawable.ic_baseline_clear_24),
            contentDescription = "leading..", tint = Color.Red)
    },
    trailingIcon = { Text(text = "clear words",color= Color.Red, modifier = Modifier.clickable { setText.invoke("") }) },
)

LaunchedEffect(key1 = focusRequest, block = {
    focusRequest.requestFocus()
})

下边单独把默认获取焦点的方法拉出来

val focusRequest= remember {
    FocusRequester() //第一步
}
val focusManager= LocalFocusManager.current//焦点管理器,用来清除focus,以便隐藏输入法
 Modifier
        .focusRequester(focusRequest)//第二步
        
LaunchedEffect(key1 = focusRequest, block = {
    focusRequest.requestFocus()//第三步,这个请求得放在launch里,
})

还有注意labe,placeholder那几个参数的类型都是代码块,记得用花括号括起来。

option简单说明

class KeyboardOptions constructor(
    val capitalization: KeyboardCapitalization = KeyboardCapitalization.None,
    val autoCorrect: Boolean = true,
    val keyboardType: KeyboardType = KeyboardType.Text,//还可以选择number选择数字输入键盘
    val imeAction: ImeAction = ImeAction.Default
) 

还有个参数忘了写了,这个就是用来对输入的文本内容进行转换的。字面意思 视觉上的转换,也就是文本框里实际显示的内容

visualTransformation= VisualTransformation { text-> TransformedText(text.toUpperCase(), OffsetMapping.Identity) },

text的类型是AnnotatedString,这个类里扩展了不少已经写好的方法,比如上边的就是字母大写 还有如下的,首字母大写,首字母小写,全部小写

fun AnnotatedString.toUpperCase
fun AnnotatedString.toLowerCase
fun AnnotatedString.capitalize
fun AnnotatedString.decapitalize

比如密码输入框,我们需要把文本内容显示成星号,如下

visualTransformation = VisualTransformation { text ->
    TransformedText(buildAnnotatedString {
        this.append("*".repeat(text.length))
    }, OffsetMapping.Identity)
},

还有电话号码,如果你想加个横杠啥的,都在这里处理,text是原本输入的内容,自己处理后返回一个另外的text用来显示即可

看些这个类的源码,可以发现它还有其他一些属性可以设置的,感觉都是一些样式,可以单独设置部分文字的样式啥的,没做研究。

class AnnotatedString internal constructor(
    val text: String,
    val spanStyles: List<Range<SpanStyle>> = emptyList(),
    val paragraphStyles: List<Range<ParagraphStyle>> = emptyList(),
    internal val annotations: List<Range<out Any>> = emptyList()
) 

一些简单的动画效果

也不是动画,就是状态变化有个过程,比如宽度从0到100,有个渐变的过程

  1. Animatable animateTo 这个需要放在一个launch里,我们用LaunchEffect即可,监听click变化触发
var we = remember {
    Animatable(1f)
}

var click by remember {
    mutableStateOf(0)
}
LaunchedEffect(key1 = click, block = {
    if(click>0)
    we.animateTo(if(we.value==1f) 2f else 1f, animationSpec = tween(1111))
})


Modifier.clickable {
           click++
}

  1. transition
val transition = rememberInfiniteTransition()
val alpha = transition.animateFloat(initialValue = 0f,
    targetValue = 1f,
    animationSpec = infiniteRepeatable(keyframes {
        durationMillis = 1000
        0.7f at 500
    }, RepeatMode.Reverse))
  1. animatexxxAsState 这个有好几个,可以是dp,float,int,color,还可以是某种type

如下当变量select变化的时候,我们的radius也会动态变化了

val radius by animateDpAsState(targetValue =if(selected) 15.dp else 0.dp )

异步执行

下边这个协成可以异步执行一些task,而且他本身和组件是绑定的,组件重组的时候它也会取消

val scope= rememberCoroutineScope()

下边这个是个compose,所以组件重组的时候会执行,key发生变化的时候也会重新执行block。

这个多用来监听key的变化而重复执行block块

@Composable
@NonRestartableComposable
@OptIn(InternalComposeApi::class)
fun LaunchedEffect(
    key1: Any?,
    block: suspend CoroutineScope.() -> Unit
)

nav 添加可选参数

  • 可选参数必须使用查询参数语法 ("?argName={argName}") 来添加
  • 可选参数必须具有 defaultValue 集或 nullability = true(将默认值隐式设置为 null
composable(
    "profile?userId={userId}",
    arguments = listOf(navArgument("userId") { defaultValue = "me" })
) { backStackEntry ->
    Profile(backStackEntry.arguments?.getString("userId")) { friendUserId ->
        navController.navigate("profile?userId=$friendUserId")
    }
}

deeplink的使用demo

从外部打开app的对应页面

<activity
    android:name=".RallyActivity"
    android:windowSoftInputMode="adjustResize"
    android:label="@string/app_name"
    android:exported="true">//这个为true,其他app才能调用打开我们的app
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="rally" android:host="accounts" />//这个设置scheme和host
    </intent-filter>
</activity>
  1. navhost的代码
NavHost(...){
composable("a"){}
//...

composable(
    route = "accounts/{name}",
    arguments = listOf(//arguments参数非必须
        navArgument("name") {
            type = NavType.StringType
        }
    ),
    deepLinks = listOf(
        navDeepLink {
            uriPattern = "rally://accounts/{name}"
        }
    ),
) { entry ->
    val accountName = entry.arguments?.getString("name")
  
}

}

命令行执行adb命令就可以测试了,可以正常打开上边的composable了

adb shell am start -d "rally://accounts/Checking" -a android.intent.action.VIEW

代码跳转

val intent= Intent(Intent.ACTION_VIEW)
intent.data="rally://accounts/test2222".toUri()
context.startActivity(intent)

LaunchedEffect 和 DisposableEffect

两者的区别,后者多了一个onDispose{}的东西,这个东西是在Effect组件销毁的时候才会执行,首次进去是不会执行的,只有当比如key发生了变化要重组了,那么onDispose里的代码会执行,或者其他原因【比如页面切换,退出页面等】引起的组件被销毁的时候才会执行

var count by remember {
    mutableStateOf(0)
}
LaunchedEffect(key1 = count, block = {
    println("launched effect============1")
})
DisposableEffect(key1 = count, effect ={
    println("disposable effect=============1")
    onDispose {
        println("on dispose============1")
    }
} )

Button(onClick = { count++ }) {
    Text(text = "click$count")
}

还有一个SideEffect 这个了就是每次重组都会执行,这里的重组感觉就是它自己同级别的发生变化。

SideEffect {
    println("side effect==========")
}

带drawer的组件 Scaffold ,ModalDrawer

scaffold可以用到的compose比较多,topbar,bottombar,floatactionbutton,snackbar等

fun Scaffold(
    modifier: Modifier = Modifier,
    scaffoldState: ScaffoldState = rememberScaffoldState(),
    topBar: @Composable () -> Unit = {},
    bottomBar: @Composable () -> Unit = {},
    snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
    floatingActionButton: @Composable () -> Unit = {},
    floatingActionButtonPosition: FabPosition = FabPosition.End,
    isFloatingActionButtonDocked: Boolean = false,
    drawerContent: @Composable (ColumnScope.() -> Unit)? = null,
    drawerGesturesEnabled: Boolean = true,
    drawerShape: Shape = MaterialTheme.shapes.large,
    drawerElevation: Dp = DrawerDefaults.Elevation,
    drawerBackgroundColor: Color = MaterialTheme.colors.surface,
    drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
    drawerScrimColor: Color = DrawerDefaults.scrimColor,
    backgroundColor: Color = MaterialTheme.colors.background,
    contentColor: Color = contentColorFor(backgroundColor),
    content: @Composable (PaddingValues) -> Unit
) 

modalDrawer 里的compose就两部分,drawer和content

fun ModalDrawer(
    drawerContent: @Composable ColumnScope.() -> Unit,
    modifier: Modifier = Modifier,
    drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
    gesturesEnabled: Boolean = true,
    drawerShape: Shape = MaterialTheme.shapes.large,
    drawerElevation: Dp = DrawerDefaults.Elevation,
    drawerBackgroundColor: Color = MaterialTheme.colors.surface,
    drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
    scrimColor: Color = DrawerDefaults.scrimColor,
    content: @Composable () -> Unit
)

Icon , Image

fun Icon(
    painter: Painter,
    contentDescription: String?,
    modifier: Modifier = Modifier,
    tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
){

Box(
    modifier.toolingGraphicsLayer().defaultSizeFor(painter)
        .paint(
            painter,
            colorFilter = colorFilter,
            contentScale = ContentScale.Fit
        )
        .then(semantics)
)
}

两者都是通过Modifier的paint方法来绘制的,Image还多了3个参数可以设定
alignment: Alignment = Alignment.Center,
contentScale: ContentScale = ContentScale.Fit,
alpha: Float = DefaultAlpha,

@Composable
fun Image(
    painter: Painter,
    contentDescription: String?,
    modifier: Modifier = Modifier,
    alignment: Alignment = Alignment.Center,
    contentScale: ContentScale = ContentScale.Fit,
    alpha: Float = DefaultAlpha,
    colorFilter: ColorFilter? = null
) {
    // Explicitly use a simple Layout implementation here as Spacer squashes any non fixed
    // constraint with zero
    Layout(
        {},
        modifier.then(semantics).clipToBounds().paint(
            painter,
            alignment = alignment,
            contentScale = contentScale,
            alpha = alpha,
            colorFilter = colorFilter
        )
    ) { _, constraints ->
        layout(constraints.minWidth, constraints.minHeight) {}
    }
}

IconToggleButton

An IconButton with two states, for icons that can be toggled 'on' and 'off', such as a bookmark icon, or a navigation icon that opens a drawer

var favorite by remember {
    mutableStateOf(false)
}
IconToggleButton(checked = favorite, onCheckedChange ={
    favorite=!favorite
} ) {
    Icon(imageVector = if (favorite) Icons.Filled.AlarmOn else Icons.Filled.AlarmOff,
        contentDescription = "")
}

IconButton

默认的ripple范围是48*48dp,里边icon的大小是24*24dp
一般用在topbar上的action按钮,或者类似的地方,当然你可以通过modifier修改button的大小,,不过你会发现那个ripple的大小就是48dp的范围,看起来就很奇怪了。

fun IconButton(
    onClick: () -> Unit,
    modifier: Modifier = Modifier,
    enabled: Boolean = true,
    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
    content: @Composable () -> Unit
)

解构声明

在 Koltin 中可以把一个对象赋值给多个变量,这种操作叫做解构声明(Destructuring declaration)
operator 运算符重载,就是对已有的方法进行重载【赋予新的含义】
MutableState这个经常要用到

interface MutableState<T> : State<T> {
    override var value: T
    operator fun component1(): T
    operator fun component2(): (T) -> Unit
}

常用的

var show by remember {
    mutableStateOf(false)
}

然后看demo发现了这个,哎,咋还可以有两个变量在括号里

val (a, b) = rememberSaveable {
    mutableStateOf(section)
}

我以为括号里随便加了,弄3个发现就报错了,然后查了下,发现是一种解构声明的东西。
如上MutableState里,component1() ,component2() ,其实括号里里(a,b) 对应的就是这个两个方法 顺道看下源码里这两个重载方法的实现

override operator fun component1(): T = value

override operator fun component2(): (T) -> Unit = { value = it }

component后边可以跟任意数字,你就可以重载多个方法了

常见的component的用法 data class

data class City(val name: String, val country: String)

反编译以后就能看到

@NotNull
public final String component1() {
   return this.name;
}

@NotNull
public final String component2() {
   return this.country;
}