Add dynamic properties for images
diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieDynamicProperties.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieDynamicProperties.kt
index ef791a9..c6333a1 100644
--- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieDynamicProperties.kt
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieDynamicProperties.kt
@@ -1,5 +1,6 @@
package com.airbnb.lottie.compose
+import android.graphics.Bitmap
import android.graphics.ColorFilter
import android.graphics.PointF
import android.graphics.Typeface
@@ -97,6 +98,7 @@
private val colorFilterProperties: List<LottieDynamicProperty<ColorFilter>>,
private val intArrayProperties: List<LottieDynamicProperty<IntArray>>,
private val typefaceProperties: List<LottieDynamicProperty<Typeface>>,
+ private val bitmapProperties: List<LottieDynamicProperty<Bitmap>>,
) {
@Suppress("UNCHECKED_CAST")
constructor(properties: List<LottieDynamicProperty<*>>) : this(
@@ -107,6 +109,7 @@
properties.filter { it.property is ColorFilter } as List<LottieDynamicProperty<ColorFilter>>,
properties.filter { it.property is IntArray } as List<LottieDynamicProperty<IntArray>>,
properties.filter { it.property is Typeface } as List<LottieDynamicProperty<Typeface>>,
+ properties.filter { it.property is Bitmap } as List<LottieDynamicProperty<Bitmap>>,
)
internal fun addTo(drawable: LottieDrawable) {
@@ -131,6 +134,10 @@
typefaceProperties.forEach { p ->
drawable.addValueCallback(p.keyPath, p.property, p.callback.toValueCallback())
}
+ bitmapProperties.forEach { p ->
+ drawable.addValueCallback(p.keyPath, p.property, p.callback.toValueCallback())
+ }
+
}
internal fun removeFrom(drawable: LottieDrawable) {
@@ -155,6 +162,9 @@
typefaceProperties.forEach { p ->
drawable.addValueCallback(p.keyPath, p.property, null as LottieValueCallback<Typeface>?)
}
+ bitmapProperties.forEach { p ->
+ drawable.addValueCallback(p.keyPath, p.property, null as LottieValueCallback<Bitmap>?)
+ }
}
}
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieImageAsset.java b/lottie/src/main/java/com/airbnb/lottie/LottieImageAsset.java
index faa7e9e..f1acbe6 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieImageAsset.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieImageAsset.java
@@ -56,7 +56,12 @@
}
/**
- * TODO
+ * Permanently sets the bitmap on this LottieImageAsset. This will:
+ * 1) Overwrite any existing Bitmaps.
+ * 2) Apply to *all* animations that use this LottieComposition.
+ *
+ * If you only want to replace the bitmap for this animation, use dynamic properties
+ * with {@link LottieProperty#IMAGE}.
*/
public void setBitmap(@Nullable Bitmap bitmap) {
this.bitmap = bitmap;
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieProperty.java b/lottie/src/main/java/com/airbnb/lottie/LottieProperty.java
index 0686156..e28128c 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieProperty.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieProperty.java
@@ -1,5 +1,7 @@
package com.airbnb.lottie;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
import android.graphics.ColorFilter;
import android.graphics.PointF;
import android.graphics.Typeface;
@@ -163,8 +165,17 @@
Float TEXT_SIZE = 14f;
ColorFilter COLOR_FILTER = new ColorFilter();
-
+ /**
+ * Array of ARGB colors that map to position stops in the original gradient.
+ * For example, a gradient from red to blue could be remapped with [0xFF00FF00, 0xFFFF00FF] (green to purple).
+ */
Integer[] GRADIENT_COLOR = new Integer[0];
-
+ /**
+ * Set on text layers.
+ */
Typeface TYPEFACE = Typeface.DEFAULT;
+ /**
+ * Set on image layers.
+ */
+ Bitmap IMAGE = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8);
}
diff --git a/lottie/src/main/java/com/airbnb/lottie/model/layer/ImageLayer.java b/lottie/src/main/java/com/airbnb/lottie/model/layer/ImageLayer.java
index fa855ae..1653449 100644
--- a/lottie/src/main/java/com/airbnb/lottie/model/layer/ImageLayer.java
+++ b/lottie/src/main/java/com/airbnb/lottie/model/layer/ImageLayer.java
@@ -25,6 +25,7 @@
private final Rect src = new Rect();
private final Rect dst = new Rect();
@Nullable private BaseKeyframeAnimation<ColorFilter, ColorFilter> colorFilterAnimation;
+ @Nullable private BaseKeyframeAnimation<Bitmap, Bitmap> imageAnimation;
ImageLayer(LottieDrawable lottieDrawable, Layer layerModel) {
super(lottieDrawable, layerModel);
@@ -60,6 +61,11 @@
@Nullable
private Bitmap getBitmap() {
+ if (imageAnimation != null) {
+ Bitmap callbackBitmap = imageAnimation.getValue();
+ if (callbackBitmap != null)
+ return callbackBitmap;
+ }
String refId = layerModel.getRefId();
return lottieDrawable.getImageAsset(refId);
}
@@ -76,6 +82,14 @@
colorFilterAnimation =
new ValueCallbackKeyframeAnimation<>((LottieValueCallback<ColorFilter>) callback);
}
+ } else if (property == LottieProperty.IMAGE) {
+ if (callback == null) {
+ imageAnimation = null;
+ } else {
+ //noinspection unchecked
+ imageAnimation =
+ new ValueCallbackKeyframeAnimation<>((LottieValueCallback<Bitmap>) callback);
+ }
}
}
}
diff --git a/sample-compose/src/main/assets/Images/android.png b/sample-compose/src/main/assets/Images/android.png
new file mode 100644
index 0000000..4bb66b0
--- /dev/null
+++ b/sample-compose/src/main/assets/Images/android.png
Binary files differ
diff --git a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/ImagesExamplesPage.kt b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/ImagesExamplesPage.kt
index b7934bf..0bedae9 100644
--- a/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/ImagesExamplesPage.kt
+++ b/sample-compose/src/main/java/com/airbnb/lottie/sample/compose/examples/ImagesExamplesPage.kt
@@ -11,13 +11,18 @@
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
-import com.airbnb.lottie.LottieImageAsset
+import com.airbnb.lottie.LottieProperty
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.rememberLottieComposition
+import com.airbnb.lottie.compose.rememberLottieDynamicProperties
+import com.airbnb.lottie.compose.rememberLottieDynamicProperty
import com.airbnb.lottie.sample.compose.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -36,8 +41,11 @@
ExampleCard("Assets Image", "Image stored in assets") {
ImageAssets()
}
- ExampleCard("Assets Image Callback", "Load an image manually") {
- ImageAssetCallback()
+ ExampleCard("Dynamic Properties", "Replace an image with dynamic properties") {
+ DynamicProperties()
+ }
+ ExampleCard("Store on LottieImageAsset", "Store the bitmap within LottieImageAsset") {
+ StoredOnImageAsset()
}
}
}
@@ -72,17 +80,40 @@
}
@Composable
-fun ImageAssetCallback() {
+fun DynamicProperties() {
+ // Don't cache the composition so the bitmaps can get released once the animation is no longer being used.
+ val composition by rememberLottieComposition(
+ LottieCompositionSpec.RawRes(R.raw.we_accept),
+ cacheKey = null,
+ )
+ val bitmap = rememberBitmapFromAssets("Images/android.png")
+
+ val dynamicProperties = rememberLottieDynamicProperties(
+ rememberLottieDynamicProperty(LottieProperty.IMAGE, bitmap, "weaccept.jpg")
+ )
+
+ LottieAnimation(
+ composition,
+ iterations = LottieConstants.IterateForever,
+ dynamicProperties = dynamicProperties,
+ )
+}
+
+@Composable
+fun StoredOnImageAsset() {
// Don't cache the composition so the bitmaps can get released once the animation is no longer being used.
val composition by rememberLottieComposition(
LottieCompositionSpec.RawRes(R.raw.we_accept),
cacheKey = null,
)
val imageAsset by derivedStateOf { composition?.images?.get("image_0") }
- val context = LocalContext.current
- LaunchedEffect(imageAsset) {
- withContext(Dispatchers.IO) {
- imageAsset?.bitmap = loadBitmapFromAssets(context, imageAsset)
+ val bitmap = rememberBitmapFromAssets("Images/android.png")
+ LaunchedEffect(imageAsset, bitmap) {
+ if (imageAsset != null && bitmap != null) {
+ // this stores the bitmap on the original composition's image asset which means that it
+ // will affect *all* LottieAnimation composables that are rendering this LottieComposition.
+ // Use with caution.
+ imageAsset?.bitmap = bitmap
}
}
LottieAnimation(
@@ -91,25 +122,27 @@
)
}
-private fun loadBitmapFromAssets(context: Context, asset: LottieImageAsset?): Bitmap? {
+@Composable
+private fun rememberBitmapFromAssets(asset: String): Bitmap? {
+ var bitmap: Bitmap? by remember { mutableStateOf(null) }
+ val context = LocalContext.current
+ LaunchedEffect(asset) {
+ withContext(Dispatchers.IO) {
+ bitmap = loadBitmapFromAssets(context, asset)
+ }
+ }
+ return bitmap
+}
+
+private fun loadBitmapFromAssets(context: Context, asset: String?): Bitmap? {
asset ?: return null
return try {
- val inputSteam = context.assets.open("Images/WeAccept/${asset.fileName}")
+ val inputSteam = context.assets.open(asset)
val opts = BitmapFactory.Options()
opts.inScaled = true
opts.inDensity = 1606
- val bitmap = BitmapFactory.decodeStream(inputSteam, null, opts)
- bitmap?.resizeTo(asset.width, asset.height)
+ BitmapFactory.decodeStream(inputSteam, null, opts)
} catch (e: Exception) {
null
}
-}
-
-private fun Bitmap.resizeTo(width: Int, height: Int): Bitmap? {
- if (width == width && height == height) {
- return this
- }
- val resizedBitmap = Bitmap.createScaledBitmap(this, width, height, true)
- recycle()
- return resizedBitmap
}
\ No newline at end of file