performancescrollandroid-jetpack-composealphabeticalletter

Alphabetical Scrollbar in jetpack compose


Does anyone know how to create an alphabetical fast scroller, displaying all the letters in Jetpack Compose?

Similar to this one: Recyclerview Alphabetical Scrollbar

I have made a list which is scrollable, but I have no clue in how to make the letters on the side and make it "jump" to the right letter.

here is my coding so far:

package com.example.ValpeHagen.ui.theme

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.ValpeHagen.model.DataProvider
import com.example.ValpeHagen.model.DataProvider2
import com.example.androiddevchallenge.data.model.Rase




class Valpen {

    val rase = DataProvider2.rase




@Composable
fun VerticalHorizontalScroll(rase: Rase) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.White)
    ) {
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .height(60.dp)
                .background(Grass),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.Center
        ) {
            Text(text = "Velg Rase",
                color = Color.White,
                fontSize = 30.sp,
                fontWeight = FontWeight.Bold,)
        }

        LazyColumn {
            item {
                Text(
                    text = "Hurtigvalg",
                    color = Color.Black,
                    fontSize = 18.sp,
                    fontWeight = FontWeight.Bold,
                    modifier = Modifier.padding(10.dp)
                )
            }

            //Horizontal Scroll view
            item {
                LazyRow {
                    itemsIndexed(items = DataProvider.puppyList) { index, itemPuppy ->
                        Card(
                            modifier = Modifier
                                .width(110.dp)
                                .height(140.dp)
                                .padding(10.dp, 5.dp, 5.dp, 0.dp)
                                .clip(RoundedCornerShape(10.dp))
                                .background(Color.White),
                            elevation = 5.dp
                        )  {
                            Column(
                                modifier = Modifier.padding(5.dp),
                                horizontalAlignment = Alignment.CenterHorizontally,
                                verticalArrangement = Arrangement.Center
                            ) {

                                Image(
                                    painter = painterResource(id = DataProvider.puppy.puppyImageId),
                                    contentDescription = "profile Image",
                                    contentScale = ContentScale.Crop,
                                    modifier = Modifier
                                        .size(60.dp)
                                        .clip(CircleShape)
                                )

                                Spacer(modifier = Modifier.padding(5.dp))

                                Text(
                                    text = DataProvider.puppy.breeds,
                                    color = Color.Black,
                                    fontWeight = FontWeight.Bold,
                                    fontSize = 16.sp)

                            }
                        }
                    }
                }
            }
            val mylist = listOf("Affenpinscher", "Wheaten terrier" , "dachshund",
                "Fransk bulldog","Affenpinscher", "Wheaten terrier" , "dachshund",
                "Fransk bulldog","Affenpinscher", "Wheaten terrier" , "dachshund",
                "Fransk bulldog","Affenpinscher", "Wheaten terrier" , "dachshund",
                "Fransk bulldog","Affenpinscher", "Wheaten terrier" , "dachshund",
                "Fransk bulldog","Affenpinscher", "Wheaten terrier" , "dachshund",
                "Fransk bulldog","Affenpinscher", "Wheaten terrier" , "dachshund",
                "Fransk bulldog")


            item {

                Text(
                    text = "Alle hunderaser",
                    color = Color.Black,
                    fontSize = 18.sp,
                    fontWeight = FontWeight.Bold,
                    modifier = Modifier
                        .padding(vertical = 10.dp, horizontal = 10.dp)
                        .clip(RoundedCornerShape(10.dp))
                        .background(Color.White))
            }


            item {


                val list = listOf("A", "B", "C", "D",
                    "E","F","G","H","I","J","K","L","M","N",
                    "O","P","Q","R","S","T","U","V","W","X", "Y","Z","Æ","Ø","Å")
                val expanded = remember { mutableStateOf(false) }
                val currentValue = remember { mutableStateOf(list[0]) }



                Surface(modifier = Modifier.fillMaxSize()) {

                    Box(modifier = Modifier.fillMaxWidth()) {

                        Row(modifier = Modifier
                            .clickable {
                                expanded.value = !expanded.value
                            }
                            .align(Alignment.CenterStart)) {
                            Text(text = currentValue.value,
                                color = Grass,
                                fontSize = 18.sp,
                                fontWeight = FontWeight.Bold,
                                modifier = Modifier.padding(horizontal = 14.dp)
                            )
                            Icon(imageVector = Icons.Filled.ArrowDropDown, contentDescription = null)


                            DropdownMenu(expanded = expanded.value, onDismissRequest = {
                                expanded.value = false
                            }) {

                                list.forEach {

                                    DropdownMenuItem(onClick = {
                                        currentValue.value = it
                                        expanded.value = false
                                    }) {

                                        Text(text = it,
                                            color = Grass,
                                            fontSize = 18.sp,
                                            fontWeight = FontWeight.Bold,
                                            modifier = Modifier
                                                .padding(vertical = 10.dp,
                                                    horizontal = 10.dp))


                                    }

                                }






                            }


                        }





                    }

                }
            }


        }





    }

}
     }

Solution

  • To display letters all you need is a Column with needed items. To scroll a lazy column to a needed item, you can use lazy column state.

    Here's a basic example:

    val items = remember { LoremIpsum().values.first().split(" ").sortedBy { it.lowercase() } }
    val headers = remember { items.map { it.first().uppercase() }.toSet().toList() }
    Row {
        val listState = rememberLazyListState()
        LazyColumn(
            state = listState,
            modifier = Modifier.weight(1f)
        ) {
            items(items) {
                Text(it)
            }
        }
        val offsets = remember { mutableStateMapOf<Int, Float>() }
        var selectedHeaderIndex by remember { mutableStateOf(0) }
        val scope = rememberCoroutineScope()
    
        fun updateSelectedIndexIfNeeded(offset: Float) {
            val index = offsets
                .mapValues { abs(it.value - offset) }
                .entries
                .minByOrNull { it.value }
                ?.key ?: return
            if (selectedHeaderIndex == index) return
            selectedHeaderIndex = index
            val selectedItemIndex = items.indexOfFirst { it.first().uppercase() == headers[selectedHeaderIndex] }
            scope.launch {
                listState.scrollToItem(selectedItemIndex)
            }
        }
    
        Column(
            verticalArrangement = Arrangement.SpaceEvenly,
            modifier = Modifier
                .fillMaxHeight()
                .background(Color.Gray)
                .pointerInput(Unit) {
                    detectTapGestures {
                        updateSelectedIndexIfNeeded(it.y)
                    }
                }
                .pointerInput(Unit) {
                    detectVerticalDragGestures { change, _ ->
                        updateSelectedIndexIfNeeded(change.position.y)
                    }
                }
        ) {
            headers.forEachIndexed { i, header ->
                Text(
                    header,
                    modifier = Modifier.onGloballyPositioned {
                        offsets[i] = it.boundsInParent().center.y
                    }
                )
            }
        }
    }