Skip to content

Commit 00fa4bf

Browse files
feat: add hardware layer optimization on Android
Enable LAYER_TYPE_HARDWARE during animations so property changes are composited on the RenderThread via a cached GPU texture. On by default, can be disabled with useHardwareLayer={false}. Saves and restores the original layer type. Also add TSDoc to all public types and props.
1 parent f6d2577 commit 00fa4bf

5 files changed

Lines changed: 90 additions & 0 deletions

File tree

android/src/main/java/com/ease/EaseView.kt

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package com.ease
22

3+
import android.animation.Animator
4+
import android.animation.AnimatorListenerAdapter
35
import android.animation.ObjectAnimator
46
import android.animation.TimeInterpolator
57
import android.content.Context
8+
import android.view.View
69
import android.view.animation.LinearInterpolator
710
import android.view.animation.PathInterpolator
811
import androidx.dynamicanimation.animation.DynamicAnimation
@@ -31,6 +34,11 @@ class EaseView(context: Context) : ReactViewGroup(context) {
3134
var transitionStiffness: Float = 120.0f
3235
var transitionMass: Float = 1.0f
3336

37+
// --- Hardware layer ---
38+
var useHardwareLayer: Boolean = true
39+
private var activeAnimationCount: Int = 0
40+
private var savedLayerType: Int = View.LAYER_TYPE_NONE
41+
3442
// --- Initial animate values (set by ViewManager) ---
3543
var initialAnimateOpacity: Float = 1.0f
3644
var initialAnimateTranslateX: Float = 0.0f
@@ -58,6 +66,26 @@ class EaseView(context: Context) : ReactViewGroup(context) {
5866
else -> PathInterpolator(0.42f, 0f, 0.58f, 1.0f)
5967
}
6068

69+
// --- Hardware layer management ---
70+
71+
private fun onEaseAnimationStart() {
72+
if (activeAnimationCount == 0 && useHardwareLayer) {
73+
savedLayerType = layerType
74+
setLayerType(View.LAYER_TYPE_HARDWARE, null)
75+
}
76+
activeAnimationCount++
77+
}
78+
79+
private fun onEaseAnimationEnd() {
80+
activeAnimationCount--
81+
if (activeAnimationCount <= 0) {
82+
activeAnimationCount = 0
83+
if (useHardwareLayer && layerType == View.LAYER_TYPE_HARDWARE) {
84+
setLayerType(savedLayerType, null)
85+
}
86+
}
87+
}
88+
6189
fun applyPendingAnimateValues() {
6290
applyAnimateValues(pendingOpacity, pendingTranslateX, pendingTranslateY, pendingScale, pendingRotate)
6391
}
@@ -192,6 +220,14 @@ class EaseView(context: Context) : ReactViewGroup(context) {
192220
val animator = ObjectAnimator.ofFloat(this, propertyName, fromValue, toValue).apply {
193221
duration = transitionDuration.toLong()
194222
interpolator = getInterpolator(transitionEasing)
223+
addListener(object : AnimatorListenerAdapter() {
224+
override fun onAnimationStart(animation: Animator) {
225+
this@EaseView.onEaseAnimationStart()
226+
}
227+
override fun onAnimationEnd(animation: Animator) {
228+
this@EaseView.onEaseAnimationEnd()
229+
}
230+
})
195231
}
196232

197233
runningAnimators[propertyName] = animator
@@ -215,8 +251,18 @@ class EaseView(context: Context) : ReactViewGroup(context) {
215251
this.dampingRatio = dampingRatio
216252
this.stiffness = transitionStiffness
217253
}
254+
addUpdateListener { _, _, _ ->
255+
// First update — enable hardware layer
256+
if (activeAnimationCount == 0) {
257+
this@EaseView.onEaseAnimationStart()
258+
}
259+
}
260+
addEndListener { _, _, _, _ ->
261+
this@EaseView.onEaseAnimationEnd()
262+
}
218263
}
219264

265+
onEaseAnimationStart()
220266
runningSpringAnimations[viewProperty] = spring
221267
spring.start()
222268
}
@@ -266,6 +312,11 @@ class EaseView(context: Context) : ReactViewGroup(context) {
266312
}
267313
runningSpringAnimations.clear()
268314

315+
if (activeAnimationCount > 0 && layerType == View.LAYER_TYPE_HARDWARE) {
316+
setLayerType(savedLayerType, null)
317+
}
318+
activeAnimationCount = 0
319+
269320
prevOpacity = null
270321
prevTranslateX = null
271322
prevTranslateY = null

