Glide Version: 4.2.0
Integration libraries: none
Issue details: I have an ImageView that needs to be refreshed often, but each time there is a change the image flickers white (unless it is present in the memory cache).
This must be the default behavior, as I can see in the basic lifecycle of a Glide request:
Is there a way to disable the default clearing of the ImageView on step 1) until the next image is loaded in order to prevent the flicker?
The following works, but I was wondering if there is a simpler solution, for example by disabling animations. I read something about a dontAnimate() method but can't find it on 4.2.0.
Glide
.with(thisActivity)
.asBitmap()
.load(someUrl)
.into(object : SimpleTarget<Bitmap>() {
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>) {
imageView.setImageBitmap(resource)
}
})
Sounds like http://bumptech.github.io/glide/doc/transitions.html#cross-fading-across-requests.
Your solution is unsafe in that your first request may complete after your second request, which would result in you showing an incorrect image. This would occur with your solution because you're not clearing out the first request when you start the second one. Any solution where you completely disable the clearing step will have the same problem.
Instead a ViewSwitcher (or any combination of multiple Views) will let you neatly display one image and then the next only when the second image is ready.
An alternative to what Sam suggested is loading the current image as a thumbnail for the next one. By doing this, the flicker will disappear since you'll extremely likely hit the memory cache and the start the next asynchronous load. See #861 for more (it assumes that loads are uniform in size and params).
Hey @sjudd, @TWiStErRob, thank you for your answers! For now I will solve it with a ViewSwitcher.
However, it feels strange that there is no way to cancel a request without also clearing the ImageView.
Maybe this could be an enhancement. Here is a possible solution:
Glide.with(this).load(firstUrl).into(imageView);
// Overloaded method that optionally clears the view (or not) besides cancelling
boolean clearOnCancel = false;
Glide.with(this).load(nextUrl).into(imageView, clearOnCancel);
What do you think?
It's possible to add this kind of API using the RequestCoordinator interface. It's a little tricky to explain and a little tricky to get the clearing behavior correct. To allow for cross fades, you probably want to clear the original image as soon as the new image is finished loading and animating. In other cases you might actually want both images until you explicitly clear the view.
If you avoid clearing the requests until the user clears the View, you might introduce a memory leak where users just call into repeatedly with clearOnCancel false, forcing every previous request to accumulate.
If you avoid clearing the requests until the user clears the View, you might introduce a memory leak where users just call into repeatedly with clearOnCancel false, forcing every previous request to accumulate.
I'm not sure I understand this part. What I meant with clearOnCancel was the clearing of the view, not of the pending requests.
As it is now, when I make a second request on a view, it first clears any pending request AND it also clears the view. I want to prevent this last effect, the clearing of the view, which causes a flicker whenever there is a change.
Clearing the View and the Request have to go together. When the Request is cleared, Glide releases and may re-use the Bitmap for that Request. It can't be left in a View after that point.
Aaaah ok, thank you for the explanation, I wasn't aware of the implementation.
I'm closing the issue because it can be easily solved with a ViewSwitcher, as you mentioned earlier :)
But I still think there should be a way to decouple the clearing of the request and the clearing of the view.
Sure let's leave this open as an enhancement to consider in the future.
@EnricSala How do you fix this problem with ViewSwitcher? Can you show me the code ?I just have the same issue like yours. Looking forword to getting your response.Thanks!
@sjudd Can you show me some example code about the solution using ViewSwitcher to solve this issue?
@fragrantforest to solve the problem I made a class that wraps a ViewSwitcher, similar to this:
class GlideSwitcher(private val context: Context,
private val viewSwitcher: ViewSwitcher) {
var imageUrl: String? = null
set(value) {
if (value != null) show(value) else clear()
field = value
}
private fun show(url: String) {
val current = viewSwitcher.currentView as ImageView
val next = viewSwitcher.nextView as ImageView
Glide.with(context)
.load(url)
.apply(RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.NONE)
.override(current.width, current.height)
.centerCrop())
.listener(listener)
.into(next)
}
private fun clear() {
val current = viewSwitcher.currentView as ImageView
Glide.with(context).clear(current)
}
private val listener = object : RequestListener<Drawable> {
override fun onResourceReady(resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean): Boolean {
viewSwitcher.showNext()
return false
}
override fun onLoadFailed(e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean): Boolean {
Timber.e(e, "Error loading image")
return true
}
}
}
To use it, just set or clear the imageUrl field.
I'm not sure if this solution is the most correct but it worked for me.
@fragrantforest to solve the problem I made a class that wraps a
ViewSwitcher, similar to this:class GlideSwitcher(private val context: Context, private val viewSwitcher: ViewSwitcher) { var imageUrl: String? = null set(value) { if (value != null) show(value) else clear() field = value } private fun show(url: String) { val current = viewSwitcher.currentView as ImageView val next = viewSwitcher.nextView as ImageView Glide.with(context) .load(url) .apply(RequestOptions() .diskCacheStrategy(DiskCacheStrategy.NONE) .override(current.width, current.height) .centerCrop()) .listener(listener) .into(next) } private fun clear() { val current = viewSwitcher.currentView as ImageView Glide.with(context).clear(current) } private val listener = object : RequestListener<Drawable> { override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean { viewSwitcher.showNext() return false } override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean { Timber.e(e, "Error loading image") return true } } }To use it, just set or clear the
imageUrlfield.I'm not sure if this solution is the most correct but it worked for me.
Thank you very much !
I'm very confused about this problem! Why it need clear the view before load a new image, this doesn't make sense, and I don't think ViewSwitcher is a good solution, because if I use recyclerview or listview, the numbers of views need x2 !
Another problem that confuses me is that the load request is not synchronized! For example, I load two images, one gif, one png, and load gif is slower than loading png, so if gif is loaded first, then png is loaded before the gif is loaded, and finally gif is displayed instead of png!
In my opinion, for a view, I think all requests should be running synchronously, and when a new request arrives, the old request should be canceled!
you can set placeholder as current image drawable
.placeholder(view.drawable)
be aware to do it with items in RecyclerView
Most helpful comment
you can set placeholder as current image drawable
be aware to do it with items in RecyclerView