初始值必须有关联的锚点 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)
}
)
}
它按预期工作,而第二个是这样实现的,抛出 IllegalArgumentException
和 The 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,
)
}
}
我创建了一个 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)
}
)
}
它按预期工作,而第二个是这样实现的,抛出 IllegalArgumentException
和 The 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,
)
}
}