android/src/main/java/com/ease/EaseViewManager.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ class EaseViewManager : ReactViewManager() {
100100
view.transitionMass = value
101101
}
102102

103+
// --- Hardware layer ---
104+
105+
@ReactProp(name = "useHardwareLayer", defaultBoolean = true)
106+
fun setUseHardwareLayer(view: EaseView, value: Boolean) {
107+
view.useHardwareLayer = value
108+
}
109+
103110
// --- Lifecycle ---
104111

105112
override fun onAfterUpdateTransaction(view: ReactViewGroup) {

src/EaseView.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,29 @@ const IDENTITY: Required<AnimateProps> = {
1313
};
1414

1515
export type EaseViewProps = Omit<ViewProps, 'style'> & {
16+
/** Target values for animated properties. */
1617
animate?: AnimateProps;
18+
/** Starting values for enter animations. Animates to `animate` on mount. */
1719
initialAnimate?: AnimateProps;
20+
/** Animation configuration (timing or spring). */
1821
transition?: Transition;
22+
/**
23+
* Use a hardware layer during animations on Android for smoother rendering.
24+
* The view is rasterized to a GPU texture while animating so property changes
25+
* are composited on the RenderThread. No-op on iOS where Core Animation
26+
* already runs off the main thread.
27+
* @default true
28+
*/
29+
useHardwareLayer?: boolean;
30+
/** Non-animated styles (layout, colors, borders, etc.). `opacity` and `transform` are excluded — use `animate` instead. */
1931
style?: EaseViewStyle | EaseViewStyle[];
2032
};
2133

2234
export function EaseView({
2335
animate,
2436
initialAnimate,
2537
transition,
38+
useHardwareLayer = true,
2639
style,
2740
...rest
2841
}: EaseViewProps) {
@@ -81,6 +94,7 @@ export function EaseView({
8194
transitionDamping={transitionDamping}
8295
transitionStiffness={transitionStiffness}
8396
transitionMass={transitionMass}
97+
useHardwareLayer={useHardwareLayer}
8498
{...rest}
8599
/>
86100
);

src/EaseViewNativeComponent.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ export interface NativeProps extends ViewProps {
3030
transitionDamping?: CodegenTypes.WithDefault<CodegenTypes.Float, 15.0>;
3131
transitionStiffness?: CodegenTypes.WithDefault<CodegenTypes.Float, 120.0>;
3232
transitionMass?: CodegenTypes.WithDefault<CodegenTypes.Float, 1.0>;
33+
34+
// Android hardware layer optimization (no-op on iOS)
35+
useHardwareLayer?: CodegenTypes.WithDefault<boolean, true>;
3336
}
3437

3538
export default codegenNativeComponent<NativeProps>(

src/types.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,39 @@
1+
/** Easing curve for timing animations. */
12
export type EasingType = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut';
23

4+
/** Timing-based transition with fixed duration and easing curve. */
35
export type TimingTransition = {
46
type: 'timing';
7+
/** Duration in milliseconds. @default 300 */
58
duration?: number;
9+
/** Easing curve. @default 'easeInOut' */
610
easing?: EasingType;
711
};
812

13+
/** Physics-based spring transition. */
914
export type SpringTransition = {
1015
type: 'spring';
16+
/** Friction — higher values reduce oscillation. @default 15 */
1117
damping?: number;
18+
/** Spring constant — higher values mean faster animation. @default 120 */
1219
stiffness?: number;
20+
/** Mass of the object — higher values mean slower, more momentum. @default 1 */
1321
mass?: number;
1422
};
1523

24+
/** Animation transition configuration. */
1625
export type Transition = TimingTransition | SpringTransition;
1726

27+
/** Animatable view properties. Unspecified properties default to their identity values. */
1828
export type AnimateProps = {
29+
/** View opacity (0–1). @default 1 */
1930
opacity?: number;
31+
/** Horizontal translation in pixels. @default 0 */
2032
translateX?: number;
33+
/** Vertical translation in pixels. @default 0 */
2134
translateY?: number;
35+
/** Uniform scale factor. @default 1 */
2236
scale?: number;
37+
/** Rotation in degrees. @default 0 */
2338
rotate?: number;
2439
};

0 commit comments

Comments
 (0)