Jetpack Compose, better method to animate vector translation? Newer libraries are not working

59 Views Asked by At

I have a vector animation in my jetpack compose app where the translation animation repeats indefinitely and this has been working well until recent updates to drawablepainter up to 0.33.2 (working) vs 0.34.0 (not working) and some combination any compose library 1.5.4 (working) vs 1.6.0 (not working). With the newer libraries, the animation does not run, and only the initial vector is drawn.

Is there a better method to animate than I am using here? With the newer library combination, the animation sticks on the first frame, and there appears to be some error but I can't catch anything in the logs, or in debug.

My code is as follows

    @Composable
    fun GarageIndicatorClosing() {
        val backgroundVector =
            ImageVector.vectorResource(id = R.drawable.garage_door_open)
        val garageBackgroundPainter = rememberVectorPainter(image = backgroundVector)

        var yState by remember { mutableIntStateOf(0) }
        val yOffset = animateIntAsState(
            targetValue = yState,
            animationSpec =
            infiniteRepeatable(animation = tween(durationMillis = 1200, easing = LinearEasing))
        )
        Box {
            Image(painter = garageBackgroundPainter, contentDescription = null)
            MoveGarageArrow(yOffset = yOffset.value) {
                GarageArrow(arrowID = R.drawable.garage_door_closing_arrow_anim)
            }
        }
        yState = 150

    }

    @Composable
    fun GarageArrow(arrowID: Int) {
        Image(
            painter = painterResource(id = arrowID),
            contentDescription = null,
            contentScale = ContentScale.Crop
        )
    }

    @Composable
    fun MoveGarageArrow(
        yOffset: Int,
        item: @Composable () -> Unit
    ) {
        // outer box
        Box {
            // inner box
            Box(
                Modifier
                    .absoluteOffset(y = yOffset.dp)
                    .align(Alignment.Center)
            ) {
                item()
            }
        }
    }

with a vector like below

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="200dp"
    android:height="60dp"
    android:viewportWidth="150"
    android:viewportHeight="45">
  <path
      android:pathData="M30.05,3.13 L75.8,41.88 121.55,3.13h-16L76.05,27.63 45.55,3.13Z"
      android:strokeLineJoin="miter"
      android:strokeWidth="1"
      android:fillColor="#00ff00"
      android:strokeColor="#ffffff"
      android:strokeLineCap="butt"/>
</vector>

Below are the most recent set of libraries I can use and still have working vector animation.

Using any combination of 1.6.0 compose library or drawablepainter 33.2 or higher breaks animation

dependencies {
    implementation "androidx.activity:activity-compose:1.8.2"
    implementation "androidx.car.app:app:1.2.0"
    implementation "androidx.compose.animation:animation-graphics:1.5.4" // any 1.6.0 breaks as well
    implementation "androidx.compose.foundation:foundation:1.5.4"
    implementation "androidx.compose.ui:ui-tooling-preview:1.5.4"
    implementation "androidx.compose.material3:material3:1.1.2"
    implementation "androidx.constraintlayout:constraintlayout:2.1.4"
    implementation "androidx.core:core-ktx:1.12.0"
    implementation "androidx.preference:preference-ktx:1.2.1"
    implementation "androidx.work:work-runtime-ktx:2.9.0"
    implementation "com.android.volley:volley:1.2.1"
    implementation 'com.google.accompanist:accompanist-drawablepainter:0.33.1-alpha' //33.2 breaks
    implementation "com.google.android.gms:play-services-location:21.1.0"
    api 'com.google.android.material:material:1.11.0'
    implementation 'com.google.code.gson:gson:2.10.1'

    apply plugin: 'com.google.gms.google-services'
    debugImplementation 'androidx.compose.ui:ui-tooling:1.5.4'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
}

I have a minimal project illustrating the issue at https://github.com/coinzdude/AnimationBug/tree/master

The working animation looks like this

enter image description here

1

There are 1 best solutions below

0
80sTron On

I found something that works well with the older and newer libraries. There were two needed changes. It's possible that the important and actual fix is just #2 below.

  1. Switch to using "animateDpAsState" with a trigger variable, e.g. 'doorState', to initiate the animation/state change
  2. Change the trigger variable to be changed in a separate thread. I could not get the animation to initiate if the state variable was changed in the same composable thread. This was possible using LaunchedEffect which has a delay, where I could change the state var after it has been initialized in the originating composable instantiation. This is important as I always want the animation any time this image is being drawn, as it's not user initiated.
    @Composable
    fun GarageIndicatorOpening() {
        var doorState by remember { mutableStateOf(DoorPosition.Start) }
        val offsetAnimation: Dp by animateDpAsState(
            if (doorState == DoorPosition.Start) 150.dp else 0.dp,
            infiniteRepeatable(animation = tween(durationMillis = 1200, easing = LinearEasing), repeatMode = RepeatMode.Restart),
            label = "Door"
        )
        val backgroundVector =
            ImageVector.vectorResource(id = R.drawable.garage_door_action_background)
        val garageBackgroundPainter = rememberVectorPainter(image = backgroundVector)

        Box {
            Image(painter = garageBackgroundPainter, contentDescription = null)
            MoveGarageArrow(yOffset = offsetAnimation) {
                GarageArrow(arrowID = R.drawable.garage_door_opening_arrow_anim)
            }
        }
        LaunchedEffect(Unit) {
            delay(1.milliseconds)
            doorState = DoorPosition.Finish
        }
    }

for completeness, the MoveGarageArrow changed slightly to accept the new Dp param vs int

    @Composable
    fun MoveGarageArrow(
        yOffset: Dp,
        item: @Composable () -> Unit
    ) {
        // outer box
        Box {
            // inner box
            Box(
                Modifier
                    .absoluteOffset(y = yOffset)
                    .align(Alignment.Center)
            ) {
                item()
            }
        }
    }