How can I scroll a custom layout and lazycolumn in the same row as if they are one component

415 Views Asked by At

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
                        )
                    )

                }
            )
        }


    }
}
1

There are 1 best solutions below

10
On BEST ANSWER

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

enter image description here

@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)
                    )
                }
            }
        }
    }
}

Edit

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()
                    )
                }
            }
        }
    }
}

Edit2

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()
                    )
                }
            }
        }
    }
}