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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,8 @@ When `borderRadius` is in `animate`, any `borderRadius` in `style` is automatica

On Android, background color uses `ValueAnimator.ofArgb()` (timing only — spring is not supported for colors). On iOS, it uses `CAAnimation` on the `backgroundColor` layer key path and supports both timing and spring transitions.

> **Note:** On Android, background color animation uses `ValueAnimator.ofArgb()` which only supports timing transitions. Spring transitions for `backgroundColor` are not supported on Android and will fall back to timing with the default duration. On iOS, both timing and spring transitions work for background color.

When `backgroundColor` is in `animate`, any `backgroundColor` in `style` is automatically stripped to avoid conflicts.

### Animatable Properties
Expand Down
20 changes: 19 additions & 1 deletion android/src/main/java/com/ease/EaseView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class EaseView(context: Context) : ReactViewGroup(context) {
var transitionStiffness: Float = 120.0f
var transitionMass: Float = 1.0f
var transitionLoop: String = "none"
var transitionDelay: Long = 0L

// --- Transform origin (0–1 fractions) ---
var transformOriginX: Float = 0.5f
Expand Down Expand Up @@ -112,6 +113,7 @@ class EaseView(context: Context) : ReactViewGroup(context) {
// --- Running animations ---
private val runningAnimators = mutableMapOf<String, Animator>()
private val runningSpringAnimations = mutableMapOf<DynamicAnimation.ViewProperty, SpringAnimation>()
private val pendingDelayedRunnables = mutableListOf<Runnable>()

// --- Animated properties bitmask (set by ViewManager) ---
var animatedProperties: Int = 0
Expand Down Expand Up @@ -384,6 +386,7 @@ class EaseView(context: Context) : ReactViewGroup(context) {

val animator = ValueAnimator.ofArgb(fromColor, toColor).apply {
duration = transitionDuration.toLong()
startDelay = transitionDelay
interpolator = PathInterpolator(
transitionEasingBezier[0], transitionEasingBezier[1],
transitionEasingBezier[2], transitionEasingBezier[3]
Expand Down Expand Up @@ -443,6 +446,7 @@ class EaseView(context: Context) : ReactViewGroup(context) {

val animator = ObjectAnimator.ofFloat(this, propertyName, fromValue, toValue).apply {
duration = transitionDuration.toLong()
startDelay = transitionDelay
interpolator = PathInterpolator(
transitionEasingBezier[0], transitionEasingBezier[1],
transitionEasingBezier[2], transitionEasingBezier[3]
Expand Down Expand Up @@ -520,10 +524,20 @@ class EaseView(context: Context) : ReactViewGroup(context) {

onEaseAnimationStart()
runningSpringAnimations[viewProperty] = spring
spring.start()
if (transitionDelay > 0) {
val runnable = Runnable { spring.start() }
pendingDelayedRunnables.add(runnable)
postDelayed(runnable, transitionDelay)
} else {
spring.start()
}
}

private fun cancelAllAnimations() {
for (runnable in pendingDelayedRunnables) {
removeCallbacks(runnable)
}
pendingDelayedRunnables.clear()
for (animator in runningAnimators.values) {
animator.cancel()
}
Expand Down Expand Up @@ -573,6 +587,10 @@ class EaseView(context: Context) : ReactViewGroup(context) {
}

fun cleanup() {
for (runnable in pendingDelayedRunnables) {
removeCallbacks(runnable)
}
pendingDelayedRunnables.clear()
for (animator in runningAnimators.values) {
animator.cancel()
}
Expand Down
5 changes: 5 additions & 0 deletions android/src/main/java/com/ease/EaseViewManager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@ class EaseViewManager : ReactViewManager() {
view.transitionLoop = value ?: "none"
}

@ReactProp(name = "transitionDelay", defaultInt = 0)
fun setTransitionDelay(view: EaseView, value: Int) {
view.transitionDelay = value.toLong()
}

// --- Border radius ---

@ReactProp(name = "animateBorderRadius", defaultFloat = 0f)
Expand Down
8 changes: 4 additions & 4 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PODS:
- boost (1.84.0)
- DoubleConversion (1.1.6)
- Ease (0.1.0):
- Ease (0.2.0):
- boost
- DoubleConversion
- fast_float
Expand Down Expand Up @@ -3005,12 +3005,12 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90
DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb
Ease: e0e726adb99768bed143604b0b07661422921519
Ease: 1926ac476b99e4d4bbe830c0f8f7edf5d3990cdb
fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6
FBLazyVector: a293a88992c4c33f0aee184acab0b64a08ff9458
fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd
glog: 5683914934d5b6e4240e497e0f4a3b42d1854183
hermes-engine: 3992289ac7e8cd4a0bb902711038e16b94212779
hermes-engine: a3bc462fbff75c23f8cb35ae50c37c12905b7423
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
RCTDeprecation: 2b70c6e3abe00396cefd8913efbf6a2db01a2b36
RCTRequired: f3540eee8094231581d40c5c6d41b0f170237a81
Expand Down Expand Up @@ -3088,4 +3088,4 @@ SPEC CHECKSUMS:

PODFILE CHECKSUM: b945c918935ce26cb0357d98636dc7184bab408a

COCOAPODS: 1.15.2
COCOAPODS: 1.16.2
23 changes: 23 additions & 0 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,28 @@ function BackgroundColorDemo() {
);
}

function DelayDemo() {
const [active, setActive] = useState(false);
return (
<Section title="Delay">
<EaseView
animate={{ opacity: active ? 1 : 0.3, translateX: active ? 100 : 0 }}
transition={{
type: 'timing',
duration: 400,
delay: 2000,
easing: 'easeOut',
}}
style={styles.box}
/>
<Button
label={active ? 'Reset' : 'Delayed Move'}
onPress={() => setActive((v) => !v)}
/>
</Section>
);
}

function CombinedDemo() {
const [active, setActive] = useState(false);
return (
Expand Down Expand Up @@ -457,6 +479,7 @@ function DemosScreen() {
<StyledCardDemo />
<BorderRadiusDemo />
<BackgroundColorDemo />
<DelayDemo />
<CustomEasingDemo />
<CombinedDemo />
<StyleReRenderDemo />
Expand Down
5 changes: 5 additions & 0 deletions ios/EaseView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@ - (void)applyAnimationForKeyPath:(NSString *)keyPath
toValue:toValue
props:props
loop:loop];
if (props.transitionDelay > 0) {
animation.beginTime =
CACurrentMediaTime() + (props.transitionDelay / 1000.0);
animation.fillMode = kCAFillModeBackwards;
}
[animation setValue:@(_animationBatchId) forKey:@"easeBatchId"];
animation.delegate = self;
[self.layer addAnimation:animation forKey:animationKey];
Expand Down
5 changes: 5 additions & 0 deletions src/EaseView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,10 @@ export function EaseView({
transition?.type === 'spring' ? transition.mass ?? 1 : 1;
const transitionLoop =
transition?.type === 'timing' ? transition.loop ?? 'none' : 'none';
const transitionDelay =
transition?.type === 'timing' || transition?.type === 'spring'
? transition.delay ?? 0
: 0;

const handleTransitionEnd = onTransitionEnd
? (event: { nativeEvent: { finished: boolean } }) =>
Expand Down Expand Up @@ -257,6 +261,7 @@ export function EaseView({
transitionStiffness={transitionStiffness}
transitionMass={transitionMass}
transitionLoop={transitionLoop}
transitionDelay={transitionDelay}
useHardwareLayer={useHardwareLayer}
transformOriginX={transformOrigin?.x ?? 0.5}
transformOriginY={transformOrigin?.y ?? 0.5}
Expand Down
1 change: 1 addition & 0 deletions src/EaseViewNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export interface NativeProps extends ViewProps {
'none' | 'repeat' | 'reverse',
'none'
>;
transitionDelay?: CodegenTypes.WithDefault<CodegenTypes.Int32, 0>;

// Transform origin (0–1 fractions, default center)
transformOriginX?: CodegenTypes.WithDefault<CodegenTypes.Float, 0.5>;
Expand Down
4 changes: 4 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ export type TimingTransition = {
easing?: EasingType;
/** Loop mode — 'repeat' restarts from the beginning, 'reverse' alternates direction. */
loop?: 'repeat' | 'reverse';
/** Delay in milliseconds before the animation starts. @default 0 */
delay?: number;
};

/** Physics-based spring transition. */
Expand All @@ -31,6 +33,8 @@ export type SpringTransition = {
stiffness?: number;
/** Mass of the object — higher values mean slower, more momentum. @default 1 */
mass?: number;
/** Delay in milliseconds before the animation starts. @default 0 */
delay?: number;
};

/** No transition — values are applied immediately without animation. */
Expand Down
Loading