@sbaiahmed1/react-native-blur 4.5.4 → 4.5.5-beta.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.
|
@@ -3,15 +3,15 @@ package com.sbaiahmed1.reactnativeblur
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.graphics.Color
|
|
5
5
|
import android.graphics.Outline
|
|
6
|
-
import android.os.Build
|
|
7
6
|
import android.util.AttributeSet
|
|
8
7
|
import android.util.Log
|
|
9
8
|
import android.util.TypedValue
|
|
10
9
|
import android.view.View
|
|
11
10
|
import android.view.ViewGroup
|
|
12
11
|
import android.view.ViewOutlineProvider
|
|
13
|
-
import
|
|
12
|
+
import android.view.ViewTreeObserver
|
|
14
13
|
import com.qmdeve.blurview.widget.BlurViewGroup
|
|
14
|
+
import com.qmdeve.blurview.base.BaseBlurViewGroup
|
|
15
15
|
import androidx.core.graphics.toColorInt
|
|
16
16
|
|
|
17
17
|
import android.view.View.MeasureSpec
|
|
@@ -22,6 +22,10 @@ import android.view.View.MeasureSpec
|
|
|
22
22
|
*
|
|
23
23
|
* QmBlurView is a high-performance blur library that uses native blur algorithms
|
|
24
24
|
* implemented with underlying Native calls for optimal performance.
|
|
25
|
+
*
|
|
26
|
+
* Uses reflection to redirect the blur capture root from the activity decor view
|
|
27
|
+
* to the nearest react-native-screens Screen ancestor, preventing flickering and
|
|
28
|
+
* wrong frame capture during navigation transitions.
|
|
25
29
|
*/
|
|
26
30
|
class ReactNativeBlurView : BlurViewGroup {
|
|
27
31
|
private var currentBlurRadius = DEFAULT_BLUR_RADIUS
|
|
@@ -31,12 +35,13 @@ class ReactNativeBlurView : BlurViewGroup {
|
|
|
31
35
|
private var glassOpacity: Float = 1.0f
|
|
32
36
|
private var viewType: String = "blur"
|
|
33
37
|
private var glassType: String = "clear"
|
|
38
|
+
private var isBlurInitialized: Boolean = false
|
|
34
39
|
|
|
35
40
|
companion object {
|
|
36
41
|
private const val TAG = "ReactNativeBlurView"
|
|
37
42
|
private const val MAX_BLUR_RADIUS = 100f
|
|
38
43
|
private const val DEFAULT_BLUR_RADIUS = 10f
|
|
39
|
-
private const val DEBUG = false
|
|
44
|
+
private const val DEBUG = false
|
|
40
45
|
|
|
41
46
|
// Cross-platform blur amount constants
|
|
42
47
|
private const val MIN_BLUR_AMOUNT = 0f
|
|
@@ -69,32 +74,149 @@ class ReactNativeBlurView : BlurViewGroup {
|
|
|
69
74
|
}
|
|
70
75
|
|
|
71
76
|
constructor(context: Context?) : super(context, null) {
|
|
72
|
-
|
|
77
|
+
setupView()
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
|
|
76
|
-
|
|
81
|
+
setupView()
|
|
77
82
|
}
|
|
78
83
|
|
|
79
84
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
85
|
+
* Initial view setup in constructor - only sets up visual defaults.
|
|
86
|
+
* Blur initialization is deferred to onAttachedToWindow to ensure the
|
|
87
|
+
* view hierarchy is fully mounted, preventing flickering and wrong frame capture.
|
|
82
88
|
*/
|
|
83
|
-
private fun
|
|
89
|
+
private fun setupView() {
|
|
90
|
+
super.setBackgroundColor(currentOverlayColor)
|
|
91
|
+
clipChildren = true
|
|
92
|
+
clipToOutline = true
|
|
93
|
+
super.setDownsampleFactor(6.0F)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Called when the view is attached to a window.
|
|
98
|
+
* After QmBlurView's onAttachedToWindow sets the decor view as blur root,
|
|
99
|
+
* we use reflection to redirect it to the nearest Screen ancestor.
|
|
100
|
+
* This scopes the blur capture to just the current screen, preventing
|
|
101
|
+
* navigation transition artifacts.
|
|
102
|
+
*/
|
|
103
|
+
override fun onAttachedToWindow() {
|
|
104
|
+
super.onAttachedToWindow()
|
|
105
|
+
|
|
106
|
+
// Defer the blur root swap to next frame so the view tree is fully mounted
|
|
107
|
+
post {
|
|
108
|
+
swapBlurRootToScreenAncestor()
|
|
109
|
+
initializeBlur()
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Uses reflection to redirect QmBlurView's internal blur capture root
|
|
115
|
+
* from the activity decor view to the nearest react-native-screens Screen ancestor.
|
|
116
|
+
*
|
|
117
|
+
* Reflection path: BlurViewGroup.mBaseBlurViewGroup -> BaseBlurViewGroup.mDecorView
|
|
118
|
+
* Also moves the OnPreDrawListener from the old root to the new one.
|
|
119
|
+
*/
|
|
120
|
+
private fun swapBlurRootToScreenAncestor() {
|
|
121
|
+
val newRoot = findOptimalBlurRoot() ?: return
|
|
122
|
+
|
|
84
123
|
try {
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
124
|
+
// Step 1: Get BlurViewGroup's private mBaseBlurViewGroup field
|
|
125
|
+
val blurViewGroupClass = BlurViewGroup::class.java
|
|
126
|
+
val baseField = blurViewGroupClass.getDeclaredField("mBaseBlurViewGroup")
|
|
127
|
+
baseField.isAccessible = true
|
|
128
|
+
val baseBlurViewGroup = baseField.get(this) ?: return
|
|
129
|
+
|
|
130
|
+
// Step 2: Get BaseBlurViewGroup's private fields
|
|
131
|
+
val baseClass = BaseBlurViewGroup::class.java
|
|
132
|
+
|
|
133
|
+
val decorViewField = baseClass.getDeclaredField("mDecorView")
|
|
134
|
+
decorViewField.isAccessible = true
|
|
135
|
+
val oldDecorView = decorViewField.get(baseBlurViewGroup) as? View
|
|
136
|
+
|
|
137
|
+
val preDrawListenerField = baseClass.getDeclaredField("preDrawListener")
|
|
138
|
+
preDrawListenerField.isAccessible = true
|
|
139
|
+
val preDrawListener = preDrawListenerField.get(baseBlurViewGroup) as? ViewTreeObserver.OnPreDrawListener
|
|
140
|
+
|
|
141
|
+
if (preDrawListener != null && oldDecorView != null) {
|
|
142
|
+
// Step 3: Remove listener from old root's ViewTreeObserver
|
|
143
|
+
try {
|
|
144
|
+
oldDecorView.viewTreeObserver.removeOnPreDrawListener(preDrawListener)
|
|
145
|
+
} catch (e: Exception) {
|
|
146
|
+
logDebug("Could not remove old pre-draw listener: ${e.message}")
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Step 4: Set new root as mDecorView
|
|
150
|
+
decorViewField.set(baseBlurViewGroup, newRoot)
|
|
151
|
+
|
|
152
|
+
// Step 5: Add listener to new root's ViewTreeObserver
|
|
153
|
+
newRoot.viewTreeObserver.addOnPreDrawListener(preDrawListener)
|
|
154
|
+
|
|
155
|
+
// Step 6: Update mDifferentRoot flag
|
|
156
|
+
val differentRootField = baseClass.getDeclaredField("mDifferentRoot")
|
|
157
|
+
differentRootField.isAccessible = true
|
|
158
|
+
differentRootField.setBoolean(baseBlurViewGroup, newRoot.rootView != this.rootView)
|
|
90
159
|
|
|
91
|
-
|
|
160
|
+
// Step 7: Force a redraw
|
|
161
|
+
val forceRedrawField = baseClass.getDeclaredField("mForceRedraw")
|
|
162
|
+
forceRedrawField.isAccessible = true
|
|
163
|
+
forceRedrawField.setBoolean(baseBlurViewGroup, true)
|
|
92
164
|
|
|
93
|
-
|
|
94
|
-
super.setBackgroundColor(currentOverlayColor)
|
|
165
|
+
logDebug("Swapped blur root to: ${newRoot.javaClass.simpleName} (was: ${oldDecorView.javaClass.simpleName})")
|
|
95
166
|
}
|
|
167
|
+
} catch (e: NoSuchFieldException) {
|
|
168
|
+
logWarning("Reflection failed - QmBlurView field not found: ${e.message}. Falling back to decor view.")
|
|
169
|
+
} catch (e: Exception) {
|
|
170
|
+
logWarning("Failed to swap blur root: ${e.message}. Falling back to decor view.")
|
|
171
|
+
}
|
|
172
|
+
}
|
|
96
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Finds the optimal view to use as blur capture root.
|
|
176
|
+
* Priority: nearest react-native-screens Screen > android.R.id.content > parent
|
|
177
|
+
*/
|
|
178
|
+
private fun findOptimalBlurRoot(): ViewGroup? {
|
|
179
|
+
return findNearestScreenAncestor() ?: getContentViewFallback()
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Walks up the view hierarchy looking for react-native-screens Screen components
|
|
184
|
+
* using class name detection to avoid hard dependencies on react-native-screens.
|
|
185
|
+
*/
|
|
186
|
+
private fun findNearestScreenAncestor(): ViewGroup? {
|
|
187
|
+
var currentParent = this.parent
|
|
188
|
+
while (currentParent != null) {
|
|
189
|
+
if (currentParent.javaClass.name == "com.swmansion.rnscreens.Screen") {
|
|
190
|
+
return currentParent as? ViewGroup
|
|
191
|
+
}
|
|
192
|
+
currentParent = currentParent.parent
|
|
193
|
+
}
|
|
194
|
+
return null
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Falls back to android.R.id.content or the activity root view.
|
|
199
|
+
*/
|
|
200
|
+
private fun getContentViewFallback(): ViewGroup? {
|
|
201
|
+
try {
|
|
202
|
+
val activity = context as? android.app.Activity
|
|
203
|
+
activity?.findViewById<ViewGroup>(android.R.id.content)?.let { return it }
|
|
204
|
+
} catch (e: Exception) {
|
|
205
|
+
logDebug("Could not access activity content view: ${e.message}")
|
|
206
|
+
}
|
|
207
|
+
return this.parent as? ViewGroup
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Initialize the blur view with current settings.
|
|
212
|
+
* Called after the view is attached and the blur root has been swapped.
|
|
213
|
+
*/
|
|
214
|
+
private fun initializeBlur() {
|
|
215
|
+
try {
|
|
216
|
+
super.setBlurRadius(currentBlurRadius)
|
|
217
|
+
super.setOverlayColor(currentOverlayColor)
|
|
97
218
|
updateCornerRadius()
|
|
219
|
+
isBlurInitialized = true
|
|
98
220
|
|
|
99
221
|
logDebug("QmBlurView initialized with blurRadius: $currentBlurRadius, overlayColor: $currentOverlayColor")
|
|
100
222
|
} catch (e: Exception) {
|
|
@@ -104,7 +226,8 @@ class ReactNativeBlurView : BlurViewGroup {
|
|
|
104
226
|
|
|
105
227
|
/**
|
|
106
228
|
* Called when the view is detached from a window.
|
|
107
|
-
* Performs cleanup to prevent memory leaks
|
|
229
|
+
* Performs cleanup to prevent memory leaks and resets initialization state
|
|
230
|
+
* so blur is re-initialized on next attach (e.g. navigation transitions).
|
|
108
231
|
*/
|
|
109
232
|
override fun onDetachedFromWindow() {
|
|
110
233
|
super.onDetachedFromWindow()
|
|
@@ -116,6 +239,7 @@ class ReactNativeBlurView : BlurViewGroup {
|
|
|
116
239
|
* Helps prevent memory leaks and ensures clean state.
|
|
117
240
|
*/
|
|
118
241
|
fun cleanup() {
|
|
242
|
+
isBlurInitialized = false
|
|
119
243
|
removeCallbacks(null)
|
|
120
244
|
logDebug("View cleaned up")
|
|
121
245
|
}
|
|
@@ -129,7 +253,6 @@ class ReactNativeBlurView : BlurViewGroup {
|
|
|
129
253
|
logDebug("setBlurAmount: $amount -> $currentBlurRadius (mapped from 0-100 to 0-25 range)")
|
|
130
254
|
|
|
131
255
|
try {
|
|
132
|
-
// QmBlurView uses setBlurRadius() to set blur intensity
|
|
133
256
|
super.setBlurRadius(currentBlurRadius)
|
|
134
257
|
} catch (e: Exception) {
|
|
135
258
|
logError("Failed to set blur radius: ${e.message}", e)
|
|
@@ -146,12 +269,8 @@ class ReactNativeBlurView : BlurViewGroup {
|
|
|
146
269
|
logDebug("setBlurType: $type -> ${blurType.name}")
|
|
147
270
|
|
|
148
271
|
try {
|
|
149
|
-
|
|
272
|
+
super.setBackgroundColor(currentOverlayColor)
|
|
150
273
|
super.setOverlayColor(currentOverlayColor)
|
|
151
|
-
|
|
152
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
153
|
-
super.setBackgroundColor(currentOverlayColor)
|
|
154
|
-
}
|
|
155
274
|
} catch (e: Exception) {
|
|
156
275
|
logError("Failed to set overlay color: ${e.message}", e)
|
|
157
276
|
}
|
|
@@ -249,11 +368,8 @@ class ReactNativeBlurView : BlurViewGroup {
|
|
|
249
368
|
"blur" -> {
|
|
250
369
|
// Restore original blur overlay color
|
|
251
370
|
try {
|
|
371
|
+
super.setBackgroundColor(currentOverlayColor)
|
|
252
372
|
super.setOverlayColor(currentOverlayColor)
|
|
253
|
-
|
|
254
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
255
|
-
super.setBackgroundColor(currentOverlayColor)
|
|
256
|
-
}
|
|
257
373
|
} catch (e: Exception) {
|
|
258
374
|
logError("Failed to restore blur overlay: ${e.message}", e)
|
|
259
375
|
}
|
|
@@ -286,16 +402,12 @@ class ReactNativeBlurView : BlurViewGroup {
|
|
|
286
402
|
context.resources.displayMetrics
|
|
287
403
|
)
|
|
288
404
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
override fun getOutline(view: View, outline: Outline?) {
|
|
293
|
-
outline?.setRoundRect(0, 0, view.width, view.height, radiusInPixels)
|
|
294
|
-
}
|
|
405
|
+
outlineProvider = object : ViewOutlineProvider() {
|
|
406
|
+
override fun getOutline(view: View, outline: Outline?) {
|
|
407
|
+
outline?.setRoundRect(0, 0, view.width, view.height, radiusInPixels)
|
|
295
408
|
}
|
|
296
|
-
|
|
297
|
-
clipToOutline = true
|
|
298
409
|
}
|
|
410
|
+
clipToOutline = true
|
|
299
411
|
|
|
300
412
|
super.setCornerRadius(radiusInPixels)
|
|
301
413
|
logDebug("Updated corner radius: ${currentCornerRadius}dp -> ${radiusInPixels}px")
|
package/android/src/main/java/com/sbaiahmed1/reactnativeblur/ReactNativeProgressiveBlurView.kt
CHANGED
|
@@ -10,10 +10,11 @@ import android.graphics.PorterDuffXfermode
|
|
|
10
10
|
import android.graphics.Shader
|
|
11
11
|
import android.util.AttributeSet
|
|
12
12
|
import android.util.Log
|
|
13
|
+
import android.view.View
|
|
14
|
+
import android.view.ViewGroup
|
|
13
15
|
import android.widget.FrameLayout
|
|
14
16
|
import android.view.View.MeasureSpec
|
|
15
17
|
import com.qmdeve.blurview.widget.BlurView
|
|
16
|
-
import androidx.core.graphics.toColorInt
|
|
17
18
|
import kotlin.math.max
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -33,12 +34,13 @@ class ReactNativeProgressiveBlurView : FrameLayout {
|
|
|
33
34
|
private var currentDirection = "topToBottom"
|
|
34
35
|
private var currentStartOffset = 0.0f
|
|
35
36
|
private var hasExplicitBackground: Boolean = false
|
|
37
|
+
private var isBlurInitialized: Boolean = false
|
|
36
38
|
|
|
37
39
|
companion object {
|
|
38
40
|
private const val TAG = "ReactNativeProgressiveBlur"
|
|
39
41
|
private const val MAX_BLUR_RADIUS = 100f
|
|
40
42
|
private const val DEFAULT_BLUR_RADIUS = 10f
|
|
41
|
-
private const val DEBUG =
|
|
43
|
+
private const val DEBUG = false
|
|
42
44
|
|
|
43
45
|
// Cross-platform blur amount constants
|
|
44
46
|
private const val MIN_BLUR_AMOUNT = 0f
|
|
@@ -72,36 +74,69 @@ class ReactNativeProgressiveBlurView : FrameLayout {
|
|
|
72
74
|
}
|
|
73
75
|
|
|
74
76
|
constructor(context: Context) : super(context) {
|
|
75
|
-
|
|
77
|
+
setupView()
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
|
|
79
|
-
|
|
81
|
+
setupView()
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
/**
|
|
83
|
-
*
|
|
85
|
+
* Initial view setup in constructor - only sets up visual defaults and gradient paint.
|
|
86
|
+
* Blur child creation is deferred to onAttachedToWindow.
|
|
84
87
|
*/
|
|
85
|
-
private fun
|
|
88
|
+
private fun setupView() {
|
|
89
|
+
// Set up the gradient paint
|
|
90
|
+
gradientPaint.style = Paint.Style.FILL
|
|
91
|
+
setWillNotDraw(false)
|
|
92
|
+
|
|
93
|
+
// Set transparent background for the container
|
|
94
|
+
super.setBackgroundColor(Color.TRANSPARENT)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Called when the view is attached to a window.
|
|
99
|
+
* Defers blur initialization to the next frame to ensure the view tree is ready.
|
|
100
|
+
*/
|
|
101
|
+
override fun onAttachedToWindow() {
|
|
102
|
+
super.onAttachedToWindow()
|
|
103
|
+
|
|
104
|
+
if (!isBlurInitialized) {
|
|
105
|
+
post {
|
|
106
|
+
initializeBlurChild()
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Initialize the internal blur view child after the view tree is ready.
|
|
113
|
+
* Also swaps the blur capture root to the nearest Screen ancestor.
|
|
114
|
+
*/
|
|
115
|
+
private fun initializeBlurChild() {
|
|
116
|
+
if (isBlurInitialized) return
|
|
117
|
+
|
|
86
118
|
try {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
119
|
+
if (blurView == null) {
|
|
120
|
+
blurView = BlurView(context, null).apply {
|
|
121
|
+
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
|
122
|
+
setDownsampleFactor(6.0F)
|
|
123
|
+
blurRounds = 3
|
|
124
|
+
}
|
|
125
|
+
addView(blurView)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
blurView?.apply {
|
|
90
129
|
setBlurRadius(currentBlurRadius)
|
|
91
|
-
setDownsampleFactor(6.0F)
|
|
92
|
-
blurRounds = 3
|
|
93
130
|
overlayColor = currentOverlayColor
|
|
94
131
|
setBackgroundColor(currentOverlayColor)
|
|
95
132
|
}
|
|
96
|
-
addView(blurView)
|
|
97
133
|
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
// Set transparent background for the container
|
|
103
|
-
super.setBackgroundColor(Color.TRANSPARENT)
|
|
134
|
+
// Swap blur root after BlurView is attached (deferred to let it attach first)
|
|
135
|
+
blurView?.post {
|
|
136
|
+
swapBlurRootToScreenAncestor()
|
|
137
|
+
}
|
|
104
138
|
|
|
139
|
+
isBlurInitialized = true
|
|
105
140
|
logDebug("Initialized progressive blur with blur + gradient approach")
|
|
106
141
|
updateGradient()
|
|
107
142
|
|
|
@@ -110,6 +145,79 @@ class ReactNativeProgressiveBlurView : FrameLayout {
|
|
|
110
145
|
}
|
|
111
146
|
}
|
|
112
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Redirects the internal BlurView's blur capture root from the activity decor view
|
|
150
|
+
* to the nearest react-native-screens Screen ancestor.
|
|
151
|
+
*
|
|
152
|
+
* BaseBlurView has public mDecorView and preDrawListener fields, so no reflection needed.
|
|
153
|
+
*/
|
|
154
|
+
private fun swapBlurRootToScreenAncestor() {
|
|
155
|
+
val bv = blurView ?: return
|
|
156
|
+
val newRoot = findOptimalBlurRoot() ?: return
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
val oldDecorView = bv.mDecorView
|
|
160
|
+
val listener = bv.preDrawListener
|
|
161
|
+
|
|
162
|
+
if (oldDecorView != null && listener != null) {
|
|
163
|
+
// Remove listener from old root
|
|
164
|
+
try {
|
|
165
|
+
oldDecorView.viewTreeObserver.removeOnPreDrawListener(listener)
|
|
166
|
+
} catch (e: Exception) {
|
|
167
|
+
logDebug("Could not remove old pre-draw listener: ${e.message}")
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Set new root
|
|
171
|
+
bv.mDecorView = newRoot
|
|
172
|
+
|
|
173
|
+
// Add listener to new root
|
|
174
|
+
newRoot.viewTreeObserver.addOnPreDrawListener(listener)
|
|
175
|
+
|
|
176
|
+
// Update mDifferentRoot flag
|
|
177
|
+
bv.mDifferentRoot = newRoot.rootView != bv.rootView
|
|
178
|
+
|
|
179
|
+
logDebug("Progressive blur: swapped root to ${newRoot.javaClass.simpleName}")
|
|
180
|
+
}
|
|
181
|
+
} catch (e: Exception) {
|
|
182
|
+
logWarning("Failed to swap progressive blur root: ${e.message}")
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Finds the optimal view to use as blur capture root.
|
|
188
|
+
* Priority: nearest react-native-screens Screen > android.R.id.content > parent
|
|
189
|
+
*/
|
|
190
|
+
private fun findOptimalBlurRoot(): ViewGroup? {
|
|
191
|
+
return findNearestScreenAncestor() ?: getContentViewFallback()
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Walks up the view hierarchy looking for react-native-screens Screen components.
|
|
196
|
+
*/
|
|
197
|
+
private fun findNearestScreenAncestor(): ViewGroup? {
|
|
198
|
+
var currentParent = this.parent
|
|
199
|
+
while (currentParent != null) {
|
|
200
|
+
if (currentParent.javaClass.name == "com.swmansion.rnscreens.Screen") {
|
|
201
|
+
return currentParent as? ViewGroup
|
|
202
|
+
}
|
|
203
|
+
currentParent = currentParent.parent
|
|
204
|
+
}
|
|
205
|
+
return null
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Falls back to android.R.id.content or the activity root view.
|
|
210
|
+
*/
|
|
211
|
+
private fun getContentViewFallback(): ViewGroup? {
|
|
212
|
+
try {
|
|
213
|
+
val activity = context as? android.app.Activity
|
|
214
|
+
activity?.findViewById<ViewGroup>(android.R.id.content)?.let { return it }
|
|
215
|
+
} catch (e: Exception) {
|
|
216
|
+
logDebug("Could not access activity content view: ${e.message}")
|
|
217
|
+
}
|
|
218
|
+
return this.parent as? ViewGroup
|
|
219
|
+
}
|
|
220
|
+
|
|
113
221
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
114
222
|
val width = MeasureSpec.getSize(widthMeasureSpec)
|
|
115
223
|
val height = MeasureSpec.getSize(heightMeasureSpec)
|
|
@@ -252,11 +360,12 @@ class ReactNativeProgressiveBlurView : FrameLayout {
|
|
|
252
360
|
|
|
253
361
|
/**
|
|
254
362
|
* Cleanup method to prevent memory leaks.
|
|
363
|
+
* Resets initialization state so blur is re-initialized on next attach.
|
|
255
364
|
*/
|
|
256
365
|
fun cleanup() {
|
|
257
366
|
hasExplicitBackground = false
|
|
367
|
+
isBlurInitialized = false
|
|
258
368
|
removeCallbacks(null)
|
|
259
|
-
blurView = null
|
|
260
369
|
logDebug("View cleaned up")
|
|
261
370
|
}
|
|
262
371
|
|