Skip to content

Commit

Permalink
Allow software rendering to be invalidated when dynamic properties ch…
Browse files Browse the repository at this point in the history
…ange (#2034)

The test case failed to re-render before but works now.
  • Loading branch information
gpeal committed Mar 7, 2022
1 parent 5c3c860 commit 1effb29
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 0 deletions.
13 changes: 13 additions & 0 deletions lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
Expand Up @@ -236,6 +236,19 @@ private void init(@Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
super.unscheduleDrawable(who);
}

@Override public void invalidate() {
super.invalidate();
Drawable d = getDrawable();
if (d instanceof LottieDrawable && ((LottieDrawable) d).getRenderMode() == RenderMode.SOFTWARE) {
// This normally isn't needed. However, when using software rendering, Lottie caches rendered bitmaps
// and updates it when the animation changes internally.
// If you have dynamic properties with a value callback and want to update the value of the dynamic property, you need a way
// to tell Lottie that the bitmap is dirty and it needs to be re-rendered. Normal drawables always re-draw the actual shapes
// so this isn't an issue but for this path, we have to take the extra step of setting the dirty flag.
lottieDrawable.invalidateSelf();
}
}

@Override public void invalidateDrawable(@NonNull Drawable dr) {
if (getDrawable() == lottieDrawable) {
// We always want to invalidate the root drawable so it redraws the whole drawable.
Expand Down
Expand Up @@ -4,11 +4,23 @@
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;

import com.airbnb.lottie.LottieAnimationView;
import com.airbnb.lottie.LottieDrawable;
import com.airbnb.lottie.animation.keyframe.BaseKeyframeAnimation;

/**
* Allows you to set a callback on a resolved {@link com.airbnb.lottie.model.KeyPath} to modify
* its animation values at runtime.
*
* If your dynamic property does the following, you must call {@link LottieAnimationView#invalidate()} or
* {@link LottieDrawable#invalidateSelf()} each time you want to update this value.
* 1. Use {@link com.airbnb.lottie.RenderMode.SOFTWARE}
* 2. Rendering a static image (the animation is either paused or there are no values
* changing within the animation itself)
* When using software rendering, Lottie caches the internal rendering bitmap. Whenever the animation changes
* internally, Lottie knows to invalidate the bitmap and re-render it on the next frame. If the animation
* never changes but your dynamic property does outside of Lottie, Lottie must be notified that it changed
* in order to set the bitmap as dirty and re-render it on the next frame.
*/
public class LottieValueCallback<T> {
private final LottieFrameInfo<T> frameInfo = new LottieFrameInfo<>();
Expand All @@ -31,6 +43,9 @@ public LottieValueCallback(@Nullable T staticValue) {
* Override this if you haven't set a static value in the constructor or with setValue.
* <p>
* Return null to resort to the default value.
*
* Refer to the javadoc for this class for a special case that requires manual invalidation
* each time you want to return something different from this method.
*/
@Nullable
public T getValue(LottieFrameInfo<T> frameInfo) {
Expand Down
Expand Up @@ -30,6 +30,7 @@ import com.airbnb.lottie.snapshots.tests.OutlineMasksAndMattesTestCase
import com.airbnb.lottie.snapshots.tests.PartialFrameProgressTestCase
import com.airbnb.lottie.snapshots.tests.ProdAnimationsTestCase
import com.airbnb.lottie.snapshots.tests.ScaleTypesTestCase
import com.airbnb.lottie.snapshots.tests.SoftwareRenderingDynamicPropertiesInvalidationTestCase
import com.airbnb.lottie.snapshots.tests.TextTestCase
import com.airbnb.lottie.snapshots.utils.BitmapPool
import com.airbnb.lottie.snapshots.utils.HappoSnapshotter
Expand Down Expand Up @@ -125,6 +126,7 @@ class LottieSnapshotTest {
ComposeDynamicPropertiesTestCase(),
ProdAnimationsTestCase(),
ClipChildrenTestCase(),
SoftwareRenderingDynamicPropertiesInvalidationTestCase(),
)

withTimeout(TimeUnit.MINUTES.toMillis(45)) {
Expand Down
@@ -0,0 +1,68 @@
package com.airbnb.lottie.snapshots.tests

import android.graphics.Canvas
import android.graphics.Color
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.ImageView
import com.airbnb.lottie.LottieCompositionFactory
import com.airbnb.lottie.LottieProperty
import com.airbnb.lottie.RenderMode
import com.airbnb.lottie.model.KeyPath
import com.airbnb.lottie.snapshots.R
import com.airbnb.lottie.snapshots.SnapshotTestCase
import com.airbnb.lottie.snapshots.SnapshotTestCaseContext
import com.airbnb.lottie.value.LottieFrameInfo
import com.airbnb.lottie.value.LottieValueCallback

/**
* When using software rendering, Lottie caches its internal render bitmap if the animation changes.
* However, if a dynamic property changes in a LottieValueCallback, the consumer must call LottieAnimationView.invalidate()
* or LottieDrawable.invalidateSelf() to invalidate the drawing cache.
*/
class SoftwareRenderingDynamicPropertiesInvalidationTestCase : SnapshotTestCase {
override suspend fun SnapshotTestCaseContext.run() {
val animationView = animationViewPool.acquire()
val composition = LottieCompositionFactory.fromRawResSync(context, R.raw.heart).value!!
animationView.setComposition(composition)
animationView.renderMode = RenderMode.SOFTWARE
animationView.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
animationView.scaleType = ImageView.ScaleType.FIT_CENTER
val widthSpec = View.MeasureSpec.makeMeasureSpec(
context.resources.displayMetrics.widthPixels,
View.MeasureSpec.EXACTLY,
)
val heightSpec = View.MeasureSpec.makeMeasureSpec(
context.resources.displayMetrics.heightPixels,
View.MeasureSpec.EXACTLY,
)
val animationViewContainer = animationView.parent as ViewGroup
animationViewContainer.measure(widthSpec, heightSpec)
animationViewContainer.layout(0, 0, animationViewContainer.measuredWidth, animationViewContainer.measuredHeight)
val canvas = Canvas()

var color = Color.GREEN
animationView.addValueCallback(KeyPath("**", "Fill 1"), LottieProperty.COLOR, object : LottieValueCallback<Int>() {
override fun getValue(frameInfo: LottieFrameInfo<Int>?): Int {
return color
}
})

var bitmap = bitmapPool.acquire(animationView.width, animationView.height)
canvas.setBitmap(bitmap)
animationView.draw(canvas)
snapshotter.record(bitmap, "Heart Software Dynamic Property", "Green")
bitmapPool.release(bitmap)

bitmap = bitmapPool.acquire(animationView.width, animationView.height)
canvas.setBitmap(bitmap)
color = Color.BLUE
animationView.invalidate()
animationView.draw(canvas)
snapshotter.record(bitmap, "Heart Software Dynamic Property", "Blue")
bitmapPool.release(bitmap)

animationViewPool.release(animationView)
}
}

0 comments on commit 1effb29

Please sign in to comment.