Add support for color state lists and theme attributes in color filters
diff --git a/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java b/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
index 8441d44..18ff5c8 100644
--- a/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
+++ b/lottie/src/main/java/com/airbnb/lottie/LottieAnimationView.java
@@ -3,10 +3,10 @@
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.drawable.Drawable;
import android.os.Build;
@@ -25,6 +25,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.RawRes;
import androidx.annotation.RequiresApi;
+import androidx.appcompat.content.res.AppCompatResources;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.core.view.ViewCompat;
@@ -194,8 +195,9 @@
enableMergePathsForKitKatAndAbove(ta.getBoolean(
R.styleable.LottieAnimationView_lottie_enableMergePathsForKitKatAndAbove, false));
if (ta.hasValue(R.styleable.LottieAnimationView_lottie_colorFilter)) {
- SimpleColorFilter filter = new SimpleColorFilter(
- ta.getColor(R.styleable.LottieAnimationView_lottie_colorFilter, Color.TRANSPARENT));
+ int colorRes = ta.getResourceId(R.styleable.LottieAnimationView_lottie_colorFilter, -1);
+ ColorStateList csl = AppCompatResources.getColorStateList(getContext(), colorRes);
+ SimpleColorFilter filter = new SimpleColorFilter(csl.getDefaultColor());
KeyPath keyPath = new KeyPath("**");
LottieValueCallback<ColorFilter> callback = new LottieValueCallback<ColorFilter>(filter);
addValueCallback(keyPath, LottieProperty.COLOR_FILTER, callback);
diff --git a/sample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.kt b/sample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.kt
index 6db0e7a..9394939 100644
--- a/sample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.kt
+++ b/sample/src/androidTest/java/com/airbnb/lottie/samples/LottieTest.kt
@@ -7,6 +7,7 @@
import android.graphics.*
import android.util.DisplayMetrics
import android.util.Log
+import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
@@ -16,11 +17,11 @@
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
-import androidx.test.rule.ActivityTestRule
import androidx.test.rule.GrantPermissionRule
import com.airbnb.lottie.*
import com.airbnb.lottie.model.KeyPath
import com.airbnb.lottie.model.LottieCompositionCache
+import com.airbnb.lottie.samples.databinding.TestColorFilterBinding
import com.airbnb.lottie.samples.testing.NoCacheLottieAnimationView
import com.airbnb.lottie.samples.views.FilmStripView
import com.airbnb.lottie.value.*
@@ -37,6 +38,7 @@
import org.junit.runner.RunWith
import java.io.File
import java.io.FileInputStream
+import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import java.util.zip.ZipInputStream
@@ -51,7 +53,7 @@
private val application get() = ApplicationProvider.getApplicationContext<Context>()
@get:Rule
- val permissionRule = GrantPermissionRule.grant(
+ val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
)
@@ -82,6 +84,7 @@
}
}
+ @Suppress("BlockingMethodInNonBlockingContext")
@Before
fun setup() {
L.DBG = false
@@ -97,15 +100,16 @@
@Test
fun testAll() = runBlocking {
withTimeout(TimeUnit.MINUTES.toMillis(45)) {
- snapshotFailure()
+ testColorStateListColorFilter()
+ testFailure()
snapshotFrameBoundaries()
snapshotScaleTypes()
testDynamicProperties()
testMarkers()
- snapshotAssets()
+ testAssets()
testText()
testPartialFrameProgress()
- snapshotProdAnimations()
+ testProdAnimations()
testNightMode()
testApplyOpacityToLayer()
testOutlineMasksAndMattes()
@@ -113,7 +117,7 @@
}
}
- private suspend fun snapshotProdAnimations() = coroutineScope {
+ private suspend fun testProdAnimations() = coroutineScope {
val s3Client = AmazonS3Client(BasicAWSCredentials(BuildConfig.S3AccessKey, BuildConfig.S3SecretKey))
val allObjects = s3Client.fetchAllObjects("lottie-prod-animations")
@@ -122,7 +126,7 @@
repeat(4) { snapshotCompositions(compositionsChannel) }
}
- private fun CoroutineScope.downloadAnimations(animations: List<S3ObjectSummary>) = produce<File>(
+ private fun CoroutineScope.downloadAnimations(animations: List<S3ObjectSummary>) = produce(
context = Dispatchers.IO,
capacity = 10
) {
@@ -134,7 +138,8 @@
}
}
- private fun CoroutineScope.parseCompositions(files: ReceiveChannel<File>) = produce<Pair<String, LottieComposition>>(
+ @Suppress("BlockingMethodInNonBlockingContext")
+ private fun CoroutineScope.parseCompositions(files: ReceiveChannel<File>) = produce(
context = Dispatchers.Default,
capacity = 1
) {
@@ -183,8 +188,7 @@
bitmapPool.release(bitmap)
}
- @ObsoleteCoroutinesApi
- private suspend fun snapshotAssets() = coroutineScope {
+ private suspend fun testAssets() = coroutineScope {
val assetsChannel = listAssets()
val compositionsChannel = parseCompositionsFromAssets(assetsChannel)
repeat(4) { snapshotCompositions(compositionsChannel) }
@@ -203,9 +207,8 @@
return assets
}
- @ObsoleteCoroutinesApi
- private fun CoroutineScope.parseCompositionsFromAssets(assets: List<String>) = produce<Pair<String, LottieComposition>>(
- context = newSingleThreadContext("Parsing"),
+ private fun CoroutineScope.parseCompositionsFromAssets(assets: List<String>) = produce(
+ context = Executors.newSingleThreadExecutor().asCoroutineDispatcher(),
capacity = 10
) {
for (asset in assets) {
@@ -216,7 +219,7 @@
}
}
- private suspend fun snapshotFailure() {
+ private suspend fun testFailure() {
val animationView = animationViewPool.acquire()
val semaphore = SuspendingSemaphore(0)
animationView.setFailureListener { semaphore.release() }
@@ -662,13 +665,13 @@
"Color Filter",
KeyPath("**"),
LottieProperty.COLOR_FILTER,
- LottieValueCallback<ColorFilter>(SimpleColorFilter(Color.GREEN)))
+ LottieValueCallback(SimpleColorFilter(Color.GREEN)))
testDynamicProperty(
"Null Color Filter",
KeyPath("**"),
LottieProperty.COLOR_FILTER,
- LottieValueCallback<ColorFilter>(null))
+ LottieValueCallback(null))
testDynamicProperty(
"Opacity interpolation (0)",
@@ -693,7 +696,7 @@
withDrawable("Tests/DynamicGradient.json", "Gradient Colors", "Linear Gradient Fill") { drawable ->
val value = object : LottieValueCallback<Array<Int>>() {
- override fun getValue(frameInfo: LottieFrameInfo<Array<Int>>?): Array<Int>? {
+ override fun getValue(frameInfo: LottieFrameInfo<Array<Int>>?): Array<Int> {
return arrayOf(Color.YELLOW, Color.GREEN)
}
}
@@ -702,7 +705,7 @@
withDrawable("Tests/DynamicGradient.json", "Gradient Colors", "Radial Gradient Fill") { drawable ->
val value = object : LottieValueCallback<Array<Int>>() {
- override fun getValue(frameInfo: LottieFrameInfo<Array<Int>>?): Array<Int>? {
+ override fun getValue(frameInfo: LottieFrameInfo<Array<Int>>?): Array<Int> {
return arrayOf(Color.YELLOW, Color.GREEN)
}
}
@@ -711,7 +714,7 @@
withDrawable("Tests/DynamicGradient.json", "Gradient Colors", "Linear Gradient Stroke") { drawable ->
val value = object : LottieValueCallback<Array<Int>>() {
- override fun getValue(frameInfo: LottieFrameInfo<Array<Int>>?): Array<Int>? {
+ override fun getValue(frameInfo: LottieFrameInfo<Array<Int>>?): Array<Int> {
return arrayOf(Color.YELLOW, Color.GREEN)
}
}
@@ -720,7 +723,7 @@
withDrawable("Tests/DynamicGradient.json", "Gradient Colors", "Radial Gradient Stroke") { drawable ->
val value = object : LottieValueCallback<Array<Int>>() {
- override fun getValue(frameInfo: LottieFrameInfo<Array<Int>>?): Array<Int>? {
+ override fun getValue(frameInfo: LottieFrameInfo<Array<Int>>?): Array<Int> {
return arrayOf(Color.YELLOW, Color.GREEN)
}
}
@@ -994,6 +997,29 @@
}
}
+ private suspend fun testColorStateListColorFilter() {
+ log("Testing color filter")
+ val binding = TestColorFilterBinding.inflate(LayoutInflater.from(application))
+ val composition = LottieCompositionFactory.fromRawResSync(application, R.raw.solid).value!!
+
+ val bitmap = bitmapPool.acquire(1000, 1000)
+ val canvas = Canvas(bitmap)
+ val spec = View.MeasureSpec.makeMeasureSpec(1000, View.MeasureSpec.EXACTLY)
+ binding.root.measure(spec, spec)
+ binding.root.layout(0, 0, 1000, 1000)
+ binding.animationView.setComposition(composition)
+ canvas.drawColor(Color.BLACK, PorterDuff.Mode.CLEAR)
+ withContext(Dispatchers.Main) {
+ binding.root.draw(canvas)
+ }
+ LottieCompositionCache.getInstance().clear()
+ snapshotter.record(bitmap, "ColorFilter", "ColorStateList")
+ snapshotActivityRule.scenario.onActivity { activity ->
+ activity.recordSnapshot("ColorFilter", "ColorStateList")
+ }
+ bitmapPool.release(bitmap)
+ }
+
private suspend fun withDrawable(assetName: String, snapshotName: String, snapshotVariant: String, callback: (LottieDrawable) -> Unit) {
val result = LottieCompositionFactory.fromAssetSync(application, assetName)
val composition = result.value
diff --git a/sample/src/main/res/color/test_color_filter.xml b/sample/src/main/res/color/test_color_filter.xml
new file mode 100644
index 0000000..c7934e6
--- /dev/null
+++ b/sample/src/main/res/color/test_color_filter.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="?attr/colorPrimary" />
+</selector>
\ No newline at end of file
diff --git a/sample/src/main/res/layout/test_color_filter.xml b/sample/src/main/res/layout/test_color_filter.xml
new file mode 100644
index 0000000..fb03ed7
--- /dev/null
+++ b/sample/src/main/res/layout/test_color_filter.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <com.airbnb.lottie.LottieAnimationView
+ android:id="@+id/animation_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ app:lottie_autoPlay="true"
+ app:lottie_colorFilter="@color/test_color_filter"
+ app:lottie_rawRes="@raw/solid"
+ app:lottie_loop="true" />
+</FrameLayout>
\ No newline at end of file
diff --git a/sample/src/main/res/raw/solid.json b/sample/src/main/res/raw/solid.json
new file mode 100644
index 0000000..fd9ce34
--- /dev/null
+++ b/sample/src/main/res/raw/solid.json
@@ -0,0 +1 @@
+{"v":"5.7.4","fr":29.9700012207031,"ip":0,"op":900.000036657751,"w":200,"h":200,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":1,"nm":"Red Solid 1","sr":1,"ks":{"p":{"a":0,"k":[100,100,0],"ix":2,"l":2},"a":{"a":0,"k":[100,100,0],"ix":1,"l":2}},"ao":0,"sw":200,"sh":200,"sc":"#f30000","ip":0,"op":900.000036657751,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file