Search code examples
android-jetpack-composeandroid-jetpacklazycolumn

Set a single AnimatedVisibility to true in a LazyColumn


I have a Composable function for operating system infos which expands it details upon click and reverts when clicked again.

@ExperimentalAnimationApi
@Composable
fun OSCard(os: OS) {
    var expanded by remember {
        mutableStateOf(false)
    }
    Column(modifier = Modifier
        .clickable { expanded = !expanded }
        .fillMaxWidth()) {
        Text(
            modifier = Modifier
                .padding(20.dp),
            text = os.name,
            style = MaterialTheme.typography.h6
        )
        AnimatedVisibility(visible = expanded) {
            Text(text = os.description, modifier = Modifier.padding(20.dp))
        }
        Divider(Modifier.height(2.dp))
    }
}

I created a list of it and passed it through a LazyColumn

var OSs = listOf<OS>(
    OS(
        "Android",
        "Android is a mobile/desktop operating system..."
      ),
    OS(
        "Microsoft Windows",
        "Microsoft Windows, commonly referred to as Windows..."
      ),
    OS(
        "Linux",
        "Linux is a family of open-source Unix-like operating systems..."
      )
)

Surface(color = MaterialTheme.colors.background) {
    LazyColumn(modifier = Modifier.fillMaxSize()) {
        items(items =  OSs){
            os -> OSCard(os)
        }
    }
}

While it works as expected, I want to make it such that if a card is opened and another card is selected, the previously opened card will be closed.

This is what I am trying to avoid, can someone give me a tip on how to?

What I am trying to avoid


Solution

  • Here is one solution. Add a parameter to your OS object called isExpanded. It only gets set to true when you click on a card. The click handler will clear the flag in all the other cards.

    I also added an id parameter which makes it easier to find the item being clicked. The name parameter could have been used.

    The state variable expandCard needs to read for it to trigger a recompose, so var e = expandCard is used to read the value. Also note that the creation of your list MUST be outside of the composable, otherwise it would just end up getting re-created and the isExpanded field would be set to false on all items.

    And lastly, I'm not sure why you're using a LazyColumn for this. LazyColumn is really meant for large datasets and primarily for paging. You could just use a normal Column with vertical scrolling if you have a reasonable number of items.

    class MainActivity : ComponentActivity() {
        @ExperimentalAnimationApi
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            var OSs = listOf(
                OS(
                    id = "android",
                    name = "Android",
                    description =  "Android is a mobile/desktop operating system..."
                ),
                OS(
                    id = "mswindows",
                    name = "Microsoft Windows",
                    description = "Microsoft Windows, commonly referred to as Windows..."
                ),
                OS(
                    id = "linux",
                    name ="Linux",
                    description = "Linux is a family of open-source Unix-like operating systems..."
                )
            )
    
            setContent {
    
                var expandCard by remember { mutableStateOf(false) }
                var e = expandCard
    
                Surface() {
                    LazyColumn(  modifier = Modifier.fillMaxSize()) {
                        itemsIndexed(
                            items = OSs,
                            key = { index, os ->
                                os.name
                            }
                        ) { index, os ->
                            OSCard(os) {osClicked ->
                                val isExpanded = osClicked.isExpanded
                                OSs.forEach { it.isExpanded = false }
                                OSs.first { it.id == osClicked.id }.isExpanded = isExpanded
                                expandCard = !expandCard
                            }
                        }
                    }
                }
            }
        }
    }
    
    @ExperimentalAnimationApi
    @Composable
    fun OSCard(
        os: OS,
        onClick: (os: OS) -> Unit
    ) {
    
        Column(modifier = Modifier
            .clickable {
                os.isExpanded = !os.isExpanded
                onClick(os)
            }
            .fillMaxWidth()) {
            Text(
                modifier = Modifier
                    .padding(20.dp),
                text = os.name,
                style = MaterialTheme.typography.h6
            )
    
            AnimatedVisibility(visible = os.isExpanded) {
                Text(text = os.description, modifier = Modifier.padding(20.dp))
            }
            Divider(Modifier.height(2.dp))
        }
    }
    
    class OS(var id: String, var name: String, var description: String, var isExpanded: Boolean= false)