Jetpack Compose quicky: List click animation with Jetpack Compose in Android
Tags: android, kotlin, mobile, tristan
Published: 2022-11-25 07:17:50

### Introduction
- If you are looking for more Jetpack Compose/Android tutorials, please checkout my profile here on Dev.to

### YouTube video
- [HERE](https://youtu.be/Qk6wHItTlRA)


### What we are going to be making
- So today we are going to be animating clicks on Lazy Rows in Jetpack compose. We are going to implement two versions of the click:

**1) normal version:** Official google documentation shows you how to implement this version [HERE](https://developer.android.com/codelabs/jetpack-compose-basics?continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fjetpack-compose-for-android-developers-1%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fjetpack-compose-basics#10) 

**2) focused version :** in this version we will make it so only one animation will be triggered at a time.

- If you are wondering what version you are looking for, check out the [video](https://youtu.be/Qk6wHItTlRA) for a demonstration

### normal version
- So we start off with a normal [Lazy Row](https://developer.android.com/jetpack/compose/lists) like so:
```kotlin
val items = (1..168).map { "Item $it" }
LazyRow(
                horizontalArrangement = Arrangement.SpaceEvenly,
                state = rememberLazyListState()
            ) {

                itemsIndexed(items) { index, item ->
                    CardShown(item)
                }

            }

```
- If you are unfamiliar with the lazy components(Lazy Row) just know that inside the curly brackets `{}` it is [implementing a type safe builder](https://kotlinlang.org/docs/type-safe-builders.html). The `type-safe builder` is creating an instance of  [LazyListScope](https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/LazyListScope) and it is what gives us access to the `itemsIndexed()` method.

- Now we can get into animating the click:
```kotlin
@Composable
fun CardShown(item: String){
var expanded by remember { mutableStateOf(false) }

 val extraPadding by animateDpAsState(
        if (!isFocused ) 48.dp else 0.dp,
        animationSpec = spring(
            dampingRatio = Spring.DampingRatioMediumBouncy,
            stiffness = Spring.StiffnessLow
        )
    )
Card(
        backgroundColor = Color.LightGray,
        modifier = Modifier
            .height(300.dp)
            .width(160.dp)
            .padding(extraPadding.coerceAtLeast(0.dp)
            .clickable{ expanded = !expanded}

    ) {
        Text(item) // card's content
    }

}

```
- First we create the state that is going to hold a true or false value depending if the Card has been clicked of not, `var expanded by remember { mutableStateOf(false) }`. The important thing to remember is that `mutableStateOf()` produces a `MutableState` which inherits from `State`. As we know a recomposition is typically triggered by a change to a `State` object. So anytime the value of `expanded` is changed the whole card will undergo a recomposition.

- Next we have the code that is doing the actual animation:
```kotlin
val extraPadding by animateDpAsState(
        targetValue = if (!expanded ) 48.dp else 0.dp,
        animationSpec = spring(
            dampingRatio = Spring.DampingRatioMediumBouncy,
            stiffness = Spring.StiffnessLow
        )
    )
```
- As the documentation states, `animateDpAsState is a fire-and-forget animation function for dp. When the provided targetValue is changed, the animation will run automatically`.

- `spring() :` creates a physic-based animation between start and end values and it takes in 2 parameters. `1)dampingRatio `, defines how bouncy the spring should be. `2)stiffness`, defines how fast the spring should run towards the end value.

- Lastly we simply have to modify the Card's modifier to make card clickable:
```kotlin
            .padding(extraPadding.coerceAtLeast(0.dp)
            .clickable{ expanded = !expanded}

```
- All `extraPadding.coerceAtLeast(0.dp)` is doing is making sure that there is no value less than 0 entered for the padding. By placing `extraPadding` inside of `padding()` all of the padding values will be automatically animated for us. `clickable{ expanded = !expanded}` is how we make the card element  clickable and change the `expanded ` variable which triggers the recomposition and the animation.

- The official documentation on how to create and implement the code above is [HERE](https://developer.android.com/codelabs/jetpack-compose-basics?continue=https%3A%2F%2Fdeveloper.android.com%2Fcourses%2Fpathways%2Fjetpack-compose-for-android-developers-1%23codelab-https%3A%2F%2Fdeveloper.android.com%2Fcodelabs%2Fjetpack-compose-basics#10)

### focused version
- Now this implementation is of my own creation, it adds a focusable element to the card and only allow one card to be open at a time. The code looks like this:
```kotlin
@Composable
fun CardShown(item: String){

    val focusRequester = remember { FocusRequester() }

    val interactionSource = remember { MutableInteractionSource() }
    val isFocused = interactionSource.collectIsFocusedAsState().value

 val extraPadding by animateDpAsState(
        targetValue = if (!isFocused ) 48.dp else 0.dp,
        animationSpec = spring(
            dampingRatio = Spring.DampingRatioMediumBouncy,
            stiffness = Spring.StiffnessLow
        )
    )
Card(
        backgroundColor = Color.LightGray,
        modifier = Modifier
            .focusRequester(focusRequester)// needed to make work
            .focusable(interactionSource = interactionSource)
            .height(300.dp)
            .width(160.dp)
            .padding(extraPadding.coerceAtLeast(0.dp))
            .clickable {focusRequester.requestFocus()}
) {
        Text(item) // card's content
    }

```
- Basically the whole idea with the code above is we want one object to be open when we click it. But when we click another object the open object closes and the clicked object opens.

- To achieve this we need to get the state of the element when it is clicked and the state of it when it is not clicked. It all starts with these 3 variables:
```kotlin
 val focusRequester = remember { FocusRequester() }

    val interactionSource = remember { MutableInteractionSource() }
    val isFocused = interactionSource.collectIsFocusedAsState().value

```
- `focusRequester ` is how we can interact with the focus state of an element. `interactionSource` is a little more complicated and if you want to find out more about interactions, check out the documentation, [HERE](https://developer.android.com/jetpack/compose/handling-interaction). But basically it acts as a pipe line that we can listen to for changes in focus events. `isFocused ` is where we listen for those changes in focus events.

- The we have:
```kotlin
        .focusRequester(focusRequester)
        .focusable(interactionSource = interactionSource)

```
- again, `focusRequester()` allows us to make the card emit focus events and `focusable()` states where we are going to send those focus events to, `interactionSource `(the prementioned pipeline).

- Lastly we have:
```kotlin
.clickable {focusRequester.requestFocus()} 

```
- The above code is us forcing focus onto the object when it get clicked, which will emit a focus event to the `interactionSource` which is being listened to by `collectIsFocusedAsState()` and a change will cause a animation to take place

###Conclusion
- Thank you for taking the time out of your day to read this blog post of mine. If you have any questions or concerns please comment below or reach out to me on [Twitter](https://twitter.com/AndroidTristan).