初始值必须有关联的锚点 ModalBottomSheetLayout

Initial value must have an associated anchor ModalBottomSheetLayout

我创建了一个 ModalBottomSheetLayout 可组合项,它通过两种实现动态获取其内容: 第一个在 FAB 点击时打开底部 sheet:

    @OptIn(ExperimentalMaterialApi::class)
    private fun setupFabClickListener(arrayRolesResponse: Set<String>?) {
        binding.btnAdd.setOnClickListener {
            val arrayString = arrayLocalDate.map { item -> item.toString() }
            arrayString as ArrayList<String>

            if (arrayRolesResponse != null && "admin" in arrayRolesResponse) {
                mainViewModel.toggleBottomSheet()
            } else {
                getCalendarActivityIntent(this, true)
            }
        }
    }
    private var _openBottomSheet =
        MutableLiveData(ModalBottomSheetState(ModalBottomSheetValue.Hidden))
    val openBottomSheet: LiveData<ModalBottomSheetState> = _openBottomSheet

    fun toggleBottomSheet() {
        _openBottomSheet.postValue(
            if (_openBottomSheet.value?.currentValue == ModalBottomSheetValue.Hidden) {
                ModalBottomSheetState(initialValue = ModalBottomSheetValue.Expanded)
            } else {
                ModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
            }
        )
    }

它按预期工作,而第二个是这样实现的,抛出 IllegalArgumentExceptionThe initial value must have an associated anchor.

    @OptIn(ExperimentalMaterialApi::class)
    private fun setupCompose(date: String) {
        binding.composeView.setContent {
            val isLoading by viewModel.isLoaded.observeAsState()
            val reservations by viewModel.reservationsInDates.observeAsState()
            val modalBottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
            val coroutineScope = rememberCoroutineScope()

            ReservationsListPage(
                reservationsList = reservations?.data?.content,
                isLoading = isLoading,
                date = date,
                bottomSheetState = modalBottomSheetState
            ) { coroutineScope.launch { modalBottomSheetState.show() } }
        }
    }

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun ReservationsListPage(
    reservationsList: List<Content>?,
    isLoading: Boolean?,
    date: String,
    bottomSheetState: ModalBottomSheetState,
    onBottomSheetCall: () -> Unit
) {
    Column {
        AppBar(onBottomSheetCall)
        if (isLoading == true) {
            LoadingSpinner()
        } else {
            ReservationsListHeading(
                month = getAbbreviatedMonth(date.getMonth()),
                dayOfWeek = formatDayOfWeek(date.getDayOfWeek()),
                numberOfMonth = date.getDayOfMonth()
            )
            ReservationsList(reservationsList)
            BottomSheet(state = bottomSheetState) {
                ListItem() {
                    Text(text = "Show / Hide rejected reservations")
                }
            }
        }
    }
}

@Composable
fun ReservationsList(reservationsList: List<Content>?) {
    Column(
        modifier = Modifier.padding(16.dp)
    ) {
        LazyColumn(
            modifier = Modifier.fillMaxSize()
        ) {
            reservationsList?.forEach {
                item { ReservationsListItem(it) }
            }
        }
    }
}

@Composable
fun ReservationsListItem(reservation: Content) {
    val pending = painterResource(R.drawable.ic_pending)
    val accepted = painterResource(R.drawable.ic_accepted)
    val rejected = painterResource(R.drawable.ic_rejected)

    Card(
        shape = MaterialTheme.shapes.small,
        backgroundColor = lightWhite,
        modifier = Modifier.padding(top = 8.dp, bottom = 8.dp),
    ) {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(top = 8.dp, bottom = 8.dp, start = 16.dp, end = 16.dp),
            verticalAlignment = Alignment.CenterVertically,
            horizontalArrangement = Arrangement.SpaceBetween
        ) {
            Column(Modifier.weight(2f)) {
                Text(
                    text = "${reservation.seat?.room?.description}",
                    style = MaterialTheme.typography.h6
                )
                Text(
                    text = "${reservation.user?.username}",
                    style = MaterialTheme.typography.subtitle1
                )
            }
            Row(
                modifier = Modifier.weight(1f),
                horizontalArrangement = Arrangement.SpaceBetween
            ) {
                Text(
                    text = "${reservation.seat?.description}",
                    style = MaterialTheme.typography.subtitle1,
                )
                Icon(
                    painter = when (reservation.reservationStatus?.status) {
                        "ACCEPTED" -> accepted
                        "REJECTED" -> rejected
                        else -> pending
                    },
                    contentDescription = "Reservation status",
                    tint = Color.Unspecified,
                )
            }
        }
    }
}

