Skip to content

Commit

Permalink
feat: implement native auto play for android
Browse files Browse the repository at this point in the history
  • Loading branch information
matinzd committed Feb 8, 2023
1 parent 902d704 commit 63f71aa
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 81 deletions.
1 change: 1 addition & 0 deletions packages/core/android/build.gradle
Expand Up @@ -112,6 +112,7 @@ def kotlin_version = getExtOrDefault('kotlinVersion', project.properties['lottie

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'

//noinspection GradleDynamicVersion
implementation 'com.facebook.react:react-native:+' // From node_modules
Expand Down
Expand Up @@ -2,6 +2,7 @@ package com.airbnb.android.react.lottie

import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.View
import android.view.View.OnAttachStateChangeListener
import android.widget.ImageView
Expand All @@ -11,13 +12,16 @@ import com.airbnb.lottie.RenderMode
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.common.MapBuilder
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.util.RNLog
import kotlinx.coroutines.*
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.URL
import kotlin.concurrent.thread

internal object LottieAnimationViewManagerImpl {
const val REACT_CLASS = "LottieAnimationView"
val coroutineScope = CoroutineScope(Dispatchers.Main)

@JvmStatic
val exportedViewConstants: Map<String, Any>
Expand Down Expand Up @@ -135,12 +139,19 @@ internal object LottieAnimationViewManagerImpl {
urlString: String?,
propManagersMap: LottieAnimationViewPropertyManager
) {
thread {
BufferedReader(InputStreamReader(URL(urlString).openStream())).useLines {
Handler(Looper.getMainLooper()).post {
propManagersMap.animationJson = it.toString()
propManagersMap.commitChanges()
CoroutineScope(Dispatchers.Main).launch {
try {
val jsonString = withContext(Dispatchers.IO) {
URL(urlString).openStream().use {
BufferedReader(InputStreamReader(it)).useLines { lines ->
lines.joinToString("\n")
}
}
}
propManagersMap.animationJson = jsonString
propManagersMap.commitChanges()
} catch (e: Exception) {
RNLog.l("Error while loading animation from URL")
}
}
}
Expand All @@ -158,7 +169,7 @@ internal object LottieAnimationViewManagerImpl {
var mode: ImageView.ScaleType? = null
when (resizeMode) {
"cover" -> {
mode = ImageView.ScaleType.CENTER_CROP
mode = ImageView.ScaleType.FIT_XY
}
"contain" -> {
mode = ImageView.ScaleType.CENTER_INSIDE
Expand Down Expand Up @@ -226,6 +237,14 @@ internal object LottieAnimationViewManagerImpl {
viewManager.loop = loop
}

@JvmStatic
fun setAutoPlay(
autoPlay: Boolean,
viewManager: LottieAnimationViewPropertyManager
) {
viewManager.autoPlay = autoPlay
}

@JvmStatic
fun setEnableMergePaths(
enableMergePaths: Boolean,
Expand Down
@@ -1,28 +1,28 @@
package com.airbnb.android.react.lottie

import com.airbnb.lottie.LottieAnimationView
import java.lang.ref.WeakReference
import android.graphics.ColorFilter
import android.widget.ImageView
import com.facebook.react.bridge.ReadableArray
import com.airbnb.lottie.RenderMode
import com.airbnb.lottie.TextDelegate
import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieDrawable
import com.facebook.react.bridge.ReadableType
import com.facebook.react.bridge.ColorPropConverter
import android.graphics.ColorFilter
import com.airbnb.lottie.LottieProperty
import com.airbnb.lottie.RenderMode
import com.airbnb.lottie.SimpleColorFilter
import com.airbnb.lottie.TextDelegate
import com.airbnb.lottie.model.KeyPath
import com.airbnb.lottie.value.LottieValueCallback
import com.airbnb.lottie.LottieProperty
import com.facebook.react.bridge.ColorPropConverter
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableType
import java.lang.ref.WeakReference
import java.util.regex.Pattern

/**
* Class responsible for applying the properties to the LottieView.
* The way react-native works makes it impossible to predict in which order properties will be set,
* also some of the properties of the LottieView needs to be set simultaneously.
* Class responsible for applying the properties to the LottieView. The way react-native works makes
* it impossible to predict in which order properties will be set, also some of the properties of
* the LottieView needs to be set simultaneously.
*
* To solve this, instance of this class accumulates all changes to the view and applies them at
* the end of react transaction, so it could control how changes are applied.
* To solve this, instance of this class accumulates all changes to the view and applies them at the
* end of react transaction, so it could control how changes are applied.
*/
class LottieAnimationViewPropertyManager(view: LottieAnimationView) {
private val viewWeakReference: WeakReference<LottieAnimationView>
Expand All @@ -48,26 +48,26 @@ class LottieAnimationViewPropertyManager(view: LottieAnimationView) {
var animationJson: String? = null
var progress: Float? = null
var loop: Boolean? = null
var autoPlay: Boolean? = null
var speed: Float? = null

init {
viewWeakReference = WeakReference(view)
}

/**
* Updates the view with changed fields.
* Majority of the properties here are independent so they are has to be reset to null
* as soon as view is updated with the value.
* Updates the view with changed fields. Majority of the properties here are independent so they
* are has to be reset to null as soon as view is updated with the value.
*
* The only exception from this rule is the group of the properties for the animation.
* For now this is animationName and cacheStrategy. These two properties are should be set
* The only exception from this rule is the group of the properties for the animation. For now
* this is animationName and cacheStrategy. These two properties are should be set
* simultaneously if the dirty flag is set.
*/
fun commitChanges() {
val view = viewWeakReference.get() ?: return

textFilters?.let {
if(it.size() > 0) {
if (it.size() > 0) {
val textDelegate = TextDelegate(view)
for (i in 0 until textFilters!!.size()) {
val current = textFilters!!.getMap(i)
Expand Down Expand Up @@ -99,6 +99,13 @@ class LottieAnimationViewPropertyManager(view: LottieAnimationView) {
loop = null
}

autoPlay?.let {
if (it && !view.isAnimating) {
view.playAnimation()
}
autoPlay = null
}

speed?.let {
view.speed = it
speed = null
Expand All @@ -114,9 +121,7 @@ class LottieAnimationViewPropertyManager(view: LottieAnimationView) {
renderMode = null
}

layerType?.let {
view.setLayerType(it, null)
}
layerType?.let { view.setLayerType(it, null) }

imageAssetsFolder?.let {
view.imageAssetsFolder = it
Expand All @@ -133,11 +138,12 @@ class LottieAnimationViewPropertyManager(view: LottieAnimationView) {
for (i in 0 until it.size()) {
val current = it.getMap(i)

val color: Int = if (current.getType("color") == ReadableType.Map) {
ColorPropConverter.getColor(current.getMap("color"), view.context)
} else {
current.getInt("color")
}
val color: Int =
if (current.getType("color") == ReadableType.Map) {
ColorPropConverter.getColor(current.getMap("color"), view.context)
} else {
current.getInt("color")
}

val path = current.getString("keypath")
val pathWithGlobStar = "$path.**"
Expand All @@ -153,4 +159,4 @@ class LottieAnimationViewPropertyManager(view: LottieAnimationView) {
}
}
}
}
}
Expand Up @@ -13,6 +13,6 @@ class LottiePackage : ReactPackage {
}

override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return listOf(LottieAnimationViewManager())
return listOf(LottieAnimationViewManager(reactContext))
}
}
@@ -1,6 +1,7 @@
package com.airbnb.android.react.lottie

import android.animation.Animator
import android.util.Log
import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.setColorFilters
import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.setEnableMergePaths
import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.setHardwareAcceleration
Expand All @@ -14,22 +15,28 @@ import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.setSourceN
import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.setSourceURL
import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.setSpeed
import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.setTextFilters
import com.airbnb.android.react.lottie.LottieAnimationViewManagerImpl.setAutoPlay
import com.airbnb.lottie.LottieAnimationView
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.uimanager.SimpleViewManager
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.uimanager.ViewManagerDelegate
import com.facebook.react.uimanager.annotations.ReactProp
import com.facebook.react.uimanager.events.Event
import com.facebook.react.uimanager.events.RCTEventEmitter
import com.facebook.react.uimanager.events.RCTModernEventEmitter
import com.facebook.react.viewmanagers.LottieAnimationViewManagerDelegate
import com.facebook.react.viewmanagers.LottieAnimationViewManagerInterface
import java.util.*

@ReactModule(name = LottieAnimationViewManagerImpl.REACT_CLASS)
class LottieAnimationViewManager : SimpleViewManager<LottieAnimationView>(),
class LottieAnimationViewManager(val reactContext: ReactContext) :
SimpleViewManager<LottieAnimationView>(),
LottieAnimationViewManagerInterface<LottieAnimationView> {
private val propManagersMap =
WeakHashMap<LottieAnimationView, LottieAnimationViewPropertyManager>()
Expand All @@ -52,16 +59,16 @@ class LottieAnimationViewManager : SimpleViewManager<LottieAnimationView>(),
val event = Arguments.createMap()
event.putBoolean("isCancelled", isCancelled)

val screenContext = view.context
if (screenContext is ThemedReactContext) {
screenContext.getJSModule(RCTModernEventEmitter::class.java)
?.receiveEvent(
screenContext.surfaceId,
view.id,
"animationFinish",
event
)
}
val screenContext = view.context as ThemedReactContext

Log.d("Lottie", "view surface id ${screenContext.surfaceId} - view id ${view.id}")
reactContext.getJSModule(RCTModernEventEmitter::class.java)
?.receiveEvent(
screenContext.surfaceId,
view.id,
"animationFinish",
event
)
}

override fun getDelegate(): ViewManagerDelegate<LottieAnimationView> {
Expand All @@ -84,11 +91,13 @@ class LottieAnimationViewManager : SimpleViewManager<LottieAnimationView>(),
}

override fun onAnimationEnd(animation: Animator) {
sendOnAnimationFinishEvent(view, false)
// TODO: fix crash
// sendOnAnimationFinishEvent(view, false)
}

override fun onAnimationCancel(animation: Animator) {
sendOnAnimationFinishEvent(view, true)
// TODO: fix crash
// sendOnAnimationFinishEvent(view, true)
}

override fun onAnimationRepeat(animation: Animator) {
Expand Down Expand Up @@ -176,6 +185,11 @@ class LottieAnimationViewManager : SimpleViewManager<LottieAnimationView>(),
setLoop(loop, getOrCreatePropertyManager(view))
}

@ReactProp(name = "autoPlay")
override fun setAutoPlay(view: LottieAnimationView, autoPlay: Boolean) {
setAutoPlay(autoPlay, getOrCreatePropertyManager(view))
}

@ReactProp(name = "imageAssetsFolder")
override fun setImageAssetsFolder(view: LottieAnimationView, imageAssetsFolder: String?) {
setImageAssetsFolder(imageAssetsFolder, getOrCreatePropertyManager(view))
Expand Down

0 comments on commit 63f71aa

Please sign in to comment.