Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ open class NativeProxy {
intBuffer: IntArray,
doubleBuffer: DoubleArray,
) {
cssPlatformTransitionsManager.onPropsWrittenSynchronously()
SynchronousPropsBufferParser.parse(intBuffer, doubleBuffer) { viewTag, props ->
if (BuildConfig.IS_REACT_NATIVE_86_OR_NEWER) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import android.util.FloatProperty
import android.view.Choreographer
import android.view.View
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.UIManager
import com.facebook.react.bridge.UIManagerListener
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.common.annotations.UnstableReactNativeAPI
import com.facebook.react.fabric.FabricUIManager
import com.facebook.react.uimanager.IllegalViewOperationException
import java.lang.ref.WeakReference
Expand All @@ -19,6 +22,34 @@ internal class CSSPlatformTransitionsManager(
private val animationTimestamp: () -> Long,
) {
private val animators = HashMap<Key, RunningTransition>()

/**
* React can overwrite an animated value only on frames where it wrote props, so the
* pre-draw repair is skipped on all others. Everything here runs on the UI thread.
*/
private var reactWroteSinceLastDraw = true

@OptIn(UnstableReactNativeAPI::class)
private val mountListener =
object : UIManagerListener {
override fun willDispatchViewUpdates(uiManager: UIManager) = Unit

override fun willMountItems(uiManager: UIManager) = Unit

override fun didMountItems(uiManager: UIManager) {
reactWroteSinceLastDraw = true
}

override fun didDispatchMountItems(uiManager: UIManager) = Unit

override fun didScheduleMountItems(uiManager: UIManager) = Unit
}

init {
@OptIn(UnstableReactNativeAPI::class)
fabricUIManager.addUIManagerEventListener(mountListener)
}

private val reconciler = CSSPlatformTransitionReconciler(::repairClobberedValues)
private val startTokens = HashMap<Key, Long>()

Expand Down Expand Up @@ -106,6 +137,8 @@ internal class CSSPlatformTransitionsManager(
// Set before posting: a start already queued would otherwise register a new
// animator and listener behind the cleanup.
invalidated = true
@OptIn(UnstableReactNativeAPI::class)
fabricUIManager.removeUIManagerEventListener(mountListener)
UiThreadUtil.runOnUiThread {
startTokens.clear()
// Snapshot first: cancel() runs onAnimationEnd, which reads the map.
Expand Down Expand Up @@ -200,8 +233,15 @@ internal class CSSPlatformTransitionsManager(
reconciler.track(view)
}

/** Synchronous prop writes run their mount item inline, so no listener reports them. */
fun onPropsWrittenSynchronously() {
reactWroteSinceLastDraw = true
}

/** Re-asserts each animator's own value wherever a commit overwrote it. */
private fun repairClobberedValues(): Boolean {
if (!reactWroteSinceLastDraw) return animators.isNotEmpty()
reactWroteSinceLastDraw = false
animators.values.forEach { running ->
// target is held weakly, so read the View through it rather than keeping one.
val view = running.animator.target as? View ?: return@forEach
Expand Down
Loading