-
-
Couldn't load subscription status.
- Fork 592
Description
Description
Hello, some time ago I opened a PR here regarding an issue involving RNScreens and Dimezis’s BlurView (v2.x). Now, I’ve updated the Dimezis BlurView library to version 3.1.0.
I was able to successfully update it to the new version, following the update approach suggested in an Expo discussion.
The approach worked very well at first, but when I use Blur as a background or as a customizable tab component with BottomTab, it renders correctly the first time. However, when switching screens, the blur effect is either not applied or remains frozen.
Below, I will show two examples, one with the BottomTab animation set to none and another with the fade animation. I’m seriously unsure whether the issue comes from my library or from react-native-screens not supporting something required in this case. I’m a bit confused about it 😄.
animation-none.mov
animation-fade.mov
It can be observed that when the animation is set to none, switching tabs causes the previously blurred tab to appear blank. Is there any internal configuration in BottomTabs that changes the background color? And when the animation is set to fade, the blur effect becomes frozen, showing only the result from the initial render of the blur.
Below I will provide my routes and screen code from the React Native side.
// routes.tsx
import { StyleSheet } from 'react-native';
import { createStaticNavigation } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { BlurView } from '@azify/react-native-blur';
import { First, Second } from '../screens';
import { styles } from './styles';
const styles = StyleSheet.create({
tabBackground: {
width: '100%',
height: 256,
},
});
const RootTabs = createBottomTabNavigator({
screens: {
First,
Second,
},
initialRouteName: 'First',
screenOptions: {
headerShown: false,
animation: 'fade',
tabBarStyle: {
position: 'absolute',
},
tabBarBackground: () => (
<BlurView targetId="target" style={styles.tabBackground} />
),
},
});
export const Routes = createStaticNavigation(RootTabs);// screens/First.tsx
import { useMemo } from 'react';
import { Pressable, ScrollView, Text, View } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { BlurTarget, BlurView } from '@azify/react-native-blur';
import { styles } from './styles';
export function First() {
const navigate = useNavigation();
function onNextScreen() {
navigate.navigate('Second' as never);
}
const renderLabels = useMemo(
() =>
Array.from({ length: 50 }).map((_, i) => (
<Text key={i} style={styles.label}>
First Screen
</Text>
)),
[]
);
return (
<View style={styles.container}>
<BlurView
targetId="target"
type="light"
radius={10}
style={styles.blurViewHeader}
>
<View style={styles.header}>
<View style={styles.headerWrapper}>
<Text style={styles.label}>Header</Text>
<View style={styles.avatar}>
<Text style={styles.paragraph}>A</Text>
</View>
</View>
<Pressable style={styles.button} onPress={onNextScreen}>
<Text style={styles.paragraph}>Next Screen</Text>
</Pressable>
</View>
</BlurView>
<BlurTarget id="target" style={styles.main}>
<ScrollView
style={styles.main}
contentContainerStyle={styles.mainContent}
showsVerticalScrollIndicator={false}
>
{renderLabels}
</ScrollView>
</BlurTarget>
</View>
);
}This is my native code in Kotlin:
// ReactNativeBlurView -> BlurView
package com.azify.reactnativeblur
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.util.Log
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import eightbitlab.com.blurview.BlurTarget
import eightbitlab.com.blurview.BlurView
import androidx.core.graphics.drawable.toDrawable
class ReactNativeBlurView : BlurView {
private var targetId: String? = null
private var overlayColor: OverlayColor = OverlayColor.fromString("light")
private var radius: Float = 10f
private var isInitialized: Boolean = false
private var rootView: BlurTarget? = null
private enum class OverlayColor(val color: Int) {
LIGHT(Color.argb(20, 255, 255, 255)),
DARK(Color.argb(60, 0, 0, 0));
companion object {
fun fromString(color: String): OverlayColor {
return when (color.lowercase()) {
"light" -> LIGHT
"dark" -> DARK
else -> LIGHT
}
}
}
}
companion object {
private const val TAG: String = "ReactNativeBlurView"
}
constructor(context: Context?) : super(context) {
this.setupBlurView()
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
this.setupBlurView()
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
this.setupBlurView()
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
if (!this.isInitialized) {
this.reinitialize()
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
this.rootView = null
this.isInitialized = false
this.removeCallbacks(null)
}
private fun setupBlurView() {
super.setBackgroundColor(this.overlayColor.color)
super.clipChildren = true
super.clipToOutline = true
super.layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT
)
}
// Wait all views are mounted in interface
private fun reinitialize() {
post {
this.initialize()
}
}
private fun initialize() {
// Find rootView only on first mount (when the initialization is false)
if (!this.isInitialized) {
this.rootView = this.findRootTargetView()
if (this.rootView == null) {
super.setBackgroundColor(this.overlayColor.color)
super.setOverlayColor(this.overlayColor.color)
super.setBlurEnabled(false)
Log.w(TAG, "Target view not found: $targetId")
return
}
}
val drawable = this.getAppropriateBackground()
super.setupWith(this.rootView!!, 4f, false)
.setBlurRadius(this.radius)
.setOverlayColor(this.overlayColor.color)
.setBlurAutoUpdate(true)
.setBlurEnabled(true)
.setFrameClearDrawable(drawable)
this.isInitialized = true
}
private fun findRootTargetView(): BlurTarget? {
if (this.targetId == null) {
Log.w(TAG, "TargetId is null")
return null
}
val activityRoot = this.getRootView()
activityRoot?.let { root ->
val target = findViewWithTagInViewGroup(root as? ViewGroup, targetId!!)
if (target != null) return target
}
var parent = this.parent
while (parent != null) {
if (parent is ViewGroup) {
val target = findViewWithTagInViewGroup(parent, targetId!!)
if (target != null) return target
}
parent = parent.parent
}
Log.w(TAG, "Target not found anywhere: $targetId")
return null
}
private fun findViewWithTagInViewGroup(viewGroup: ViewGroup?, tag: String): BlurTarget? {
if (viewGroup == null) return null
if (viewGroup.tag == tag && viewGroup is BlurTarget) {
return viewGroup
}
for (i in 0 until viewGroup.childCount) {
val child = viewGroup.getChildAt(i)
if (child.tag == tag && child is BlurTarget) {
return child
}
if (child is ViewGroup) {
val found = this.findViewWithTagInViewGroup(child, tag)
if (found != null) return found
}
}
return null
}
private fun getAppropriateBackground(): android.graphics.drawable.Drawable? {
try {
val activity = this.getActivityFromContext()
activity?.window?.decorView?.background?.let {
return it
}
activity?.window?.let { window ->
val windowBackground = window.decorView.background
windowBackground?.let {
return it
}
}
return when (this.overlayColor) {
OverlayColor.LIGHT -> Color.WHITE
OverlayColor.DARK -> Color.BLACK
}.toDrawable()
} catch (e: Exception) {
Log.e(TAG, "Error getting background: ${e.message}")
return this.overlayColor.color.toDrawable()
}
}
private fun getActivityFromContext(): AppCompatActivity? {
var context = this.context
while (context != null) {
when (context) {
is AppCompatActivity -> return context
is android.content.ContextWrapper -> {
context = context.baseContext
}
else -> break
}
}
return null
}
private fun clipRadius(radius: Float): Float {
return when {
radius <= 0 -> 0f
radius >= 100 -> 100f
else -> radius
}
}
fun setOverlayColor(overlayColor: String) {
val overlay = OverlayColor.fromString(overlayColor)
this.overlayColor = overlay
super.setBackgroundColor(overlay.color)
if (this.isInitialized) {
super.setOverlayColor(overlay.color)
this.isInitialized = false
this.reinitialize()
}
}
fun setRadius(radius: Float) {
this.radius = radius
if (this.isInitialized) {
val clippedRadius = this.clipRadius(radius)
super.setBlurRadius(clippedRadius)
this.reinitialize()
}
}
fun setTargetId(targetId: String?) {
val oldTargetId = this.targetId
this.targetId = targetId
if (oldTargetId != targetId && this.isAttachedToWindow) {
this.isInitialized = false
this.rootView = null
this.reinitialize()
}
}
}// ReactNativeTargetView -> BlurTarget
package com.azify.reactnativeblur
import android.content.Context
import android.util.AttributeSet
import android.util.Log
import eightbitlab.com.blurview.BlurTarget
class ReactNativeTargetView : BlurTarget {
private var id: String? = null
private var isInitialized: Boolean = false
companion object {
private const val TAG: String = "ReactNativeTargetView"
}
constructor(context: Context): super(context) {
this.setupBlurTarget()
}
constructor(context: Context, attrs: AttributeSet): super(context, attrs) {
this.setupBlurTarget()
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int): super(context, attrs, defStyleAttr) {
this.setupBlurTarget()
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
if (!this.isInitialized) {
this.reinitialize()
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
this.isInitialized = false
this.removeCallbacks(null)
}
private fun setupBlurTarget() {
super.layoutParams = LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT
)
}
private fun reinitialize() {
post {
this.initialize()
}
}
private fun initialize() {
if (this.id != null) {
super.tag = this.id
this.isInitialized = true
} else {
Log.w(TAG, "Target view id is null")
this.isInitialized = false
}
}
fun setId(id: String?) {
val oldId = this.id
this.id = id
if (oldId != id && this.isAttachedToWindow) {
this.isInitialized = false
this.reinitialize()
}
}
}If you find any issues in my native code, please let me know. So far, I haven’t detected any errors or warnings in the LogCat of Android Studio, but this problem shown in the videos above still persists. From my perspective, the issue seems to be related to the BottomTab animations — it appears to freeze the blur effect and also prevent it from re-rendering when navigating to a new screen.
Steps to reproduce
- Install
@react-navigation/bottom-tabs(^7.4.7),@react-navigation/native(^7.1.17) andreact-native-screens(4.16.0).
Snack or a link to a repository
None
Screens version
4.16.0
React Native version
0.79.2
Platforms
Android
JavaScript runtime
None
Workflow
React Native (without Expo)
Architecture
Fabric (New Architecture)
Build type
None
Device
Android emulator
Device model
Pixel 9 (API 36.0)
Acknowledgements
Yes