react-native-ease 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Ease.podspec +20 -0
- package/LICENSE +20 -0
- package/README.md +411 -0
- package/android/build.gradle +68 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/ease/EasePackage.kt +17 -0
- package/android/src/main/java/com/ease/EaseView.kt +541 -0
- package/android/src/main/java/com/ease/EaseViewManager.kt +233 -0
- package/ios/EaseView.h +14 -0
- package/ios/EaseView.mm +435 -0
- package/lib/module/EaseView.js +186 -0
- package/lib/module/EaseView.js.map +1 -0
- package/lib/module/EaseViewNativeComponent.ts +68 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/EaseView.d.ts +33 -0
- package/lib/typescript/src/EaseView.d.ts.map +1 -0
- package/lib/typescript/src/EaseViewNativeComponent.d.ts +38 -0
- package/lib/typescript/src/EaseViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +4 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +66 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +187 -0
- package/src/EaseView.tsx +256 -0
- package/src/EaseViewNativeComponent.ts +68 -0
- package/src/index.tsx +13 -0
- package/src/types.ts +78 -0
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
package com.ease
|
|
2
|
+
|
|
3
|
+
import android.animation.Animator
|
|
4
|
+
import android.animation.AnimatorListenerAdapter
|
|
5
|
+
import android.animation.ObjectAnimator
|
|
6
|
+
import android.content.Context
|
|
7
|
+
import android.graphics.Outline
|
|
8
|
+
import android.view.View
|
|
9
|
+
import android.view.ViewOutlineProvider
|
|
10
|
+
import android.view.animation.PathInterpolator
|
|
11
|
+
import androidx.dynamicanimation.animation.DynamicAnimation
|
|
12
|
+
import androidx.dynamicanimation.animation.SpringAnimation
|
|
13
|
+
import androidx.dynamicanimation.animation.SpringForce
|
|
14
|
+
import com.facebook.react.views.view.ReactViewGroup
|
|
15
|
+
import kotlin.math.sqrt
|
|
16
|
+
|
|
17
|
+
class EaseView(context: Context) : ReactViewGroup(context) {
|
|
18
|
+
|
|
19
|
+
// --- Previous animate values (for change detection) ---
|
|
20
|
+
private var prevOpacity: Float? = null
|
|
21
|
+
private var prevTranslateX: Float? = null
|
|
22
|
+
private var prevTranslateY: Float? = null
|
|
23
|
+
private var prevScaleX: Float? = null
|
|
24
|
+
private var prevScaleY: Float? = null
|
|
25
|
+
private var prevRotate: Float? = null
|
|
26
|
+
private var prevRotateX: Float? = null
|
|
27
|
+
private var prevRotateY: Float? = null
|
|
28
|
+
private var prevBorderRadius: Float? = null
|
|
29
|
+
|
|
30
|
+
// --- First mount tracking ---
|
|
31
|
+
private var isFirstMount: Boolean = true
|
|
32
|
+
|
|
33
|
+
// --- Transition config (set by ViewManager) ---
|
|
34
|
+
var transitionType: String = "timing"
|
|
35
|
+
var transitionDuration: Int = 300
|
|
36
|
+
var transitionEasingBezier: FloatArray = floatArrayOf(0.42f, 0f, 0.58f, 1.0f)
|
|
37
|
+
var transitionDamping: Float = 15.0f
|
|
38
|
+
var transitionStiffness: Float = 120.0f
|
|
39
|
+
var transitionMass: Float = 1.0f
|
|
40
|
+
var transitionLoop: String = "none"
|
|
41
|
+
|
|
42
|
+
// --- Transform origin (0–1 fractions) ---
|
|
43
|
+
var transformOriginX: Float = 0.5f
|
|
44
|
+
set(value) {
|
|
45
|
+
field = value
|
|
46
|
+
applyTransformOrigin()
|
|
47
|
+
}
|
|
48
|
+
var transformOriginY: Float = 0.5f
|
|
49
|
+
set(value) {
|
|
50
|
+
field = value
|
|
51
|
+
applyTransformOrigin()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// --- Border radius (hardware-accelerated via outline clipping) ---
|
|
55
|
+
// Animated via ObjectAnimator("borderRadius") — setter invalidates outline each frame.
|
|
56
|
+
private var _borderRadius: Float = 0f
|
|
57
|
+
|
|
58
|
+
fun getBorderRadius(): Float = _borderRadius
|
|
59
|
+
fun setBorderRadius(value: Float) {
|
|
60
|
+
if (_borderRadius != value) {
|
|
61
|
+
_borderRadius = value
|
|
62
|
+
if (value > 0f) {
|
|
63
|
+
clipToOutline = true
|
|
64
|
+
} else {
|
|
65
|
+
clipToOutline = false
|
|
66
|
+
}
|
|
67
|
+
invalidateOutline()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// --- Hardware layer ---
|
|
72
|
+
var useHardwareLayer: Boolean = false
|
|
73
|
+
|
|
74
|
+
// --- Event callback ---
|
|
75
|
+
var onTransitionEnd: ((finished: Boolean) -> Unit)? = null
|
|
76
|
+
private var activeAnimationCount: Int = 0
|
|
77
|
+
private var animationBatchId: Int = 0
|
|
78
|
+
private var pendingBatchAnimationCount: Int = 0
|
|
79
|
+
private var anyInterrupted: Boolean = false
|
|
80
|
+
private var savedLayerType: Int = View.LAYER_TYPE_NONE
|
|
81
|
+
|
|
82
|
+
// --- Initial animate values (set by ViewManager) ---
|
|
83
|
+
var initialAnimateOpacity: Float = 1.0f
|
|
84
|
+
var initialAnimateTranslateX: Float = 0.0f
|
|
85
|
+
var initialAnimateTranslateY: Float = 0.0f
|
|
86
|
+
var initialAnimateScaleX: Float = 1.0f
|
|
87
|
+
var initialAnimateScaleY: Float = 1.0f
|
|
88
|
+
var initialAnimateRotate: Float = 0.0f
|
|
89
|
+
var initialAnimateRotateX: Float = 0.0f
|
|
90
|
+
var initialAnimateRotateY: Float = 0.0f
|
|
91
|
+
var initialAnimateBorderRadius: Float = 0.0f
|
|
92
|
+
|
|
93
|
+
// --- Pending animate values (buffered per-view, applied in onAfterUpdateTransaction) ---
|
|
94
|
+
var pendingOpacity: Float = 1.0f
|
|
95
|
+
var pendingTranslateX: Float = 0.0f
|
|
96
|
+
var pendingTranslateY: Float = 0.0f
|
|
97
|
+
var pendingScaleX: Float = 1.0f
|
|
98
|
+
var pendingScaleY: Float = 1.0f
|
|
99
|
+
var pendingRotate: Float = 0.0f
|
|
100
|
+
var pendingRotateX: Float = 0.0f
|
|
101
|
+
var pendingRotateY: Float = 0.0f
|
|
102
|
+
var pendingBorderRadius: Float = 0.0f
|
|
103
|
+
|
|
104
|
+
// --- Running animations ---
|
|
105
|
+
private val runningAnimators = mutableMapOf<String, ObjectAnimator>()
|
|
106
|
+
private val runningSpringAnimations = mutableMapOf<DynamicAnimation.ViewProperty, SpringAnimation>()
|
|
107
|
+
|
|
108
|
+
// --- Animated properties bitmask (set by ViewManager) ---
|
|
109
|
+
var animatedProperties: Int = 0
|
|
110
|
+
|
|
111
|
+
// --- Easing interpolators (lazy singletons shared across all instances) ---
|
|
112
|
+
companion object {
|
|
113
|
+
// Bitmask flags — must match JS constants
|
|
114
|
+
const val MASK_OPACITY = 1 shl 0
|
|
115
|
+
const val MASK_TRANSLATE_X = 1 shl 1
|
|
116
|
+
const val MASK_TRANSLATE_Y = 1 shl 2
|
|
117
|
+
const val MASK_SCALE_X = 1 shl 3
|
|
118
|
+
const val MASK_SCALE_Y = 1 shl 4
|
|
119
|
+
const val MASK_ROTATE = 1 shl 5
|
|
120
|
+
const val MASK_ROTATE_X = 1 shl 6
|
|
121
|
+
const val MASK_ROTATE_Y = 1 shl 7
|
|
122
|
+
const val MASK_BORDER_RADIUS = 1 shl 8
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
init {
|
|
126
|
+
// Set camera distance for 3D perspective rotations (rotateX/rotateY)
|
|
127
|
+
cameraDistance = resources.displayMetrics.density * 850f
|
|
128
|
+
|
|
129
|
+
// ViewOutlineProvider reads _borderRadius dynamically — set once, invalidated on each frame.
|
|
130
|
+
outlineProvider = object : ViewOutlineProvider() {
|
|
131
|
+
override fun getOutline(view: View, outline: Outline) {
|
|
132
|
+
outline.setRoundRect(0, 0, view.width, view.height, _borderRadius)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// --- Hardware layer management ---
|
|
138
|
+
|
|
139
|
+
private fun onEaseAnimationStart() {
|
|
140
|
+
if (activeAnimationCount == 0 && useHardwareLayer) {
|
|
141
|
+
savedLayerType = layerType
|
|
142
|
+
setLayerType(View.LAYER_TYPE_HARDWARE, null)
|
|
143
|
+
}
|
|
144
|
+
activeAnimationCount++
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
private fun onEaseAnimationEnd() {
|
|
148
|
+
activeAnimationCount--
|
|
149
|
+
if (activeAnimationCount <= 0) {
|
|
150
|
+
activeAnimationCount = 0
|
|
151
|
+
if (useHardwareLayer && layerType == View.LAYER_TYPE_HARDWARE) {
|
|
152
|
+
setLayerType(savedLayerType, null)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// --- Transform origin ---
|
|
158
|
+
|
|
159
|
+
fun applyTransformOrigin() {
|
|
160
|
+
if (width > 0 && height > 0) {
|
|
161
|
+
pivotX = width * transformOriginX
|
|
162
|
+
pivotY = height * transformOriginY
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
167
|
+
super.onLayout(changed, left, top, right, bottom)
|
|
168
|
+
applyTransformOrigin()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
fun applyPendingAnimateValues() {
|
|
172
|
+
applyAnimateValues(pendingOpacity, pendingTranslateX, pendingTranslateY, pendingScaleX, pendingScaleY, pendingRotate, pendingRotateX, pendingRotateY, pendingBorderRadius)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private fun applyAnimateValues(
|
|
176
|
+
opacity: Float,
|
|
177
|
+
translateX: Float,
|
|
178
|
+
translateY: Float,
|
|
179
|
+
scaleX: Float,
|
|
180
|
+
scaleY: Float,
|
|
181
|
+
rotate: Float,
|
|
182
|
+
rotateX: Float,
|
|
183
|
+
rotateY: Float,
|
|
184
|
+
borderRadius: Float
|
|
185
|
+
) {
|
|
186
|
+
if (pendingBatchAnimationCount > 0) {
|
|
187
|
+
onTransitionEnd?.invoke(false)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
animationBatchId++
|
|
191
|
+
pendingBatchAnimationCount = 0
|
|
192
|
+
anyInterrupted = false
|
|
193
|
+
|
|
194
|
+
// Bitmask: which properties are animated. Non-animated = let style handle.
|
|
195
|
+
val mask = animatedProperties
|
|
196
|
+
|
|
197
|
+
if (isFirstMount) {
|
|
198
|
+
isFirstMount = false
|
|
199
|
+
|
|
200
|
+
val hasInitialAnimation =
|
|
201
|
+
(mask and MASK_OPACITY != 0 && initialAnimateOpacity != opacity) ||
|
|
202
|
+
(mask and MASK_TRANSLATE_X != 0 && initialAnimateTranslateX != translateX) ||
|
|
203
|
+
(mask and MASK_TRANSLATE_Y != 0 && initialAnimateTranslateY != translateY) ||
|
|
204
|
+
(mask and MASK_SCALE_X != 0 && initialAnimateScaleX != scaleX) ||
|
|
205
|
+
(mask and MASK_SCALE_Y != 0 && initialAnimateScaleY != scaleY) ||
|
|
206
|
+
(mask and MASK_ROTATE != 0 && initialAnimateRotate != rotate) ||
|
|
207
|
+
(mask and MASK_ROTATE_X != 0 && initialAnimateRotateX != rotateX) ||
|
|
208
|
+
(mask and MASK_ROTATE_Y != 0 && initialAnimateRotateY != rotateY) ||
|
|
209
|
+
(mask and MASK_BORDER_RADIUS != 0 && initialAnimateBorderRadius != borderRadius)
|
|
210
|
+
|
|
211
|
+
if (hasInitialAnimation) {
|
|
212
|
+
// Set initial values for animated properties
|
|
213
|
+
if (mask and MASK_OPACITY != 0) this.alpha = initialAnimateOpacity
|
|
214
|
+
if (mask and MASK_TRANSLATE_X != 0) this.translationX = initialAnimateTranslateX
|
|
215
|
+
if (mask and MASK_TRANSLATE_Y != 0) this.translationY = initialAnimateTranslateY
|
|
216
|
+
if (mask and MASK_SCALE_X != 0) this.scaleX = initialAnimateScaleX
|
|
217
|
+
if (mask and MASK_SCALE_Y != 0) this.scaleY = initialAnimateScaleY
|
|
218
|
+
if (mask and MASK_ROTATE != 0) this.rotation = initialAnimateRotate
|
|
219
|
+
if (mask and MASK_ROTATE_X != 0) this.rotationX = initialAnimateRotateX
|
|
220
|
+
if (mask and MASK_ROTATE_Y != 0) this.rotationY = initialAnimateRotateY
|
|
221
|
+
if (mask and MASK_BORDER_RADIUS != 0) setBorderRadius(initialAnimateBorderRadius)
|
|
222
|
+
|
|
223
|
+
// Animate properties that differ from initial to target
|
|
224
|
+
if (mask and MASK_OPACITY != 0 && initialAnimateOpacity != opacity) {
|
|
225
|
+
animateProperty("alpha", DynamicAnimation.ALPHA, initialAnimateOpacity, opacity, loop = true)
|
|
226
|
+
}
|
|
227
|
+
if (mask and MASK_TRANSLATE_X != 0 && initialAnimateTranslateX != translateX) {
|
|
228
|
+
animateProperty("translationX", DynamicAnimation.TRANSLATION_X, initialAnimateTranslateX, translateX, loop = true)
|
|
229
|
+
}
|
|
230
|
+
if (mask and MASK_TRANSLATE_Y != 0 && initialAnimateTranslateY != translateY) {
|
|
231
|
+
animateProperty("translationY", DynamicAnimation.TRANSLATION_Y, initialAnimateTranslateY, translateY, loop = true)
|
|
232
|
+
}
|
|
233
|
+
if (mask and MASK_SCALE_X != 0 && initialAnimateScaleX != scaleX) {
|
|
234
|
+
animateProperty("scaleX", DynamicAnimation.SCALE_X, initialAnimateScaleX, scaleX, loop = true)
|
|
235
|
+
}
|
|
236
|
+
if (mask and MASK_SCALE_Y != 0 && initialAnimateScaleY != scaleY) {
|
|
237
|
+
animateProperty("scaleY", DynamicAnimation.SCALE_Y, initialAnimateScaleY, scaleY, loop = true)
|
|
238
|
+
}
|
|
239
|
+
if (mask and MASK_ROTATE != 0 && initialAnimateRotate != rotate) {
|
|
240
|
+
animateProperty("rotation", DynamicAnimation.ROTATION, initialAnimateRotate, rotate, loop = true)
|
|
241
|
+
}
|
|
242
|
+
if (mask and MASK_ROTATE_X != 0 && initialAnimateRotateX != rotateX) {
|
|
243
|
+
animateProperty("rotationX", DynamicAnimation.ROTATION_X, initialAnimateRotateX, rotateX, loop = true)
|
|
244
|
+
}
|
|
245
|
+
if (mask and MASK_ROTATE_Y != 0 && initialAnimateRotateY != rotateY) {
|
|
246
|
+
animateProperty("rotationY", DynamicAnimation.ROTATION_Y, initialAnimateRotateY, rotateY, loop = true)
|
|
247
|
+
}
|
|
248
|
+
if (mask and MASK_BORDER_RADIUS != 0 && initialAnimateBorderRadius != borderRadius) {
|
|
249
|
+
animateProperty("borderRadius", null, initialAnimateBorderRadius, borderRadius, loop = true)
|
|
250
|
+
}
|
|
251
|
+
} else {
|
|
252
|
+
// No initial animation — set target values directly (skip non-animated)
|
|
253
|
+
if (mask and MASK_OPACITY != 0) this.alpha = opacity
|
|
254
|
+
if (mask and MASK_TRANSLATE_X != 0) this.translationX = translateX
|
|
255
|
+
if (mask and MASK_TRANSLATE_Y != 0) this.translationY = translateY
|
|
256
|
+
if (mask and MASK_SCALE_X != 0) this.scaleX = scaleX
|
|
257
|
+
if (mask and MASK_SCALE_Y != 0) this.scaleY = scaleY
|
|
258
|
+
if (mask and MASK_ROTATE != 0) this.rotation = rotate
|
|
259
|
+
if (mask and MASK_ROTATE_X != 0) this.rotationX = rotateX
|
|
260
|
+
if (mask and MASK_ROTATE_Y != 0) this.rotationY = rotateY
|
|
261
|
+
if (mask and MASK_BORDER_RADIUS != 0) setBorderRadius(borderRadius)
|
|
262
|
+
}
|
|
263
|
+
} else if (transitionType == "none") {
|
|
264
|
+
// No transition — set values immediately, cancel running animations
|
|
265
|
+
cancelAllAnimations()
|
|
266
|
+
if (mask and MASK_OPACITY != 0) this.alpha = opacity
|
|
267
|
+
if (mask and MASK_TRANSLATE_X != 0) this.translationX = translateX
|
|
268
|
+
if (mask and MASK_TRANSLATE_Y != 0) this.translationY = translateY
|
|
269
|
+
if (mask and MASK_SCALE_X != 0) this.scaleX = scaleX
|
|
270
|
+
if (mask and MASK_SCALE_Y != 0) this.scaleY = scaleY
|
|
271
|
+
if (mask and MASK_ROTATE != 0) this.rotation = rotate
|
|
272
|
+
if (mask and MASK_ROTATE_X != 0) this.rotationX = rotateX
|
|
273
|
+
if (mask and MASK_ROTATE_Y != 0) this.rotationY = rotateY
|
|
274
|
+
if (mask and MASK_BORDER_RADIUS != 0) setBorderRadius(borderRadius)
|
|
275
|
+
onTransitionEnd?.invoke(true)
|
|
276
|
+
} else {
|
|
277
|
+
// Subsequent updates: animate changed properties (skip non-animated)
|
|
278
|
+
if (prevOpacity != null && mask and MASK_OPACITY != 0 && prevOpacity != opacity) {
|
|
279
|
+
val from = getCurrentValue("alpha")
|
|
280
|
+
animateProperty("alpha", DynamicAnimation.ALPHA, from, opacity)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (prevTranslateX != null && mask and MASK_TRANSLATE_X != 0 && prevTranslateX != translateX) {
|
|
284
|
+
val from = getCurrentValue("translationX")
|
|
285
|
+
animateProperty("translationX", DynamicAnimation.TRANSLATION_X, from, translateX)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (prevTranslateY != null && mask and MASK_TRANSLATE_Y != 0 && prevTranslateY != translateY) {
|
|
289
|
+
val from = getCurrentValue("translationY")
|
|
290
|
+
animateProperty("translationY", DynamicAnimation.TRANSLATION_Y, from, translateY)
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (prevScaleX != null && mask and MASK_SCALE_X != 0 && prevScaleX != scaleX) {
|
|
294
|
+
val from = getCurrentValue("scaleX")
|
|
295
|
+
animateProperty("scaleX", DynamicAnimation.SCALE_X, from, scaleX)
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (prevScaleY != null && mask and MASK_SCALE_Y != 0 && prevScaleY != scaleY) {
|
|
299
|
+
val from = getCurrentValue("scaleY")
|
|
300
|
+
animateProperty("scaleY", DynamicAnimation.SCALE_Y, from, scaleY)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (prevRotate != null && mask and MASK_ROTATE != 0 && prevRotate != rotate) {
|
|
304
|
+
val from = getCurrentValue("rotation")
|
|
305
|
+
animateProperty("rotation", DynamicAnimation.ROTATION, from, rotate)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (prevRotateX != null && mask and MASK_ROTATE_X != 0 && prevRotateX != rotateX) {
|
|
309
|
+
val from = getCurrentValue("rotationX")
|
|
310
|
+
animateProperty("rotationX", DynamicAnimation.ROTATION_X, from, rotateX)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (prevRotateY != null && mask and MASK_ROTATE_Y != 0 && prevRotateY != rotateY) {
|
|
314
|
+
val from = getCurrentValue("rotationY")
|
|
315
|
+
animateProperty("rotationY", DynamicAnimation.ROTATION_Y, from, rotateY)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (prevBorderRadius != null && mask and MASK_BORDER_RADIUS != 0 && prevBorderRadius != borderRadius) {
|
|
319
|
+
val from = getCurrentValue("borderRadius")
|
|
320
|
+
animateProperty("borderRadius", null, from, borderRadius)
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
prevOpacity = opacity
|
|
325
|
+
prevTranslateX = translateX
|
|
326
|
+
prevTranslateY = translateY
|
|
327
|
+
prevScaleX = scaleX
|
|
328
|
+
prevScaleY = scaleY
|
|
329
|
+
prevRotate = rotate
|
|
330
|
+
prevRotateX = rotateX
|
|
331
|
+
prevRotateY = rotateY
|
|
332
|
+
prevBorderRadius = borderRadius
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private fun getCurrentValue(propertyName: String): Float = when (propertyName) {
|
|
336
|
+
"alpha" -> this.alpha
|
|
337
|
+
"translationX" -> this.translationX
|
|
338
|
+
"translationY" -> this.translationY
|
|
339
|
+
"scaleX" -> this.scaleX
|
|
340
|
+
"scaleY" -> this.scaleY
|
|
341
|
+
"rotation" -> this.rotation
|
|
342
|
+
"rotationX" -> this.rotationX
|
|
343
|
+
"rotationY" -> this.rotationY
|
|
344
|
+
"borderRadius" -> getBorderRadius()
|
|
345
|
+
else -> 0f
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private fun animateProperty(
|
|
349
|
+
propertyName: String,
|
|
350
|
+
viewProperty: DynamicAnimation.ViewProperty?,
|
|
351
|
+
fromValue: Float,
|
|
352
|
+
toValue: Float,
|
|
353
|
+
loop: Boolean = false
|
|
354
|
+
) {
|
|
355
|
+
if (transitionType == "spring" && viewProperty != null) {
|
|
356
|
+
animateSpring(viewProperty, toValue)
|
|
357
|
+
} else {
|
|
358
|
+
animateTiming(propertyName, fromValue, toValue, loop)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
private fun animateTiming(propertyName: String, fromValue: Float, toValue: Float, loop: Boolean = false) {
|
|
363
|
+
cancelSpringForProperty(propertyName)
|
|
364
|
+
runningAnimators[propertyName]?.cancel()
|
|
365
|
+
|
|
366
|
+
val batchId = animationBatchId
|
|
367
|
+
pendingBatchAnimationCount++
|
|
368
|
+
|
|
369
|
+
val animator = ObjectAnimator.ofFloat(this, propertyName, fromValue, toValue).apply {
|
|
370
|
+
duration = transitionDuration.toLong()
|
|
371
|
+
interpolator = PathInterpolator(
|
|
372
|
+
transitionEasingBezier[0], transitionEasingBezier[1],
|
|
373
|
+
transitionEasingBezier[2], transitionEasingBezier[3]
|
|
374
|
+
)
|
|
375
|
+
if (loop && transitionLoop != "none") {
|
|
376
|
+
repeatCount = ObjectAnimator.INFINITE
|
|
377
|
+
repeatMode = if (transitionLoop == "reverse") {
|
|
378
|
+
ObjectAnimator.REVERSE
|
|
379
|
+
} else {
|
|
380
|
+
ObjectAnimator.RESTART
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
addListener(object : AnimatorListenerAdapter() {
|
|
384
|
+
private var cancelled = false
|
|
385
|
+
override fun onAnimationStart(animation: Animator) {
|
|
386
|
+
this@EaseView.onEaseAnimationStart()
|
|
387
|
+
}
|
|
388
|
+
override fun onAnimationCancel(animation: Animator) {
|
|
389
|
+
cancelled = true
|
|
390
|
+
}
|
|
391
|
+
override fun onAnimationEnd(animation: Animator) {
|
|
392
|
+
this@EaseView.onEaseAnimationEnd()
|
|
393
|
+
if (batchId == animationBatchId) {
|
|
394
|
+
if (cancelled) anyInterrupted = true
|
|
395
|
+
pendingBatchAnimationCount--
|
|
396
|
+
if (pendingBatchAnimationCount <= 0) {
|
|
397
|
+
onTransitionEnd?.invoke(!anyInterrupted)
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
})
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
runningAnimators[propertyName] = animator
|
|
405
|
+
animator.start()
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
private fun animateSpring(viewProperty: DynamicAnimation.ViewProperty, toValue: Float) {
|
|
409
|
+
cancelTimingForViewProperty(viewProperty)
|
|
410
|
+
|
|
411
|
+
val existingSpring = runningSpringAnimations[viewProperty]
|
|
412
|
+
if (existingSpring != null && existingSpring.isRunning) {
|
|
413
|
+
existingSpring.animateToFinalPosition(toValue)
|
|
414
|
+
return
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
val batchId = animationBatchId
|
|
418
|
+
pendingBatchAnimationCount++
|
|
419
|
+
|
|
420
|
+
val dampingRatio = (transitionDamping / (2.0f * sqrt(transitionStiffness * transitionMass)))
|
|
421
|
+
.coerceAtLeast(0.01f)
|
|
422
|
+
|
|
423
|
+
val spring = SpringAnimation(this, viewProperty).apply {
|
|
424
|
+
spring = SpringForce(toValue).apply {
|
|
425
|
+
this.dampingRatio = dampingRatio
|
|
426
|
+
this.stiffness = transitionStiffness
|
|
427
|
+
}
|
|
428
|
+
addUpdateListener { _, _, _ ->
|
|
429
|
+
// First update — enable hardware layer
|
|
430
|
+
if (activeAnimationCount == 0) {
|
|
431
|
+
this@EaseView.onEaseAnimationStart()
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
addEndListener { _, canceled, _, _ ->
|
|
435
|
+
this@EaseView.onEaseAnimationEnd()
|
|
436
|
+
if (batchId == animationBatchId) {
|
|
437
|
+
if (canceled) anyInterrupted = true
|
|
438
|
+
pendingBatchAnimationCount--
|
|
439
|
+
if (pendingBatchAnimationCount <= 0) {
|
|
440
|
+
onTransitionEnd?.invoke(!anyInterrupted)
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
onEaseAnimationStart()
|
|
447
|
+
runningSpringAnimations[viewProperty] = spring
|
|
448
|
+
spring.start()
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
private fun cancelAllAnimations() {
|
|
452
|
+
for (animator in runningAnimators.values) {
|
|
453
|
+
animator.cancel()
|
|
454
|
+
}
|
|
455
|
+
runningAnimators.clear()
|
|
456
|
+
for (spring in runningSpringAnimations.values) {
|
|
457
|
+
if (spring.isRunning) {
|
|
458
|
+
spring.cancel()
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
runningSpringAnimations.clear()
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
private fun cancelTimingForViewProperty(viewProperty: DynamicAnimation.ViewProperty) {
|
|
465
|
+
val propertyName = when (viewProperty) {
|
|
466
|
+
DynamicAnimation.ALPHA -> "alpha"
|
|
467
|
+
DynamicAnimation.TRANSLATION_X -> "translationX"
|
|
468
|
+
DynamicAnimation.TRANSLATION_Y -> "translationY"
|
|
469
|
+
DynamicAnimation.SCALE_X -> "scaleX"
|
|
470
|
+
DynamicAnimation.SCALE_Y -> "scaleY"
|
|
471
|
+
DynamicAnimation.ROTATION -> "rotation"
|
|
472
|
+
DynamicAnimation.ROTATION_X -> "rotationX"
|
|
473
|
+
DynamicAnimation.ROTATION_Y -> "rotationY"
|
|
474
|
+
else -> return
|
|
475
|
+
}
|
|
476
|
+
runningAnimators[propertyName]?.cancel()
|
|
477
|
+
runningAnimators.remove(propertyName)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
private fun cancelSpringForProperty(propertyName: String) {
|
|
481
|
+
val viewProperty = when (propertyName) {
|
|
482
|
+
"alpha" -> DynamicAnimation.ALPHA
|
|
483
|
+
"translationX" -> DynamicAnimation.TRANSLATION_X
|
|
484
|
+
"translationY" -> DynamicAnimation.TRANSLATION_Y
|
|
485
|
+
"scaleX" -> DynamicAnimation.SCALE_X
|
|
486
|
+
"scaleY" -> DynamicAnimation.SCALE_Y
|
|
487
|
+
"rotation" -> DynamicAnimation.ROTATION
|
|
488
|
+
"rotationX" -> DynamicAnimation.ROTATION_X
|
|
489
|
+
"rotationY" -> DynamicAnimation.ROTATION_Y
|
|
490
|
+
else -> return
|
|
491
|
+
}
|
|
492
|
+
runningSpringAnimations[viewProperty]?.let { spring ->
|
|
493
|
+
if (spring.isRunning) {
|
|
494
|
+
spring.cancel()
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
runningSpringAnimations.remove(viewProperty)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
fun cleanup() {
|
|
501
|
+
for (animator in runningAnimators.values) {
|
|
502
|
+
animator.cancel()
|
|
503
|
+
}
|
|
504
|
+
runningAnimators.clear()
|
|
505
|
+
|
|
506
|
+
for (spring in runningSpringAnimations.values) {
|
|
507
|
+
if (spring.isRunning) {
|
|
508
|
+
spring.cancel()
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
runningSpringAnimations.clear()
|
|
512
|
+
|
|
513
|
+
if (activeAnimationCount > 0 && layerType == View.LAYER_TYPE_HARDWARE) {
|
|
514
|
+
setLayerType(savedLayerType, null)
|
|
515
|
+
}
|
|
516
|
+
activeAnimationCount = 0
|
|
517
|
+
|
|
518
|
+
prevOpacity = null
|
|
519
|
+
prevTranslateX = null
|
|
520
|
+
prevTranslateY = null
|
|
521
|
+
prevScaleX = null
|
|
522
|
+
prevScaleY = null
|
|
523
|
+
prevRotate = null
|
|
524
|
+
prevRotateX = null
|
|
525
|
+
prevRotateY = null
|
|
526
|
+
prevBorderRadius = null
|
|
527
|
+
|
|
528
|
+
this.alpha = 1f
|
|
529
|
+
this.translationX = 0f
|
|
530
|
+
this.translationY = 0f
|
|
531
|
+
this.scaleX = 1f
|
|
532
|
+
this.scaleY = 1f
|
|
533
|
+
this.rotation = 0f
|
|
534
|
+
this.rotationX = 0f
|
|
535
|
+
this.rotationY = 0f
|
|
536
|
+
setBorderRadius(0f)
|
|
537
|
+
|
|
538
|
+
isFirstMount = true
|
|
539
|
+
transitionLoop = "none"
|
|
540
|
+
}
|
|
541
|
+
}
|