@onekeyfe/react-native-skeleton 3.0.48 → 3.0.49
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.
|
@@ -36,6 +36,27 @@ class HybridSkeleton(val context: ThemedReactContext) : HybridSkeletonSpec() {
|
|
|
36
36
|
private val maxRetryCount: Int = 10
|
|
37
37
|
private var pendingStartRunnable: Runnable? = null
|
|
38
38
|
|
|
39
|
+
// Guard against high-frequency afterUpdate() ValueAnimator churn (REACT-NATIVE-40K ANR).
|
|
40
|
+
// Fabric calls afterUpdate() on every prop commit, and list/order-book screens update
|
|
41
|
+
// very frequently. Unconditionally restarting the shimmer cancels/rebuilds the
|
|
42
|
+
// ValueAnimator each time (ValueAnimator.cancel -> AnimationHandler.removeCallback ->
|
|
43
|
+
// ArrayList.indexOf), blocking the main thread. We cache a signature of the inputs that
|
|
44
|
+
// actually drive the animation (speed + gradient colors + view size) and only restart
|
|
45
|
+
// when that signature changes. If nothing relevant changed and the shimmer is already
|
|
46
|
+
// running, we leave the existing animator untouched (no cancel, no rebuild).
|
|
47
|
+
private var lastShimmerSignature: String? = null
|
|
48
|
+
private var isShimmerRunning: Boolean = false
|
|
49
|
+
|
|
50
|
+
// Build a signature from every input that decides the shimmer animation:
|
|
51
|
+
// - animationSpeed -> ValueAnimator.duration
|
|
52
|
+
// - gradient colors -> shimmer gradient / draw output
|
|
53
|
+
// - view width/height -> animation travel range (-width..width*2) and draw bounds
|
|
54
|
+
private fun currentShimmerSignature(): String {
|
|
55
|
+
val colors = customGradientColors ?: DEFAULT_GRADIENT_COLORS
|
|
56
|
+
val colorsKey = colors.joinToString(",")
|
|
57
|
+
return "$animationSpeed|$colorsKey|${view.width}x${view.height}"
|
|
58
|
+
}
|
|
59
|
+
|
|
39
60
|
// View with shimmer effect
|
|
40
61
|
override val view: View = object : View(context) {
|
|
41
62
|
override fun onDraw(canvas: Canvas) {
|
|
@@ -137,6 +158,11 @@ class HybridSkeleton(val context: ThemedReactContext) : HybridSkeletonSpec() {
|
|
|
137
158
|
}
|
|
138
159
|
start()
|
|
139
160
|
}
|
|
161
|
+
|
|
162
|
+
// Record the inputs this running animator was built from, so afterUpdate() can skip
|
|
163
|
+
// restarting while none of them change.
|
|
164
|
+
lastShimmerSignature = currentShimmerSignature()
|
|
165
|
+
isShimmerRunning = true
|
|
140
166
|
}
|
|
141
167
|
|
|
142
168
|
private fun stopShimmer() {
|
|
@@ -152,13 +178,24 @@ class HybridSkeleton(val context: ThemedReactContext) : HybridSkeletonSpec() {
|
|
|
152
178
|
cancel()
|
|
153
179
|
}
|
|
154
180
|
shimmerAnimator = null
|
|
181
|
+
isShimmerRunning = false
|
|
155
182
|
}
|
|
156
183
|
|
|
157
184
|
override fun afterUpdate() {
|
|
158
185
|
super.afterUpdate()
|
|
159
|
-
if (
|
|
160
|
-
|
|
186
|
+
if (isDisposed) return
|
|
187
|
+
|
|
188
|
+
// Defensive guard against ValueAnimator churn (REACT-NATIVE-40K ANR):
|
|
189
|
+
// afterUpdate() runs on every Fabric prop commit. Only restart the shimmer when an
|
|
190
|
+
// input that actually drives the animation changed (speed / colors / view size).
|
|
191
|
+
// If the shimmer is already running and nothing relevant changed, leave the existing
|
|
192
|
+
// ValueAnimator alone to avoid main-thread cancel/rebuild churn.
|
|
193
|
+
val signature = currentShimmerSignature()
|
|
194
|
+
if (isShimmerRunning && shimmerAnimator != null && signature == lastShimmerSignature) {
|
|
195
|
+
return
|
|
161
196
|
}
|
|
197
|
+
|
|
198
|
+
restartShimmer()
|
|
162
199
|
}
|
|
163
200
|
|
|
164
201
|
private fun restartShimmer() {
|
package/ios/Skeleton.swift
CHANGED
|
@@ -15,6 +15,31 @@ class HybridSkeleton : HybridSkeletonSpec {
|
|
|
15
15
|
private let maxRetryCount: Int = 10
|
|
16
16
|
private var viewObserver: NSKeyValueObservation?
|
|
17
17
|
|
|
18
|
+
// Guard against high-frequency afterUpdate() animation churn (REACT-NATIVE-40K ANR).
|
|
19
|
+
// Fabric calls afterUpdate() on every prop commit; list/order-book screens update very
|
|
20
|
+
// frequently. Unconditionally restarting the shimmer tears down/rebuilds the
|
|
21
|
+
// CAGradientLayer + CABasicAnimation each time on the main thread. We cache a signature
|
|
22
|
+
// of the inputs that actually drive the animation (speed + gradient colors + bounds) and
|
|
23
|
+
// only restart when that signature changes. Otherwise the running animation is left as-is.
|
|
24
|
+
private var lastShimmerSignature: String?
|
|
25
|
+
private var isShimmerRunning: Bool = false
|
|
26
|
+
|
|
27
|
+
// Build a signature from every input that decides the shimmer animation:
|
|
28
|
+
// - shimmerSpeed -> CABasicAnimation.duration
|
|
29
|
+
// - gradient colors -> CAGradientLayer.colors
|
|
30
|
+
// - view bounds -> animation travel range (-width..width) and layer frames
|
|
31
|
+
private func currentShimmerSignature() -> String {
|
|
32
|
+
let colors: [UIColor] = customGradientColors ?? DEFAULT_GRADIENT_COLORS
|
|
33
|
+
let colorsKey = colors.map { color -> String in
|
|
34
|
+
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
|
|
35
|
+
color.getRed(&r, green: &g, blue: &b, alpha: &a)
|
|
36
|
+
return "\(r)-\(g)-\(b)-\(a)"
|
|
37
|
+
}.joined(separator: ",")
|
|
38
|
+
let speed = shimmerSpeed ?? 3
|
|
39
|
+
let bounds = view.bounds
|
|
40
|
+
return "\(speed)|\(colorsKey)|\(bounds.width)x\(bounds.height)"
|
|
41
|
+
}
|
|
42
|
+
|
|
18
43
|
var shimmerGradientColors: [String]? {
|
|
19
44
|
didSet {
|
|
20
45
|
guard isActive else { return }
|
|
@@ -172,6 +197,11 @@ class HybridSkeleton : HybridSkeletonSpec {
|
|
|
172
197
|
|
|
173
198
|
// Use weak self pattern for potential future animation delegates/completion handlers
|
|
174
199
|
gradientLayer.add(animation, forKey: "shimmerAnimation")
|
|
200
|
+
|
|
201
|
+
// Record the inputs this running animation was built from, so afterUpdate() can skip
|
|
202
|
+
// restarting while none of them change.
|
|
203
|
+
lastShimmerSignature = currentShimmerSignature()
|
|
204
|
+
isShimmerRunning = true
|
|
175
205
|
}
|
|
176
206
|
|
|
177
207
|
func stopShimmer() {
|
|
@@ -195,12 +225,24 @@ class HybridSkeleton : HybridSkeletonSpec {
|
|
|
195
225
|
// Nil out references
|
|
196
226
|
shimmerLayer = nil
|
|
197
227
|
skeletonLayer = nil
|
|
228
|
+
isShimmerRunning = false
|
|
198
229
|
}
|
|
199
230
|
|
|
200
231
|
func afterUpdate() {
|
|
201
232
|
guard isActive else { return }
|
|
202
233
|
DispatchQueue.main.async { [weak self] in
|
|
203
234
|
guard let self = self, self.isActive else { return }
|
|
235
|
+
|
|
236
|
+
// Defensive guard against animation churn (REACT-NATIVE-40K ANR):
|
|
237
|
+
// afterUpdate() runs on every Fabric prop commit. Only restart the shimmer when an
|
|
238
|
+
// input that actually drives the animation changed (speed / colors / bounds). If the
|
|
239
|
+
// shimmer is already running and nothing relevant changed, leave the existing
|
|
240
|
+
// CABasicAnimation alone to avoid main-thread layer teardown/rebuild churn.
|
|
241
|
+
let signature = self.currentShimmerSignature()
|
|
242
|
+
if self.isShimmerRunning, self.shimmerLayer != nil, signature == self.lastShimmerSignature {
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
|
|
204
246
|
self.restartShimmer()
|
|
205
247
|
}
|
|
206
248
|
}
|