когда ленивый список перезагружает элементы (поскольку я запускаю некоторые изменения MutableStateFlow и поэтому элементы в моем списке изменяются), у меня возникает какой-то странный эффект мерцания / обрезки тени карточек (определяется через свойство ElevatedCard в TaskCard.kt).
Это определенно связано с animateItem() в модификаторе Box, инструкция
Box(modifier = Modifier.animateItem())
внутри MainScreen.kt , так как если я его удалю, мерцания не будет. Проблема в том, что мне нужен animateItem для анимации удаления карты из списка после пролистывания для удаления (вы можете увидеть это в другом моем вопросе)
В то же время animateItem дает мне странный артефакт мерцания при перезагрузке списка карточек, см. прикрепленный gif:

Есть ли решение этой проблемы?
код из моего приложения:
MainScreen.kt
@Composable
fun MainScreen(
tasksInWeek: TasksInPeriod,
selectedWeek: DateInterval,
selectedDate: LocalDate,
onDateEvent: (DateEvent) -> Unit,
onTaskEvent: (TaskEvent) -> Unit,
navigateTo: (route: Destinations) -> Unit,
) {
val locale = ConfigurationCompat.getLocales(LocalConfiguration.current).get(0)
val dates = selectedWeek.getDatesBetween()
Scaffold(
content = { paddingValues ->
Column(
modifier = Modifier.padding(
start = Constants.screenPadding,
end = Constants.screenPadding,
top = paddingValues.calculateTopPadding() + 10.dp,
bottom = paddingValues.calculateBottomPadding()
)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(9.dp),
modifier = Modifier.weight(1f)
) {
Text(
text = SimpleDateFormat("MMM", locale)
.format(
Date.from(
selectedDate.atStartOfDay(ZoneId.systemDefault()).toInstant()
)
)
.toString().uppercase(Locale.getDefault()),
fontWeight = FontWeight.SemiBold,
fontSize = 12.sp,
style = TextStyle(
platformStyle = PlatformTextStyle(
includeFontPadding = false
)
),
modifier = Modifier.rotate(-90f)
)
for (date in dates)
CircularDayProgressBar(
0f,
day = date,
isSelectedDay = date == selectedDate,
isCurrentDay = date == LocalDate.now(),
strokeColor = MaterialTheme.colorScheme.primary,
backgroundColor = MaterialTheme.colorScheme.surface,
textColor = MaterialTheme.colorScheme.outline,
onClick = { onDateEvent(DateEvent.ChangeSelectedDate(date)) }
)
}
Spacer(modifier = Modifier.weight(0.1f))
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.weight(1f)
) {
Text(
text = "1/10",
fontWeight = FontWeight.SemiBold,
fontSize = 16.sp,
)
Text(
text = stringResource(R.string.done_tasks),
fontWeight = FontWeight.Medium,
fontSize = 16.sp,
)
Spacer(modifier = Modifier.weight(1f))
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.width(100.dp)
) {
ElevatedButton(
onClick = { onDateEvent(DateEvent.ChangeToPrevWeek) },
modifier = Modifier.size(width = 40.dp, height = 30.dp),
contentPadding = PaddingValues(0.dp), shape =
RoundedCornerShape(8.dp),
elevation = ButtonDefaults.buttonElevation(defaultElevation = 2.dp)
) {
Icon(Icons.Filled.ChevronLeft, contentDescription = null)
}
Spacer(modifier = Modifier.weight(1f))
ElevatedButton(
onClick = { onDateEvent(DateEvent.ChangeToNextWeek) },
modifier = Modifier.size(width = 40.dp, height = 30.dp),
contentPadding = PaddingValues(0.dp),
shape = RoundedCornerShape(8.dp),
elevation = ButtonDefaults.buttonElevation(defaultElevation = 2.dp)
) { Icon(Icons.Filled.ChevronRight, contentDescription = null) }
}
}
Spacer(modifier = Modifier.weight(0.2f))
Box(
modifier = Modifier.weight(10f),
contentAlignment = Alignment.Center
) {
LazyColumn(
contentPadding = PaddingValues(bottom = 10.dp, top = 10.dp),
verticalArrangement = Arrangement.spacedBy(32.dp),
modifier = Modifier.fillMaxSize()
) {
items(
items = tasksInWeek.getForDate(selectedDate),
key = { it.taskId.toString() + it.date.toString() }
) { task ->
Box(modifier = Modifier.animateItem()) {
SwipeToDeleteContainer(
item = task,
onDelete = {
onTaskEvent(TaskEvent.DeleteTask(task))
}
) {
TaskCard(
taskCardDto = task,
onCheckedChange = { id, done ->
onTaskEvent(
TaskEvent.SetDone(
id,
done,
selectedDate
)
)
}
)
}
}
}
}
}
}
},
bottomBar = {
BottomAppBar(
modifier = Modifier.graphicsLayer { shadowElevation = 80f },
containerColor = MaterialTheme.colorScheme.background,
contentColor = MaterialTheme.colorScheme.outline,
actions = {
IconButton(onClick = { /* do something */ }) {
Icon(
Icons.Outlined.Settings,
contentDescription = "description"
)
}
IconButton(onClick = { /* do something */ }) {
Icon(
Icons.Outlined.Search,
contentDescription = "description",
)
}
},
floatingActionButton = {
FloatingActionButton(
onClick = {
navigateTo(Destinations.CreateTask)
},
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.background,
elevation = FloatingActionButtonDefaults.bottomAppBarFabElevation()
) {
Icon(Icons.Filled.Add, "description")
}
}
)
}
)
}
TaskCard.kt
@Composable
fun TaskCard(
taskCardDto: TaskCardDto,
onCheckedChange: (Long, Boolean) -> Unit
) {
ElevatedCard(
elevation = CardDefaults.cardElevation(
defaultElevation = 6.dp
),
modifier = Modifier
.height(70.dp)
.fillMaxWidth(),
shape = Constants.cardShape
) {
Row(
modifier = Modifier
.padding(vertical = 12.dp, horizontal = 20.dp)
.fillMaxWidth()
.fillMaxHeight(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Image(
painter = painterResource(id = R.drawable.postit),
contentDescription = "default task icon",
modifier = Modifier.weight(0.7f)
)
Text(
text = taskCardDto.title,
fontWeight = FontWeight.Medium,
style = if (taskCardDto.done) {
LocalTextStyle.current.copy(textDecoration = TextDecoration.LineThrough)
} else LocalTextStyle.current.copy(),
modifier = Modifier
.weight(4f)
.padding(horizontal = 20.dp)
)
RoundedCornerCheckbox(
isChecked = taskCardDto.done,
onValueChange = { isChecked -> onCheckedChange(taskCardDto.taskId, isChecked) },
checkedColor = MaterialTheme.colorScheme.primary,
uncheckedColor = Color.White
)
}
}
}
SwipeToDeleteContainer.kt
@Composable
fun SwipeToDeleteContainer(
item: T,
onDelete: (T) -> Unit,
content: @Composable (T) -> Unit
) {
val state = rememberSwipeToDismissBoxState(
initialValue = SwipeToDismissBoxValue.Settled,
positionalThreshold = { distance -> distance * 0.5f })
LaunchedEffect(state.currentValue) {
if (state.currentValue == SwipeToDismissBoxValue.EndToStart) {
onDelete(item)
}
}
SwipeToDismissBox(
state = state,
backgroundContent = {
DeleteBackground(swipeDismissState = state)
},
content = { content(item) },
enableDismissFromEndToStart = true,
enableDismissFromStartToEnd = false
)
}
@Composable
private fun DeleteBackground(
swipeDismissState: SwipeToDismissBoxState
) {
val color = if (swipeDismissState.dismissDirection == SwipeToDismissBoxValue.EndToStart) {
Color.Red
} else Color.Transparent
Box(
modifier = Modifier
.clip(Constants.cardShape)
.background(color)
.padding(16.dp)
.fillMaxSize(),
contentAlignment = Alignment.CenterEnd
) {
Icon(
imageVector = Icons.Default.Delete,
contentDescription = null,
tint = Color.White
)
}
}