skydoves / landscapist

🌻 A pluggable, highly optimized Jetpack Compose and Kotlin Multiplatform image loading library that fetches and displays network images with Glide, Coil, and Fresco.
https://skydoves.github.io/landscapist/
Apache License 2.0
2.03k stars 113 forks source link

White background #308

Closed taasonei closed 3 weeks ago

taasonei commented 1 year ago

Please complete the following information:

Describe the Bug:

I've tried GlideImage and CoilImage. Both show white background when image loading. It appears before loading placeholder:
error placeholder -> white background -> loading placeholder -> loaded image
There is no white background when using coil or glide compose instead

Sample code

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ImageTestTheme {
                Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
                    MainScreen()
                }
            }
        }
    }
}

@Composable
private fun MainScreen() {
    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        var imageRes by remember { mutableStateOf("") }
        key(imageRes) {
            CoilImage(
                modifier = Modifier
                    .height(300.dp)
                    .fillMaxWidth(),
                imageModel = remember { { imageRes } },
                imageOptions = remember {
                    ImageOptions(
                        contentScale = ContentScale.Crop,
                    )
                },
                previewPlaceholder = R.drawable.ic_empty_image,
                component = rememberImageComponent {
                    +ShimmerPlugin(
                        baseColor = Color.DarkGray,
                        highlightColor = Color.LightGray
                    )
                    +PlaceholderPlugin.Failure(painterResource(id = R.drawable.ic_broken_image))
                },
            )
        }
        Button(
            onClick = remember { { imageRes = Generator.generate() } } ) {
            Text(text = "Update")
        }
    }
}

object Generator {
    private val list = listOf(
        "https://proprikol.ru/wp-content/uploads/2019/08/kartinki-nyashnye-kotiki-16.jpg",
        "https://www.wallpaperflare.com/static/688/210/437/landscape-nature-sunset-river-wallpaper.jpg",
        "https://i.pinimg.com/736x/d1/db/93/d1db93fed23ae10972bfac57406142a9.jpg"
    )

    fun generate(): String = list.random()
}

Video sample (slowing)

https://github.com/skydoves/landscapist/assets/58473570/7dd552a1-bccd-4b4d-abb2-6183585ec2f8

Expected Behavior:

error placeholder -> loading placeholder -> loaded image

skydoves commented 1 year ago

@taasonei I think this result is an expected result because Landscapist doesn't compose everything from the first, it observes the state and compose each different composables depending on its state.

Let me think if there's a great way to make the transition smooth with fade animation or something.

skydoves commented 1 year ago

@taasonei I'm wondering what if you use the Crossfade over GlideImage like the sample below?

Crossfade(targetState = imageRes, label = "") { image ->
        CoilImage(
        )
..
taasonei commented 1 year ago

@skydoves looks like there is no difference when I add Crossfade, white background's still visible

aznj commented 1 year ago

any update on this issue?

skydoves commented 1 year ago

Hey guys, @taasonei @aznj I wonder if this issue occurs only when the error placeholder is displayed first.

JayyyR commented 11 months ago

A similar thing is happening for me. It's a gray box that appears every time before the loading preview appears. There's no error placeholder being shown. It's especially jarring while scrolling in a grid with multiple items because you see a lot of gray boxes really quickly and then the loading state/image.

I think this could be the same issue but our apps have different default color schemes? Not sure though

skydoves commented 11 months ago

Hey @JayyyR, sorry for the delayed response. Do you use a shimmering effect or what placeholder do you use?

It would be really helpful to debug if you could share the imageComponent implementation on your project. Thanks!

curioustechizen commented 7 months ago

I use a shimmer effect with landscapist-coil and I see this problem.

The first time that this screen is opened, there's the regular shimmer (for a few seconds) because the image is being loaded from the network.

Next time I open this screen, I see is a brief shimmer appears in the UI before disappearing. It looks like the shimmer is being applied for a very brief duration (perhaps less than 100ms), but in reality the shimmer was not actually needed (because the image was fetched from cache).

Here's the code I use (landscapist 2.2.13):

if (classPreviewState.thumbnailUrl != null) {
    CoilImage(
        imageModel = { classPreviewState.thumbnailUrl },
        imageOptions = ImageOptions(
            contentScale = ContentScale.Crop,
            alignment = Alignment.Center
        ),
        component = rememberImageComponent {
            +ShimmerPlugin(
                baseColor = colorResource(id = R.color.background_blue_light),
                highlightColor = colorResource(id = R.color.highlight_purple)
            )
        },
        modifier = Modifier.fillMaxSize(),
    )
}
skydoves commented 7 months ago

@curioustechizen that sounds very fair. Thank you for the details! Let me fix this in the next stable release.

skydoves commented 3 weeks ago

Hey @taasonei @curioustechizen @aznj @JayyyR, sorry for the delayed response.

Here's the solution that I've found the way that you can switch images without displaying the background using AnimatedContent.

  var palette by rememberPaletteState(null)
  var imageRes by remember { mutableStateOf("") }

  AnimatedContent(
    targetState = imageRes, label = "",
    transitionSpec = {
      (fadeIn(animationSpec = tween(500, delayMillis = 0)))
        .togetherWith(fadeOut(animationSpec = tween(500, delayMillis = 0)))
    }
  ) { res ->
    GlideImage(
      imageModel = { res },
      modifier = Modifier.aspectRatio(0.8f).background(Color.Red),
      component = rememberImageComponent {
        +ShimmerPlugin(
          Shimmer.Resonate(
            baseColor = if (isSystemInDarkTheme()) {
              Color.Yellow
            } else {
              Color.White
            },
            highlightColor = Color.Green,
          ),
        )
        +PlaceholderPlugin.Failure(painterResource(id = R.drawable.ic_android))
        +PalettePlugin { palette = it }
      },
      previewPlaceholder = painterResource(id = R.drawable.poster),
    )
  }

  Button(
    onClick = remember { { imageRes = Generator.generate() } } ) {
    Text(text = "Update")
  }

This is the result:

2024-08-03 19 11 27

I hope this solution can resolve your issues!