K Merle
05/22/2023, 2:09 PMLazyColumn
and LazyRow
. They need to be synced, meaning, first item needs to be the same. I am having issues syncing their scrolls when vertical items have different height and am unable to figure out why. Anyone have an idea? 🧵@Composable
fun SyncedScrollScreen() {
val dates = generateDates()
val verticalScrollState = rememberLazyListState()
val horizontalScrollState = rememberLazyListState()
var scrollSource by remember { mutableStateOf<ScrollSource?>(null) }
val density = LocalDensity.current
val calendarItemHeight by remember {
derivedStateOf {
val firstVisibleColumnItemIndex = verticalScrollState.firstVisibleItemIndex
val firstVisibleColumnItem = verticalScrollState.layoutInfo.visibleItemsInfo
.firstOrNull { it.index == firstVisibleColumnItemIndex }
val firstVisibleColumnItemWidth = firstVisibleColumnItem?.size ?: 0
with(density) { firstVisibleColumnItemWidth.toDp() }
}
}
val timelineItemWidth by remember {
derivedStateOf {
val firstVisibleRowItemIndex = horizontalScrollState.firstVisibleItemIndex
val firstVisibleRowItem = horizontalScrollState.layoutInfo.visibleItemsInfo
.firstOrNull { it.index == firstVisibleRowItemIndex }
val firstVisibleRowItemWidth = firstVisibleRowItem?.size ?: 0
with(density) { firstVisibleRowItemWidth.toDp() }
}
}
LaunchedEffect(horizontalScrollState, scrollSource) {
if(scrollSource == ScrollSource.TIMELINE) {
snapshotFlow { horizontalScrollState.firstVisibleItemScrollOffset }
.collect { scrollOffset ->
val verticalScrollOffset = (scrollOffset * (calendarItemHeight / timelineItemWidth)).toInt()
verticalScrollState.scrollToItem(
horizontalScrollState.firstVisibleItemIndex,
verticalScrollOffset
)
}
}
}
LaunchedEffect(verticalScrollState, scrollSource) {
if(scrollSource == ScrollSource.CALENDAR) {
snapshotFlow { verticalScrollState.firstVisibleItemScrollOffset }
.collect { scrollOffset ->
val horizontalScrollOffset = (scrollOffset * (timelineItemWidth / calendarItemHeight)).toInt()
horizontalScrollState.scrollToItem(
verticalScrollState.firstVisibleItemIndex,
horizontalScrollOffset
)
}
}
}
LaunchedEffect(verticalScrollState.isScrollInProgress) {
if(scrollSource == null) {
scrollSource = ScrollSource.CALENDAR
} else if(!verticalScrollState.isScrollInProgress) {
scrollSource = null
}
}
LaunchedEffect(horizontalScrollState.isScrollInProgress) {
if(scrollSource == null) {
scrollSource = ScrollSource.TIMELINE
} else if(!horizontalScrollState.isScrollInProgress) {
scrollSource = null
}
}
Column(
modifier = Modifier.fillMaxSize().padding(top = 50.dp)
) {
LazyRow(
state = horizontalScrollState,
) {
itemsIndexed(dates, key= {index, key -> key}) { index, date ->
Box(modifier = Modifier
.width(300.dp)
.height(50.dp)) {
Text(date)
}
}
}
LazyColumn(
state = verticalScrollState
) {
itemsIndexed(dates, key= {index, key -> key}) { index, date ->
Box(
modifier = Modifier
.fillMaxWidth()
.height(if (index % 2 == 0) 90.dp else 200.dp)
.background(if (index % 2 == 0) Color.Gray else Color.White)
) {
Text(date)
}
}
}
}
}
fun generateDates(): List<String> {
val format = DateTimeFormatter.ofPattern("yyyy-MM-dd")
val startDate = LocalDate.now()
return List(30) { index ->
startDate.plusDays(index.toLong()).format(format)
}
}