@@ -334,6 +334,12 @@ class EaseView(context: Context) : ReactViewGroup(context) {
334334 if (mask and MASK_BACKGROUND_COLOR != 0 && initialAnimateBackgroundColor != backgroundColor) {
335335 animateBackgroundColor(initialAnimateBackgroundColor, backgroundColor, getTransitionConfigForProperty(PROP_INDEX_BACKGROUND_COLOR ), loop = true )
336336 }
337+
338+ // If all per-property configs were 'none', no animations were queued.
339+ // Fire onTransitionEnd immediately to match the scalar 'none' contract.
340+ if (pendingBatchAnimationCount == 0 ) {
341+ onTransitionEnd?.invoke(true )
342+ }
337343 } else {
338344 // No initial animation — set target values directly (skip non-animated)
339345 if (mask and MASK_OPACITY != 0 ) this .alpha = opacity
@@ -363,9 +369,15 @@ class EaseView(context: Context) : ReactViewGroup(context) {
363369 onTransitionEnd?.invoke(true )
364370 } else {
365371 // Subsequent updates: animate changed properties (skip non-animated)
372+ var anyPropertyChanged = false
373+
366374 if (prevOpacity != null && mask and MASK_OPACITY != 0 && prevOpacity != opacity) {
375+ anyPropertyChanged = true
367376 val config = getTransitionConfigForProperty(PROP_INDEX_OPACITY )
368377 if (config.type == " none" ) {
378+ cancelSpringForProperty(" alpha" )
379+ runningAnimators[" alpha" ]?.cancel()
380+ runningAnimators.remove(" alpha" )
369381 this .alpha = opacity
370382 } else {
371383 val from = getCurrentValue(" alpha" )
@@ -374,8 +386,12 @@ class EaseView(context: Context) : ReactViewGroup(context) {
374386 }
375387
376388 if (prevTranslateX != null && mask and MASK_TRANSLATE_X != 0 && prevTranslateX != translateX) {
389+ anyPropertyChanged = true
377390 val config = getTransitionConfigForProperty(PROP_INDEX_TRANSLATE_X )
378391 if (config.type == " none" ) {
392+ cancelSpringForProperty(" translationX" )
393+ runningAnimators[" translationX" ]?.cancel()
394+ runningAnimators.remove(" translationX" )
379395 this .translationX = translateX
380396 } else {
381397 val from = getCurrentValue(" translationX" )
@@ -384,8 +400,12 @@ class EaseView(context: Context) : ReactViewGroup(context) {
384400 }
385401
386402 if (prevTranslateY != null && mask and MASK_TRANSLATE_Y != 0 && prevTranslateY != translateY) {
403+ anyPropertyChanged = true
387404 val config = getTransitionConfigForProperty(PROP_INDEX_TRANSLATE_Y )
388405 if (config.type == " none" ) {
406+ cancelSpringForProperty(" translationY" )
407+ runningAnimators[" translationY" ]?.cancel()
408+ runningAnimators.remove(" translationY" )
389409 this .translationY = translateY
390410 } else {
391411 val from = getCurrentValue(" translationY" )
@@ -394,8 +414,12 @@ class EaseView(context: Context) : ReactViewGroup(context) {
394414 }
395415
396416 if (prevScaleX != null && mask and MASK_SCALE_X != 0 && prevScaleX != scaleX) {
417+ anyPropertyChanged = true
397418 val config = getTransitionConfigForProperty(PROP_INDEX_SCALE_X )
398419 if (config.type == " none" ) {
420+ cancelSpringForProperty(" scaleX" )
421+ runningAnimators[" scaleX" ]?.cancel()
422+ runningAnimators.remove(" scaleX" )
399423 this .scaleX = scaleX
400424 } else {
401425 val from = getCurrentValue(" scaleX" )
@@ -404,8 +428,12 @@ class EaseView(context: Context) : ReactViewGroup(context) {
404428 }
405429
406430 if (prevScaleY != null && mask and MASK_SCALE_Y != 0 && prevScaleY != scaleY) {
431+ anyPropertyChanged = true
407432 val config = getTransitionConfigForProperty(PROP_INDEX_SCALE_Y )
408433 if (config.type == " none" ) {
434+ cancelSpringForProperty(" scaleY" )
435+ runningAnimators[" scaleY" ]?.cancel()
436+ runningAnimators.remove(" scaleY" )
409437 this .scaleY = scaleY
410438 } else {
411439 val from = getCurrentValue(" scaleY" )
@@ -414,8 +442,12 @@ class EaseView(context: Context) : ReactViewGroup(context) {
414442 }
415443
416444 if (prevRotate != null && mask and MASK_ROTATE != 0 && prevRotate != rotate) {
445+ anyPropertyChanged = true
417446 val config = getTransitionConfigForProperty(PROP_INDEX_ROTATE )
418447 if (config.type == " none" ) {
448+ cancelSpringForProperty(" rotation" )
449+ runningAnimators[" rotation" ]?.cancel()
450+ runningAnimators.remove(" rotation" )
419451 this .rotation = rotate
420452 } else {
421453 val from = getCurrentValue(" rotation" )
@@ -424,8 +456,12 @@ class EaseView(context: Context) : ReactViewGroup(context) {
424456 }
425457
426458 if (prevRotateX != null && mask and MASK_ROTATE_X != 0 && prevRotateX != rotateX) {
459+ anyPropertyChanged = true
427460 val config = getTransitionConfigForProperty(PROP_INDEX_ROTATE_X )
428461 if (config.type == " none" ) {
462+ cancelSpringForProperty(" rotationX" )
463+ runningAnimators[" rotationX" ]?.cancel()
464+ runningAnimators.remove(" rotationX" )
429465 this .rotationX = rotateX
430466 } else {
431467 val from = getCurrentValue(" rotationX" )
@@ -434,8 +470,12 @@ class EaseView(context: Context) : ReactViewGroup(context) {
434470 }
435471
436472 if (prevRotateY != null && mask and MASK_ROTATE_Y != 0 && prevRotateY != rotateY) {
473+ anyPropertyChanged = true
437474 val config = getTransitionConfigForProperty(PROP_INDEX_ROTATE_Y )
438475 if (config.type == " none" ) {
476+ cancelSpringForProperty(" rotationY" )
477+ runningAnimators[" rotationY" ]?.cancel()
478+ runningAnimators.remove(" rotationY" )
439479 this .rotationY = rotateY
440480 } else {
441481 val from = getCurrentValue(" rotationY" )
@@ -444,8 +484,11 @@ class EaseView(context: Context) : ReactViewGroup(context) {
444484 }
445485
446486 if (prevBorderRadius != null && mask and MASK_BORDER_RADIUS != 0 && prevBorderRadius != borderRadius) {
487+ anyPropertyChanged = true
447488 val config = getTransitionConfigForProperty(PROP_INDEX_BORDER_RADIUS )
448489 if (config.type == " none" ) {
490+ runningAnimators[" animateBorderRadius" ]?.cancel()
491+ runningAnimators.remove(" animateBorderRadius" )
449492 setAnimateBorderRadius(borderRadius)
450493 } else {
451494 val from = getCurrentValue(" animateBorderRadius" )
@@ -454,13 +497,22 @@ class EaseView(context: Context) : ReactViewGroup(context) {
454497 }
455498
456499 if (prevBackgroundColor != null && mask and MASK_BACKGROUND_COLOR != 0 && prevBackgroundColor != backgroundColor) {
500+ anyPropertyChanged = true
457501 val config = getTransitionConfigForProperty(PROP_INDEX_BACKGROUND_COLOR )
458502 if (config.type == " none" ) {
503+ runningAnimators[" backgroundColor" ]?.cancel()
504+ runningAnimators.remove(" backgroundColor" )
459505 applyBackgroundColor(backgroundColor)
460506 } else {
461507 animateBackgroundColor(getCurrentBackgroundColor(), backgroundColor, config)
462508 }
463509 }
510+
511+ // If per-property arrays are populated and all changed properties resolved
512+ // to 'none', no animations were queued. Fire onTransitionEnd immediately.
513+ if (hasPerPropertyArrays() && anyPropertyChanged && pendingBatchAnimationCount == 0 ) {
514+ onTransitionEnd?.invoke(true )
515+ }
464516 }
465517
466518 prevOpacity = opacity
@@ -549,6 +601,17 @@ class EaseView(context: Context) : ReactViewGroup(context) {
549601 config : TransitionConfig ,
550602 loop : Boolean = false
551603 ) {
604+ if (config.type == " none" ) {
605+ // Set immediately — cancel any running animation for this property
606+ cancelSpringForProperty(propertyName)
607+ runningAnimators[propertyName]?.cancel()
608+ runningAnimators.remove(propertyName)
609+ ObjectAnimator .ofFloat(this , propertyName, toValue).apply {
610+ duration = 0
611+ start()
612+ }
613+ return
614+ }
552615 if (config.type == " spring" && viewProperty != null ) {
553616 animateSpring(viewProperty, toValue, config)
554617 } else {
@@ -605,11 +668,13 @@ class EaseView(context: Context) : ReactViewGroup(context) {
605668 private fun animateSpring (viewProperty : DynamicAnimation .ViewProperty , toValue : Float , config : TransitionConfig ) {
606669 cancelTimingForViewProperty(viewProperty)
607670
608- val existingSpring = runningSpringAnimations[viewProperty]
609- if (existingSpring != null && existingSpring.isRunning) {
610- existingSpring.animateToFinalPosition(toValue)
611- return
671+ // Cancel any existing spring so we get a fresh end listener with the current batchId.
672+ runningSpringAnimations[viewProperty]?.let { existing ->
673+ if (existing.isRunning) {
674+ existing.cancel()
675+ }
612676 }
677+ runningSpringAnimations.remove(viewProperty)
613678
614679 val batchId = animationBatchId
615680 pendingBatchAnimationCount++
0 commit comments