[Compose] Add support for images
diff --git a/.idea/misc.xml b/.idea/misc.xml
index c3085c1..d72f78c 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -26,6 +26,8 @@
<entry key="../../../../layout/compose-model-1615661124216.xml" value="0.28631756756756754" />
<entry key="../../../../layout/compose-model-1615661338631.xml" value="1.0" />
<entry key="../../../../layout/compose-model-1615661344364.xml" value="0.28607594936708863" />
+ <entry key="../../../../layout/custom_preview.xml" value="1.0" />
+ <entry key="issue-repro/src/main/res/layout/issue_repro_activity.xml" value="0.2545289855072464" />
</map>
</option>
</component>
diff --git a/.idea/modules.xml b/.idea/modules.xml
index 2590511..ec33ca5 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -4,6 +4,7 @@
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/lottie-android.iml" filepath="$PROJECT_DIR$/.idea/lottie-android.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/issue-repro/lottie-android.issue-repro.iml" filepath="$PROJECT_DIR$/.idea/modules/issue-repro/lottie-android.issue-repro.iml" />
+ <module fileurl="file://$PROJECT_DIR$/.idea/modules/issue-repro-compose/lottie-android.issue-repro-compose.iml" filepath="$PROJECT_DIR$/.idea/modules/issue-repro-compose/lottie-android.issue-repro-compose.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/lottie/lottie-android.lottie.iml" filepath="$PROJECT_DIR$/.idea/modules/lottie/lottie-android.lottie.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/lottie-compose/lottie-android.lottie-compose.iml" filepath="$PROJECT_DIR$/.idea/modules/lottie-compose/lottie-android.lottie-compose.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/sample/lottie-android.sample.iml" filepath="$PROJECT_DIR$/.idea/modules/sample/lottie-android.sample.iml" />
diff --git a/issue-repro-compose/build.gradle b/issue-repro-compose/build.gradle
new file mode 100755
index 0000000..72f8700
--- /dev/null
+++ b/issue-repro-compose/build.gradle
@@ -0,0 +1,41 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileSdkVersion 30
+ defaultConfig {
+ applicationId "com.airbnb.lottie.issues.compose"
+ minSdkVersion 21
+ targetSdkVersion 30
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ buildFeatures {
+ compose true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion composeVersion
+ kotlinCompilerVersion kotlinVersion
+ }
+}
+
+dependencies {
+ implementation project(':lottie-compose')
+ implementation 'androidx.appcompat:appcompat:1.3.0-beta01'
+ implementation 'androidx.activity:activity-compose:1.3.0-alpha04'
+ implementation "androidx.compose.ui:ui:$composeVersion"
+ implementation "androidx.compose.material:material:$composeVersion"
+ implementation "androidx.compose.material:material-icons-extended:$composeVersion"
+ implementation "androidx.compose.ui:ui-tooling:$composeVersion"
+}
diff --git a/issue-repro-compose/src/main/AndroidManifest.xml b/issue-repro-compose/src/main/AndroidManifest.xml
new file mode 100755
index 0000000..3a75e49
--- /dev/null
+++ b/issue-repro-compose/src/main/AndroidManifest.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.airbnb.lottie.issues.compose">
+
+ <application
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:theme="@style/Theme.AppCompat.Light.NoActionBar">
+ <activity android:name=".ComposeIssueReproActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
\ No newline at end of file
diff --git a/issue-repro-compose/src/main/java/com/airbnb/lottie/issues/compose/ComposeIssueReproActivity.kt b/issue-repro-compose/src/main/java/com/airbnb/lottie/issues/compose/ComposeIssueReproActivity.kt
new file mode 100755
index 0000000..4e55a5d
--- /dev/null
+++ b/issue-repro-compose/src/main/java/com/airbnb/lottie/issues/compose/ComposeIssueReproActivity.kt
@@ -0,0 +1,19 @@
+package com.airbnb.lottie.issues.compose
+
+import android.os.Bundle
+import androidx.activity.compose.setContent
+import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.runtime.Composable
+
+class ComposeIssueReproActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ Content()
+ }
+ }
+
+ @Composable
+ fun Content() {
+ }
+}
diff --git a/issue-repro-compose/src/main/res/mipmap-hdpi/ic_launcher.png b/issue-repro-compose/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100755
index 0000000..898f3ed
--- /dev/null
+++ b/issue-repro-compose/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/issue-repro-compose/src/main/res/mipmap-mdpi/ic_launcher.png b/issue-repro-compose/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100755
index 0000000..64ba76f
--- /dev/null
+++ b/issue-repro-compose/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/issue-repro-compose/src/main/res/mipmap-xhdpi/ic_launcher.png b/issue-repro-compose/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100755
index 0000000..e5ed465
--- /dev/null
+++ b/issue-repro-compose/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/issue-repro-compose/src/main/res/mipmap-xxhdpi/ic_launcher.png b/issue-repro-compose/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100755
index 0000000..b0907ca
--- /dev/null
+++ b/issue-repro-compose/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/issue-repro-compose/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/issue-repro-compose/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100755
index 0000000..2c18de9
--- /dev/null
+++ b/issue-repro-compose/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/issue-repro-compose/src/main/res/values/strings.xml b/issue-repro-compose/src/main/res/values/strings.xml
new file mode 100755
index 0000000..dd8717f
--- /dev/null
+++ b/issue-repro-compose/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+<resources>
+ <string name="app_name">Lottie Issue</string>
+</resources>
diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/ImageAssetDelegateSetter.kt b/lottie-compose/src/main/java/com/airbnb/lottie/ImageAssetDelegateSetter.kt
new file mode 100644
index 0000000..222e45d
--- /dev/null
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/ImageAssetDelegateSetter.kt
@@ -0,0 +1,10 @@
+package com.airbnb.lottie
+
+import com.airbnb.lottie.manager.ImageAssetManager
+
+/**
+ * Proxy for this internal API.
+ */
+internal fun LottieDrawable.setImageAssetManager(imageAssetManager: ImageAssetManager?) {
+ this.setImageAssetManager(imageAssetManager)
+}
\ No newline at end of file
diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt
index a242575..57feaa7 100644
--- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimation.kt
@@ -13,6 +13,8 @@
import com.airbnb.lottie.LottieComposition
import com.airbnb.lottie.LottieCompositionFactory
import com.airbnb.lottie.LottieDrawable
+import com.airbnb.lottie.manager.ImageAssetManager
+import com.airbnb.lottie.setImageAssetManager
import java.io.FileInputStream
import java.util.concurrent.TimeUnit
import java.util.zip.ZipInputStream
@@ -80,6 +82,16 @@
modifier: Modifier = Modifier,
) {
val drawable = remember { LottieDrawable() }
+ var imageAssetManager: ImageAssetManager? by remember { mutableStateOf(null) }
+
+ if (composition?.hasImages() == true) {
+ val context = LocalContext.current
+ LaunchedEffect(context, composition, state.imageAssetsFolder, state.imageAssetDelegate) {
+ imageAssetManager = ImageAssetManager(context, state.imageAssetsFolder, state.imageAssetDelegate, composition.images)
+ }
+ } else {
+ imageAssetManager = null
+ }
SideEffect {
drawable.composition = composition
@@ -119,10 +131,8 @@
.maintainAspectRatio(composition)
) {
drawIntoCanvas { canvas ->
- drawable.progress = state.progress
- drawable.setOutlineMasksAndMattes(state.outlineMasksAndMattes)
- drawable.isApplyingOpacityToLayersEnabled = state.applyOpacityToLayers
- drawable.enableMergePathsForKitKatAndAbove(state.enableMergePaths)
+ state.applyTo(drawable)
+ drawable.setImageAssetManager(imageAssetManager)
withTransform({
scale(size.width / composition.bounds.width().toFloat(), size.height / composition.bounds.height().toFloat(), Offset.Zero)
}) {
diff --git a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimationState.kt b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimationState.kt
index b410285..6801bd0 100644
--- a/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimationState.kt
+++ b/lottie-compose/src/main/java/com/airbnb/lottie/compose/LottieAnimationState.kt
@@ -1,10 +1,8 @@
package com.airbnb.lottie.compose
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
+import androidx.compose.runtime.*
+import com.airbnb.lottie.ImageAssetDelegate
+import com.airbnb.lottie.LottieDrawable
/**
* Create a [LottieAnimationState] and remember it
@@ -13,13 +11,18 @@
* @param repeatCount Initial value for [LottieAnimationState.repeatCount]
* @param initialProgress Initial value for [LottieAnimationState.progress]
* @param enableMergePaths Initial value for [LottieAnimationState.enableMergePaths]
+ * @param imageAssetsFolder Initial value for [LottieAnimationState.imageAssetsFolder]
+ * @param imageAssetDelegate Initial value for [LottieAnimationState.imageAssetDelegate]
*/
@Composable
fun rememberLottieAnimationState(
autoPlay: Boolean = true,
repeatCount: Int = 0,
initialProgress: Float = 0f,
- enableMergePaths: Boolean = true
+ enableMergePaths: Boolean = true,
+ imageAssetsFolder: String? = null,
+ imageAssetDelegate: ImageAssetDelegate? = null,
+
): LottieAnimationState {
// Use rememberSavedInstanceState so you can pause/resume animations
return remember(repeatCount, autoPlay) {
@@ -27,7 +30,9 @@
isPlaying = autoPlay,
repeatCount = repeatCount,
initialProgress = initialProgress,
- enableMergePaths = enableMergePaths
+ enableMergePaths = enableMergePaths,
+ imageAssetsFolder = imageAssetsFolder,
+ imageAssetDelegate = imageAssetDelegate,
)
}
}
@@ -39,6 +44,8 @@
* @param repeatCount Initial value for [repeatCount]
* @param initialProgress Initial value for [progress]
* @param enableMergePaths Initial value for [enableMergePaths]
+ * @param imageAssetsFolder Initial value for [LottieAnimationState.imageAssetsFolder]
+ * @param imageAssetDelegate Initial value for [LottieAnimationState.imageAssetDelegate]
*
* @see rememberLottieAnimationState
*/
@@ -46,7 +53,9 @@
isPlaying: Boolean,
repeatCount: Int = 0,
initialProgress: Float = 0f,
- enableMergePaths: Boolean = true
+ enableMergePaths: Boolean = true,
+ imageAssetsFolder: String? = null,
+ imageAssetDelegate: ImageAssetDelegate? = null,
) {
var progress by mutableStateOf(initialProgress)
@@ -100,6 +109,35 @@
*/
var enableMergePaths by mutableStateOf(enableMergePaths)
+ /**
+ * If you use image assets, you must explicitly specify the folder in assets/ in which they are
+ * located because bodymovin uses the name filenames across all compositions (img_#).
+ * Do NOT rename the images themselves.
+ * <p>
+ * If your images are located in src/main/assets/airbnb_loader/ then set this to "airbnb_loader".
+ * <p>
+ * <p>
+ * Be wary if you are using many images, however. Lottie is designed to work with vector shapes
+ * from After Effects. If your images look like they could be represented with vector shapes,
+ * see if it is possible to convert them to shape layers and re-export your animation. Check
+ * the documentation at https://airbnb.io/lottie for more information about importing shapes from
+ * Sketch or Illustrator to avoid this.
+ */
+ var imageAssetsFolder by mutableStateOf(imageAssetsFolder)
+
+ /**
+ * Use this if you can't bundle images with your app. This may be useful if you download the
+ * animations from the network or have the images saved to an SD Card. In that case, Lottie
+ * will defer the loading of the bitmap to this delegate.
+ * <p>
+ * Be wary if you are using many images, however. Lottie is designed to work with vector shapes
+ * from After Effects. If your images look like they could be represented with vector shapes,
+ * see if it is possible to convert them to shape layers and re-export your animation. Check
+ * the documentation at https://airbnb.io/lottie for more information about importing shapes from
+ * Sketch or Illustrator to avoid this.
+ */
+ var imageAssetDelegate by mutableStateOf(imageAssetDelegate)
+
internal fun updateFrame(frame: Int) {
_frame.value = frame
}
@@ -107,4 +145,11 @@
fun toggleIsPlaying() {
isPlaying = !isPlaying
}
+
+ internal fun applyTo(drawable: LottieDrawable) {
+ drawable.progress = progress
+ drawable.setOutlineMasksAndMattes(outlineMasksAndMattes)
+ drawable.isApplyingOpacityToLayersEnabled = applyOpacityToLayers
+ drawable.enableMergePathsForKitKatAndAbove(enableMergePaths)
+ }
}
\ No newline at end of file
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
index 8bfecd9..14ff5ac 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieDrawable.java
@@ -23,6 +23,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
import com.airbnb.lottie.manager.FontAssetManager;
import com.airbnb.lottie.manager.ImageAssetManager;
@@ -75,6 +76,15 @@
}
}
};
+
+ /**
+ * ImageAssetManager created externally. By Compose, for example.
+ */
+ @Nullable
+ private ImageAssetManager imageAssetManagerOverride;
+ /**
+ * ImageAssetManager created automatically by Lottie for views.
+ */
@Nullable
private ImageAssetManager imageAssetManager;
@Nullable
@@ -1095,7 +1105,18 @@
return null;
}
+ /**
+ * Use by Lottie internally when outside of a normal View tree such as for Jetpack Compose.
+ */
+ @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+ void setImageAssetManager(@Nullable ImageAssetManager imageAssetManager) {
+ this.imageAssetManagerOverride = imageAssetManager;
+ }
+
private ImageAssetManager getImageAssetManager() {
+ if (imageAssetManagerOverride != null) {
+ return imageAssetManagerOverride;
+ }
if (getCallback() == null) {
// We can't get a bitmap since we can't get a Context from the callback.
return null;
diff --git a/lottie/src/main/java/com/airbnb/lottie/manager/ImageAssetManager.java b/lottie/src/main/java/com/airbnb/lottie/manager/ImageAssetManager.java
index 7182b70..833cf61 100644
--- a/lottie/src/main/java/com/airbnb/lottie/manager/ImageAssetManager.java
+++ b/lottie/src/main/java/com/airbnb/lottie/manager/ImageAssetManager.java
@@ -24,16 +24,16 @@
private static final Object bitmapHashLock = new Object();
private final Context context;
- private String imagesFolder;
+ private final String imagesFolder;
@Nullable private ImageAssetDelegate delegate;
private final Map<String, LottieImageAsset> imageAssets;
public ImageAssetManager(Drawable.Callback callback, String imagesFolder,
ImageAssetDelegate delegate, Map<String, LottieImageAsset> imageAssets) {
- this.imagesFolder = imagesFolder;
- if (!TextUtils.isEmpty(imagesFolder) &&
- this.imagesFolder.charAt(this.imagesFolder.length() - 1) != '/') {
- this.imagesFolder += '/';
+ if (!TextUtils.isEmpty(imagesFolder) && imagesFolder.charAt(imagesFolder.length() - 1) != '/') {
+ this.imagesFolder = imagesFolder + '/';
+ } else {
+ this.imagesFolder = imagesFolder;
}
if (!(callback instanceof View)) {
@@ -48,6 +48,17 @@
setDelegate(delegate);
}
+ public ImageAssetManager(Context context, String imagesFolder, ImageAssetDelegate delegate, Map<String, LottieImageAsset> imageAssets) {
+ this.context = context;
+ if (!TextUtils.isEmpty(imagesFolder) && imagesFolder.charAt(imagesFolder.length() - 1) != '/') {
+ this.imagesFolder = imagesFolder + '/';
+ } else {
+ this.imagesFolder = imagesFolder;
+ }
+ this.imageAssets = imageAssets;
+ setDelegate(delegate);
+ }
+
public void setDelegate(@Nullable ImageAssetDelegate assetDelegate) {
this.delegate = assetDelegate;
}
diff --git a/sample-compose/build.gradle b/sample-compose/build.gradle
index 6c57c64..8b6535d 100644
--- a/sample-compose/build.gradle
+++ b/sample-compose/build.gradle
@@ -56,20 +56,20 @@
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.activity:activity-ktx:1.2.1'
- implementation 'androidx.activity:activity-compose:1.3.0-alpha03'
+ implementation 'androidx.activity:activity-compose:1.3.0-alpha04'
implementation 'androidx.appcompat:appcompat:1.3.0-beta01'
implementation 'com.google.android.material:material:1.3.0'
implementation "androidx.compose.ui:ui:$composeVersion"
implementation "androidx.compose.material:material:$composeVersion"
implementation "androidx.compose.material:material-icons-extended:$composeVersion"
implementation "androidx.compose.ui:ui-tooling:$composeVersion"
- implementation "androidx.navigation:navigation-compose:1.0.0-alpha08"
+ implementation "androidx.navigation:navigation-compose:1.0.0-alpha09"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0'
implementation "androidx.navigation:navigation-ui-ktx:2.3.4"
- implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.2'
implementation "com.google.dagger:dagger:$daggerVersion"
@@ -77,7 +77,7 @@
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
- implementation "dev.chrisbanes.accompanist:accompanist-coil:0.6.0"
+ implementation "dev.chrisbanes.accompanist:accompanist-coil:0.6.1"
implementation 'com.airbnb.android:mavericks:2.1.0'
implementation 'com.airbnb.android:mavericks-compose:2.1.0-alpha01'
diff --git a/settings.gradle b/settings.gradle
index 428b963..a895a44 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -3,3 +3,4 @@
include ':sample'
include ':sample-compose'
include ':issue-repro'
+include ':issue-repro-compose'