Compose LazyColumn select one item
Asked Answered
R

2

12

I want to select one item of my LazyColumn and change the textcolor. How is it possible to identify which item is selected?

Code:

val items = listOf(Pair("A", 1), Pair("AA", 144), Pair("BA", 99))
var selectedItem by mutableStateOf(items[0])
LazyColumn {
    this.items(items = items) {
        Row(modifier = Modifier.clickable(onClick = {selectedItem = it}) {
            if (selectedItem == it) {
                Text(it.first, color = Color.Red)
            } else {
                Text(it.first)
            }
        }
    }
}

Depending how I save it (with remember or without) they just highlight both if I click on one and not just the one I clicked the last.

Reginareginald answered 25/3, 2021 at 5:56 Comment(0)
P
21

You can use the the .selectable modifier instead of .clickable

Something like:

data class Message(val id: Int,
                   val message : String)
val messages : List<Message> = listOf(...))

val listState = rememberLazyListState()
var selectedIndex by remember{mutableStateOf(-1)}
 
LazyColumn(state = listState) {
        items(items = messages) { message ->

            Text(
                text = message.message,
                modifier = Modifier
                    .fillMaxWidth()
                    .background(
                        if (message.id == selectedIndex)
                            Color.Red else Color.Yellow
                    )
                    .selectable(
                        selected = message.id == selectedIndex,
                        onClick = { if (selectedIndex != message.id)
                             selectedIndex = message.id else selectedIndex = -1})
            )
        }
 }

In your case you can use:

var selectedItem by remember{mutableStateOf( "")}
LazyColumn {
    this.items(items = items) {
        Row(modifier = Modifier.selectable(
            selected = selectedItem == it.first,
            onClick = { selectedItem = it.first}
                )
        ) {
            if (selectedItem == it.first) {
                Text(it.first, color = Color.Red)
            } else {
                Text(it.first)
            }
        }
    }
}
Pusey answered 25/3, 2021 at 7:24 Comment(1)
Note that with your solution, all the item views will be recomposed every time the selection changes, because the lambdas passed in onClick and content (of Row) are not stable (developer.android.com/jetpack/compose/lifecycle#skipping).Balbur
B
15

Note that in the accepted answer, all the item views will be recomposed every time the selection changes, because the lambdas passed in onClick and content (of Row) are not stable (https://developer.android.com/jetpack/compose/lifecycle#skipping).

Here's one way to do it so that only the deselected and selected items are recomposed:

@Composable
fun ItemView(index: Int, selected: Boolean, onClick: (Int) -> Unit){
    Text(
        text = "Item $index",
        modifier = Modifier
            .clickable {
                onClick.invoke(index)
            }
            .background(if (selected) MaterialTheme.colors.secondary else Color.Transparent)
            .fillMaxWidth()
            .padding(12.dp)
    )
}

@Composable
fun LazyColumnWithSelection(){
    var selectedIndex by remember { mutableStateOf(0) }
    val onItemClick = { index: Int -> selectedIndex = index}
    LazyColumn(
        modifier = Modifier.fillMaxSize(),
    ){
        items(100){ index ->
            ItemView(
                index = index,
                selected = selectedIndex == index,
                onClick = onItemClick
            )
        }
    }
}

Note how the arguments passed to ItemView only change for the items whose selected state changes. This is because the onItemClick lambda is the same all the time.

Balbur answered 28/1, 2022 at 15:54 Comment(1)
You use the .clickable method in your example. Will it also work with .selectable like in the other answer?Sylph

© 2022 - 2024 — McMap. All rights reserved.