@Composable
fun AppBar(onBottomSheetCall: () -> Unit) {
    var isMenuExpanded by remember { mutableStateOf(false) }

    Column() {
        TopAppBar(
            title = { Text(text = "Reservations", color = Color.White) },
            actions = {
                IconButton(onClick = { isMenuExpanded = true }) {
                    Icon(
                        imageVector = Icons.Default.MoreVert,
                        contentDescription = "More options",
                        tint = Color.White
                    )
                }
                AppBarDropdownMenu(
                    isMenuExpanded = isMenuExpanded,
                    onBottomSheetCall = onBottomSheetCall,
                    onDismissRequest = { isMenuExpanded = false }
                )
            },
            backgroundColor = primary,
        )
    }
}

@Composable
@OptIn(ExperimentalMaterialApi::class)
fun AppBarDropdownMenu(
    isMenuExpanded: Boolean,
    onDismissRequest: () -> Unit,
    onBottomSheetCall: () -> Unit
) {
    DropdownMenu(expanded = isMenuExpanded, onDismissRequest = onDismissRequest) {
        DropdownMenuItem(onClick = { /*TODO*/ }) {
            ListItem(
                icon = { Icon(painterResource(R.drawable.ic_sort), "Sort") }
            ) { Text(text = "Sort", style = MaterialTheme.typography.body1) }
        }
        Divider()
        DropdownMenuItem(onClick = onBottomSheetCall) {
            ListItem(
                icon = { Icon(painterResource(R.drawable.ic_filter), "Filter") }
            ) { Text(text = "Filter", style = MaterialTheme.typography.body1) }
        }
    }
}

@Composable
fun ReservationsListHeading(month: String, dayOfWeek: String, numberOfMonth: String) {
    Row(
        modifier = Modifier
            .fillMaxWidth()
            .padding(top = 16.dp, start = 16.dp, end = 16.dp),
        verticalAlignment = Alignment.CenterVertically
    ) {
        HeadingLeft(month = month, dayOfWeek = dayOfWeek, numberOfMonth = numberOfMonth)
    }
}

@Composable
fun HeadingLeft(month: String, dayOfWeek: String, numberOfMonth: String) {
    Row(
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Text(text = month, style = MaterialTheme.typography.h3)
        Column(
            modifier = Modifier.padding(8.dp)
        ) {
            Text(text = dayOfWeek, style = MaterialTheme.typography.body1)
            Text(text = numberOfMonth, style = MaterialTheme.typography.subtitle1)
        }
    }
}

这是我对 ModalBottomSheetLayout 的实现。

@Composable
@OptIn(ExperimentalMaterialApi::class)
fun BottomSheet(
    state: ModalBottomSheetState,
    content: @Composable () -> Unit
) {
    ModalBottomSheetLayout(
        sheetContent = {
            Column(
                Modifier
                    .fillMaxWidth()
                    .padding(top = 8.dp, bottom = 8.dp)
            ) {
                content()
            }
        },
        sheetElevation = 0.dp,
        sheetState = state,
    ) {}
}

知道为什么会这样吗?一般来说,我在 SO 或 google 上真的找不到太多东西。

已解决

我完全错过了可选参数 content: (@Composable () -> Unit)? 根据 androidx.compose 文档是 The content of rest of the screen. 我通过将 BottomSheet 可组合项放在函数的开头并将视图的其余部分作为 content 参数来解决这个问题。

fun ReservationsListPage(
    reservationsList: ArrayList<Content>,
    isLoading: Boolean?,
    date: String,
    bottomSheetState: ModalBottomSheetState,
) {
    Column {
        BottomSheet(
            pageContent = {
                Column {
                    AppBar(bottomSheetState)
                    if (isLoading == true) {
                        LoadingSpinner()
                    } else {
                        ReservationsListHeading(
                            month = getAbbreviatedMonth(date.getMonth()),
                            dayOfWeek = formatDayOfWeek(date.getDayOfWeek()),
                            numberOfMonth = date.getDayOfMonth()
                        )
                        ReservationsList(reservationsList)
                    }
                }
            },
            content = {
                Column {
                    Text("Hello")
                }
            },
            state = bottomSheetState,
        )
    }
}