Search code examples
androidanimationandroid-jetpack-composeswipe

Compose swipe animation does not push the view as entering the screen


i am using SwipeToDismiss (Material3) in order to swipe and dismiss items from a list. It works fine but i want to display a small animation for first time to the user in order to see that he is able to swipe and dismiss items from the list. Basically I want to display a small swipe (for example 20 dp) and then return to initial state.

In order to achieve the animation i did this:

@Composable
fun AnimatedItem(
  onMeasuredHeight: (Dp) -> Unit = {}
) {
  val animationDuration = 2000

  val localDensity = LocalDensity.current

  var height by remember {
    mutableStateOf(0.dp)
  }

  var width by remember {
    mutableStateOf(0.dp)
  }

  var isVisible by remember {
    mutableStateOf(false)
  }

  Box(
    modifier = Modifier
      .background(
        color = Color.White,
        shape = RoundedCornerShape(8.dp)
      )
      .onGloballyPositioned { coordinates ->
        height = with(localDensity) { coordinates.size.height.toDp() }
        width = with(localDensity) { coordinates.size.width.toDp() }
        onMeasuredHeight(height)
      }
  ) {
    AnimatedVisibility(
      modifier = Modifier.align(Alignment.CenterEnd),
      visible = isVisible,
      enter = fadeIn(animationSpec = tween(durationMillis = animationDuration)) +
          expandHorizontally(
            expandFrom = Alignment.End,
            animationSpec = tween(
              durationMillis = animationDuration,
              easing = FastOutSlowInEasing,
            )
          ),
      exit = fadeOut(
        animationSpec = tween(
          durationMillis = animationDuration,
          easing = FastOutSlowInEasing,
        )
      ) + shrinkHorizontally(
        shrinkTowards = Alignment.End,
        animationSpec = tween(
          durationMillis = animationDuration,
          easing = FastOutSlowInEasing,
        )
      )
    ) {
      // the animated view
      Box(
        modifier = Modifier
          .width(32.dp)
          .height(height)
          .background(
            color = Color.Red,
            shape = RoundedCornerShape(
              topEnd = 8.dp,
              bottomEnd = 8.dp
            )
          )
      )
    }

    LaunchedEffect(Unit) {
      delay(500)
      isVisible = true
      delay(1000)
      isVisible = false
    }

    // the info view that does not pushed when red box enters
    Column(
      modifier = Modifier.padding(16.dp)
    ) {
      Row(
        modifier = Modifier.fillMaxWidth(),
        horizontalArrangement = Arrangement.SpaceBetween,
        verticalAlignment = Alignment.CenterVertically
      ) {
        Text(
          modifier = Modifier.padding(top = 8.dp),
          text = "text 1",
          style = MaterialTheme.typography.headlineMedium
        )

        Text(
          modifier = Modifier.padding(top = 8.dp),
          text = "text 2",
          style = MaterialTheme.typography.headlineMedium
        )
      }

      Text(
        modifier = Modifier.padding(top = 8.dp),
        text = "text 3",
        style = MaterialTheme.typography.titleMedium
      )

      Text(
        modifier = Modifier.padding(top = 2.dp),
        text = "text 4",
        style =MaterialTheme.typography.titleMedium,
      )
    }
  }
}

As a result i can see the animation (a red view entering from end to start) but my question is : Is there a way to make it like the red view "push" the remaining view? Now it is like the red box is above the other one, and not pushing the existing one as entering the screen.

I tried to use a Row that contains the info view and the animated one (red box) but then the animation does not work at all.

Any help is welcome


Solution

  • I was approaching wrongly the solution.

    Basically i added a red view on background and on top of that my actual row item view. So with the usage of offsetAnimation i have the required swipe animation (times can be adjusted based on our needs). My row item view swipes from end to start and the red background become visible. And then returns to initial state.

    @Composable
    fun AnimatedItem() {
    
      val startDelay = 500L
      val endDelay = 800L
    
      var isVisible by remember {
        mutableStateOf(false)
      }
    
      val targetValue: Dp = if (isVisible) {
        20.dp
      } else {
        0.dp
      }
    
      val offsetAnimation: Dp by animateDpAsState(
        targetValue = targetValue,
        label = "",
        animationSpec = tween(
          durationMillis = 1000,
          easing = FastOutSlowInEasing
        )
      )
    
      Box(
        modifier = Modifier
          .background(
            color = Color.Red,
            shape = RoundedCornerShape(8.dp)
          )
      ) {
        Row(
          modifier = Modifier
            .fillMaxSize()
            .offset(x = -offsetAnimation)
            .background(
              color = Color.White,
              shape = RoundedCornerShape(8.dp)
            )
        ) {
          Column(
            modifier = Modifier.padding(16.dp)
          ) {
            Row(
              modifier = Modifier.fillMaxWidth(),
              horizontalArrangement = Arrangement.SpaceBetween,
              verticalAlignment = Alignment.CenterVertically
            ) {
              Text(
                modifier = Modifier.padding(top = 8.dp),
                text = "text 1",
                style = MaterialTheme.typography.headlineMedium
              )
    
              Text(
                modifier = Modifier.padding(top = 8.dp),
                text = "text 2",
                style = MaterialTheme.typography.headlineMedium
              )
            }
    
            Text(
              modifier = Modifier.padding(top = 8.dp),
              text = "text 3",
              style = MaterialTheme.typography.titleMedium
            )
    
            Text(
              modifier = Modifier.padding(top = 2.dp),
              text = "text 4",
              style = MaterialTheme.typography.titleMedium,
            )
          }
    
          LaunchedEffect(Unit) {
            delay(startDelay)
            isVisible = true
            delay(endDelay)
            isVisible = false
          }
        }
      }
    }