chrisbanes / haze

Background blurring for Compose Multiplatform / Jetpack Compose
https://chrisbanes.github.io/haze/
Apache License 2.0
1.27k stars 33 forks source link

Glitched artifacts with blurred items inside a scrollable container #201

Closed Lisomatrix closed 7 months ago

Lisomatrix commented 7 months ago

Information

Expected Behavior

A HorizontalPager/LazyRow/Scrollable Row with items that have a background blur and clip applied

Actual Behavior

They do have background blur but also some glitchy effects, it occurs more often with a background image loaded with Coil and the blurred contained has shape applied. When the items have background blur with Rectangle shape applied the same happens only on the background with a color, at least I was not able to replicate it when the background is an image and the shape is a Rectangle

Screen_recording_20240429_145551.webm

Screen_recording_20240429_145959.webm

Steps to Reproduce the Problem

The code I used to reproduce, with and empty new project:

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PageSize
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.example.hazehorizontalpager.ui.theme.HazeHorizontalPagerTheme
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.haze
import dev.chrisbanes.haze.hazeChild
import dev.chrisbanes.haze.materials.HazeMaterials

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            HazeHorizontalPagerTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->

                    Column(
                        modifier = Modifier
                            .fillMaxSize()
                            .padding(innerPadding)
                    ) {
                        ShowcaseHorizontalPager(
                            modifier = Modifier
                                .fillMaxWidth()
                                .weight(1f)
                            ,
                            imgUrl = "https://picsum.photos/200/300",
                            showImg = true,
                        )

                        ShowcaseHorizontalPager(
                            modifier = Modifier
                                .fillMaxWidth()
                                .weight(1f)
                            ,
                            imgUrl = "https://picsum.photos/200/300",
                            showImg = false,
                        )
                    }

                }
            }
        }
    }
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ShowcaseHorizontalPager(
    modifier: Modifier = Modifier,
    imgUrl: String,
    showImg: Boolean,
) {

    val dropsHazeState = remember { HazeState() }

    val pagerState = rememberPagerState { 6 }

    val maxWidth = LocalConfiguration.current.screenWidthDp.dp
    val itemWidth = maxWidth * 0.78f

    Box(
        modifier = modifier
            .fillMaxSize()
        ,
        contentAlignment = Alignment.Center,
    ) {

        if (showImg) {
            AsyncImage(
                contentScale = ContentScale.Crop,
                modifier = Modifier
                    .fillMaxSize()
                    .background(Color.White)
                    .haze(
                        dropsHazeState,
                        HazeMaterials.regular(Color(0x80808080))
                    )
                ,
                model = ImageRequest.Builder(LocalContext.current)
                    .data(imgUrl)
                    .crossfade(true)
                    .build(),
                contentDescription = null
            )
        } else {
            Box(
                modifier = Modifier
                    .fillMaxSize()
                    .background(Color.Blue)
                    .haze(
                        dropsHazeState,
                        HazeMaterials.regular(Color(0x80808080))
                    )
            ) {

            }
        }

        Column(
            modifier = Modifier
                .fillMaxWidth()
                .align(Alignment.BottomCenter),
            verticalArrangement = Arrangement.spacedBy(20.dp)
        ) {

            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .horizontalScroll(rememberScrollState()),
                horizontalArrangement = Arrangement.spacedBy(20.dp),
            ) {

                Spacer(
                    modifier = Modifier.width(20.dp)
                )

                repeat(6) {
                    key(it) {
                        BoxItem(
                            modifier = Modifier
                                .width(itemWidth),
                            hazeState = dropsHazeState,
                            index = it
                        )
                    }
                }

                Spacer(
                    modifier = Modifier.width(20.dp)
                )
            }

            LazyRow(
                modifier = Modifier
                    .fillMaxWidth()
                ,
                contentPadding = PaddingValues(20.dp),
                horizontalArrangement = Arrangement.spacedBy(20.dp, alignment = Alignment.CenterHorizontally)
            ) {
                repeat(6) {
                    item(key = "$it") {
                        BoxItem(
                            modifier = Modifier.fillParentMaxWidth(0.78f),
                            hazeState = dropsHazeState,
                            index = it
                        )
                    }
                }
            }

            HorizontalPager(
                modifier = Modifier
                    .fillMaxWidth()
                ,
                state = pagerState,
                pageSize = PageSize.Fixed(itemWidth),
                pageSpacing = 20.dp,
                contentPadding = PaddingValues(20.dp),
            ) { index ->
                BoxItem(
                    modifier = Modifier.fillMaxWidth(),
                    hazeState = dropsHazeState,
                    index = index
                )
            }
        }

    }
}

@Composable
fun BoxItem(
    modifier: Modifier,
    hazeState: HazeState,
    index: Int,
) {
    Row(
        modifier = modifier
            .height(65.dp)
            .hazeChild(hazeState, RoundedCornerShape(12.dp))
        ,
        horizontalArrangement = Arrangement.Center,
        verticalAlignment = Alignment.CenterVertically,
    ) {
        Text(
            text = "$index",
            color = Color.White,
            fontSize = 18.sp,
            fontWeight = FontWeight.Bold,
        )
    }
}

Libraries and Versions:

From a clean project just added coil and haze

[versions]
agp = "8.4.0-rc02"
kotlin = "1.9.0"
coreKtx = "1.13.0"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
lifecycleRuntimeKtx = "2.7.0"
activityCompose = "1.9.0"
hazeVersion = "0.7.0"
coilVersion = "2.6.0"
composeBom = "2024.04.01"

[libraries]
coil = { module = "io.coil-kt:coil", version.ref = "coilVersion" }
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilVersion" }
haze = { module = "dev.chrisbanes.haze:haze", version.ref = "hazeVersion" }
haze-materials = { module = "dev.chrisbanes.haze:haze-materials", version.ref = "hazeVersion" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

Applied:

implementation(libs.coil)
implementation(libs.coil.compose)
implementation(libs.haze)
implementation(libs.haze.materials)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
chrisbanes commented 7 months ago

So there's 2 issues here by the look of it: