Dimezis / BlurView

Dynamic iOS-like blur of underlying Views for Android
Apache License 2.0
3.47k stars 328 forks source link

BlurView breaks Compose recomposition #195

Open zakrodionov opened 1 year ago

zakrodionov commented 1 year ago

1) 2.0.3 2) All devices / Android 12, Poco X3 Pro

I'm trying to display a notification with a blur on top of all views. When a notification is shown, Compose stops updating, does not respond to clicks. I wrote a small sample where this can be reproduced: https://github.com/zakrodionov/ComposeBugStuckComposition

NotificationView.kt

class NotificationView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {

    private val view: View = View.inflate(context, R.layout.view_notification, this)
    private val blurView = view.findViewById<BlurView>(R.id.blurView)

    init {
        val decorView = getActivityDecorView()
        val rootView = decorView?.findViewById(android.R.id.content) as ViewGroup
        val windowBackground = decorView.background

        blurView.setupWith(rootView, RenderScriptBlur(context)) // or RenderEffectBlur
            .setFrameClearDrawable(windowBackground)
            .setBlurRadius(2f)
    }

    private fun getActivityDecorView(): View? {
        var ctx = context
        var i = 0
        while (i < 4 && ctx != null && ctx !is Activity && ctx is ContextWrapper) {
            ctx = ctx.baseContext
            i++
        }
        return if (ctx is Activity) {
            ctx.window.decorView
        } else {
            null
        }
    }
}

ComposeFragment.kt

private fun showNotification() {
    val viewGroup =
        requireActivity().window?.decorView?.findViewById(android.R.id.content) as? ViewGroup
    val view =
        NotificationView(requireContext()).apply {
            setOnClickListener { viewGroup?.removeView(it) }
        }
    val params =
        FrameLayout.LayoutParams(
            FrameLayout.LayoutParams.MATCH_PARENT,
            FrameLayout.LayoutParams.WRAP_CONTENT
        )
    params.gravity = Gravity.TOP or Gravity.CENTER
    viewGroup?.addView(view, params)
    view.bringToFront()
}

view_notification.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="300dp"
    android:orientation="vertical">

    <eightbitlab.com.blurview.BlurView
        android:id="@+id/blurView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:backgroundTint="@color/purple_200"
        app:blurOverlayColor="#A353536C">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:gravity="center"
            android:text="Notification title"
            android:textSize="30sp" />

    </eightbitlab.com.blurview.BlurView>

</LinearLayout>
Dimezis commented 1 year ago

Fascinating. I haven't tested this kind of scenario, but a BlurView embedded in a Compose hierarchy should work, if that suits your needs. I'm assuming this breaks because of the software rendering that BlurView performs on a Compose hierarchy, but I'm not entirely sure how to debug or fix it.

zakrodionov commented 1 year ago

Thanks for the quick response! The problem is very strange and the Compose itself seems to be to blame. Wrapping the compositing screen and specifying setLayerType(View.LAYER_TYPE_SOFTWARE, null) fixes the problem, but the performance is terrible.

@Composable
fun SoftwareLayerComposable(
    modifier: Modifier = Modifier,
    content: @Composable () -> Unit,
) {
    AndroidView(
        factory = { context ->
            ComposeView(context).apply {
                setLayerType(View.LAYER_TYPE_SOFTWARE, null)
            }
        },
        update = { composeView ->
            composeView.setContent(content)
        },
        modifier = modifier,
    )
}
Dimezis commented 1 year ago

Yeah, I wouldn't consider setting a software layer even if it fixes the problem

anthony-fresneau-kiplin commented 1 year ago

Hi, any updates on this breaking compose recomposition problem? Thanks

Dimezis commented 1 year ago

@anthony-fresneau-kiplin no updates. I'm not investigating it though. The only way to fix it on my side is to move to hardware rendering for UI snapshots, which comes with a whole bunch of caveats because of the API constraints.

You can try to report it to Compose team, but not sure if they'd want to investigate it either.

ruby-turn2cloud commented 1 year ago

I tried handling it using a combination of View and Compose and it works. I built the BlurView inside a View and used ComposeView to handle all the other views below or inside it.

williankl commented 1 year ago

I tried handling it using a combination of View and Compose and it works. I built the BlurView inside a View and used ComposeView to handle all the other views below or inside it.

Can you elaborate on that @ruby-turn2cloud?
I'm facing the same issue but not really getting anywhere :eyes:

ruby-turn2cloud commented 1 year ago

@williankl I separate compose part and blur part like this:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/content_compose_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <eightbitlab.com.blurview.BlurView
        android:id="@+id/blur_view"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        app:blurOverlayColor="#66FFFFFF"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.compose.ui.platform.ComposeView
            android:id="@+id/compose_view_inside_blur_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </eightbitlab.com.blurview.BlurView>
</androidx.constraintlayout.widget.ConstraintLayout>
williankl commented 1 year ago

Yup, I've tried this but still isn't working for me, unfortunately. Any other ideas or news?

Dimezis commented 4 months ago

I reported this bug to Compose team, but they might just dismiss it and say I'm trying to do something stupid