I have a row that contains a lazycolumn and a custom layout. The lazy column contains the hours and a custom layout containing events positioned. They have different scroll states, so they won't scroll together. How can I fix this. Cause I need a lazycolumnn to track the day I am in using the layout info.
@Composable
fun ScheduleSidebar(
hourHeight: Dp = 64.dp){
LazyColumn(
modifier = modifier,
) {
val startTime = LocalTime.MIN
items(168) { hour ->
Box(modifier = Modifier.height(hourHeight)) {
Text(startTime.plusHours(hour.toLong()))
}
}
}
This is my view:
@Composable
fun ScheduleView(
viewModel: ScheduleViewModel = getViewModel()
) {
val verticalScrollState = rememberScrollState()
val ss = rememberLazyListState()
val scrollConnection = rememberNestedScrollInteropConnection()
//added this and still the lazycolumn can scroll alone
LaunchedEffect(key1 = Unit) {
var oldValue = 0
snapshotFlow {
verticalScrollState.value
}.collect {
println("Value: $it")
ss.scrollBy((it-oldValue).toFloat())
oldValue = it
}
}
Column(modifier = Modifier.fillMaxSize()) {
ScheduleHeader()
Row(
modifier = Modifier
.weight(1f)
) {
ScheduleSidebar(
hourHeight = 64.dp,
modifier = Modifier
,
)
Schedule(
bookings = viewModel.state.value.bookingsList,
modifier = Modifier
.verticalScroll(verticalScrollState),
onEmptyViewClicked = { days, hour, selectedDate ->
showSheet = true
viewModel.onEvent(
ScheduleEvent.OnEmptyViewClicked(
days = days,
startTime = hour,
selectedDate = selectedDate
)
)
}
)
}
}
}
You can use
val state = rememberScrollState()
val lazyListState = rememberLazyListState()
LaunchedEffect(key1 = Unit) {
var oldValue = 0
snapshotFlow {
state.value
}.collect {
println("Value: $it")
lazyListState.scrollBy((it -oldValue).toFloat())
oldValue = it
}
}
to scroll LazyColumn when Column with vertical scroll is scrolled.
Since it's not reproducible or no image for expected result, made a sample to show that how you can scroll using ones value
@Preview
@Composable
private fun LayoutTest() {
val state = rememberScrollState()
val lazyListState = rememberLazyListState()
LaunchedEffect(key1 = Unit) {
var oldValue = 0
snapshotFlow {
state.value
}.collect {
println("Value: $it")
lazyListState.scrollBy((it - oldValue).toFloat())
oldValue = it
}
}
Column(
Modifier
.fillMaxSize()
.border(2.dp, Color.Red)
) {
Row(
Modifier
.fillMaxSize()
.border(3.dp, Color.Green)
) {
Column(
modifier = Modifier
.width(150.dp)
.fillMaxHeight()
.verticalScroll(state = state)
) {
for (i in 0..99) {
Text(
text = "Header $i",
fontSize = 20.sp,
color = Color.White,
modifier = Modifier
.fillMaxWidth()
.background(Color.Green)
)
}
}
LazyColumn(
modifier = Modifier
.fillMaxWidth(),
state = lazyListState
) {
items(100) {
Text(
text = "Row $it",
fontSize = 20.sp,
color = Color.White,
modifier = Modifier
.fillMaxWidth()
.background(Color.Red)
)
}
}
}
}
}
If you wish to scroll both scrollable Column and LazyColumn together NestedScrollConnection can be used to get scroll value of LazyColumn and scroll stat to disable LaunchedEffect that listens Column scroll to not scroll LazyColumn further.
However this doesn't work properly if you start scrolling while scroll in other Composable is in progress.
@Preview
@Composable
private fun LayoutTest() {
val state = rememberScrollState()
val lazyListState = rememberLazyListState()
var listScrolling by remember {
mutableStateOf(false)
}
val coroutineScope = rememberCoroutineScope()
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
val delta = -available.y
coroutineScope.launch {
state.scrollBy(delta)
listScrolling = true
}
return Offset.Zero
}
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
listScrolling = false
return super.onPostFling(consumed, available)
}
}
}
LaunchedEffect(key1 = Unit) {
var oldValue = 0
snapshotFlow {
state.value
}.collect {
if (listScrolling.not() && lazyListState.isScrollInProgress.not()) {
lazyListState.scrollBy((it - oldValue).toFloat())
oldValue = it
}
}
}
Column(
Modifier
.fillMaxSize()
.padding(8.dp)
) {
Row(
Modifier
.fillMaxSize()
) {
Column(
modifier = Modifier
.width(150.dp)
.fillMaxHeight()
.verticalScroll(state = state)
) {
for (i in 0..400) {
Text(
text = "Header $i",
fontSize = 24.sp,
modifier = Modifier
.shadow(4.dp, RoundedCornerShape(8.dp))
.fillMaxWidth()
.background(Color.White, RoundedCornerShape(8.dp))
.padding(8.dp)
.wrapContentSize()
)
}
}
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.nestedScroll(nestedScrollConnection),
state = lazyListState
) {
items(401) {
Text(
text = "Row $it",
fontSize = 24.sp,
modifier = Modifier
.shadow(4.dp, RoundedCornerShape(8.dp))
.fillMaxWidth()
.background(Color.White, RoundedCornerShape(8.dp))
.padding(8.dp)
.wrapContentSize()
)
}
}
}
}
}
This one seem like works well in any condition
@Preview
@Composable
private fun LayoutTest2() {
val state = rememberScrollState()
val lazyListState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
val delta = -available.y
if (state.isScrollInProgress.not()) {
coroutineScope.launch {
state.scrollBy(delta)
}
}
if (lazyListState.isScrollInProgress.not()) {
coroutineScope.launch {
lazyListState.scrollBy(delta)
}
}
return Offset.Zero
}
}
}
Column(
Modifier
.fillMaxSize()
.nestedScroll(nestedScrollConnection)
.padding(8.dp)
) {
Row(
Modifier
.fillMaxSize()
) {
Column(
modifier = Modifier
.width(150.dp)
.fillMaxHeight()
.verticalScroll(state = state)
) {
for (i in 0..400) {
Text(
text = "Header $i",
fontSize = 24.sp,
modifier = Modifier
.shadow(4.dp, RoundedCornerShape(8.dp))
.fillMaxWidth()
.background(Color.White, RoundedCornerShape(8.dp))
.padding(8.dp)
.wrapContentSize()
)
}
}
LazyColumn(
state = lazyListState,
modifier = Modifier
.fillMaxWidth()
) {
items(401) {
Text(
text = "Row $it",
fontSize = 24.sp,
modifier = Modifier
.shadow(4.dp, RoundedCornerShape(8.dp))
.fillMaxWidth()
.background(Color.White, RoundedCornerShape(8.dp))
.padding(8.dp)
.wrapContentSize()
)
}
}
}
}
}