Glide 不同请求间的过渡效果
Glide 中允许我们通过 Transitions
来设置占位图,缩略图过度到请求的图片的动画效果。
DrawableCrossFadeFactory factory =
new DrawableCrossFadeFactory.Builder().build();
GlideApp.with(context)
.load(url)
.transition(withCrossFade(factory))
.placeholder(R.color.placeholder)
.into(imageView);
加载的图片透明度逐渐增加,覆盖 placeholder。
但是在不同的请求之间,每次都是变成 placehodler(或透明后)再渐变显示新的图片。
为什么不同请求间 Transition
不能平滑过渡
想要解决这个问题,我们需要先搞清楚,为什么不同请求间,Transition
不能平滑过渡。
首先,我们去看看 Transition
内部的动画怎么实现的
以
DrawableCrossFadeTransition
为例,BitmapCrossFadeTransition
内部也是调用DrawableCrossFadeTransition
实现的,可以自行去查看一下
@Override
public boolean transition(Drawable current, ViewAdapter adapter) {
Drawable previous = adapter.getCurrentDrawable();
if (previous == null) {
previous = new ColorDrawable(Color.TRANSPARENT);
}
TransitionDrawable transitionDrawable =
new TransitionDrawable(new Drawable[] {previous, current});
transitionDrawable.setCrossFadeEnabled(isCrossFadeEnabled);
transitionDrawable.startTransition(duration);
adapter.setDrawable(transitionDrawable);
return true;
}
可以发现,Glide 的切换动画其实就是利用了 TransitionDrawable
那么,这里的 previous
和 current
就是重点了,我们 debug 一下,看看他们的值。
debug 后,我们发现:
- 当初次加载图片的时候,
previous
为placeholder
(如果设置了) - 而当切换 url 后,
adapter.getCurrentDrawable()
则为空了
所以,每次切换请求,图片都是从透明渐变到请求的图片。
为什么 adapter.getCurrentDrawable()
会为空呢
debug 的时候可以发现,这里的 ViewAdapter
实际上就是我们的 DrawableImageViewTarget
,继承自 ImageViewTraget
代码有点长,就不放出来了,自行查看
其中 getCurrentDrawable()
方法则是直接返回 ImageView.getDrawable()
。那么,我们就看看哪里设置了 Drawable. 我们重点关注下面几个方法。
/*
加载前调用,设置 placeholder
*/
@Override
public void onLoadStarted(@Nullable Drawable placeholder) {
super.onLoadStarted(placeholder);
setResourceInternal(null);
setDrawable(placeholder);
}
/*
切换请求和推出时调用
1. 停止动画
2. 设置 placeholder
*/
@Override
public void onLoadCleared(@Nullable Drawable placeholder) {
super.onLoadCleared(placeholder);
if (animatable != null) {
animatable.stop();
}
setResourceInternal(null);
setDrawable(placeholder);
}
@Override
public void onResourceReady(@NonNull Z resource, @Nullable Transition<? super Z> transition) {
//加载成功后,切换显示图片,播放动画
if (transition == null || !transition.transition(resource, this)) {
setResourceInternal(resource);
} else {
maybeUpdateAnimatable(resource);
}
}
private void setResourceInternal(@Nullable Z resource) {
setResource(resource);
maybeUpdateAnimatable(resource);
}
Code from
ImageViewTraget
我们可以发现,问题就出在 onLoadCleared
方法上,每次切换请求的时候都重新设置了 placeholder。 如果没有设置 placeholder,结合上述 Transition 的代码,可以知道就会从透明过渡到下一个图片。
自定义 Target
既然知道问题所在,就来重写一下 Target
,去掉原本 ImaegViewTarget
中 onLadCleared
中的方法吧。
重写,运行后应该会出现 Execption:java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@xxxxxx
这是因为 Glide 在 onLoadCleared
的时候,把 Bitmap
回收了,所以显示下一图片时,TransitionDrawable
就会抛出这个问题,想要解决这个问题,就需要在 onResourceReady
中,copy 一份 Bitmap
进行显示了,但是这部分的 Bitmap
, Glide
就不能帮你回收了,要注意一下,这应该也是 Glide 没有默认就在不同请求间显示动画的原因吧。
最终代码:
class TransitionImageTarget(val imageView: ImageView) :
CustomViewTarget<ImageView, Drawable>(imageView), ViewAdapter {
private var animatable: Animatable? = null
override fun onLoadFailed(errorDrawable: Drawable?) {
setResourceInternal(null)
setDrawable(errorDrawable)
}
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
var dst: Drawable = resource
//避免Glide回收,造成
//java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@xxxxxx
if (resource is BitmapDrawable) {
val bmp = resource.bitmap
dst = BitmapDrawable(imageView.resources, bmp.copy(bmp.config, true))
}
if (transition == null || !transition.transition(dst, this)) {
setResourceInternal(dst)
} else {
maybeUpdateAnimatable(dst)
}
}
override fun onResourceCleared(placeholder: Drawable?) {
animatable?.stop()
}
private fun setResourceInternal(resource: Drawable?) {
setDrawable(resource)
maybeUpdateAnimatable(resource)
}
private fun maybeUpdateAnimatable(resource: Drawable?) {
if (resource is Animatable) {
animatable = resource as Animatable?
animatable?.start()
} else {
animatable = null
}
}
override fun getCurrentDrawable(): Drawable? {
return imageView.drawable
}
override fun setDrawable(drawable: Drawable?) {
imageView.setImageDrawable(drawable)
}
}
这时候,你应该就可以看到明显的过渡效果了,但是,当你重新加载缓存过的图片的时候动画消失了,这是因为 DrawableCrossFadeFactory
会判断当图片来自缓存时,使用 NoTransition
@Override
public Transition<Drawable> build(DataSource dataSource, boolean isFirstResource) {
return dataSource == DataSource.MEMORY_CACHE
? NoTransition.<Drawable>get()
: getResourceTransition();
}
code from
DrawableCrossFadeFactory
想要每次切换都显示,可以自定义 TransitionFactory
private var factory = TransitionFactory<Drawable> { dataSource, isFirstResource ->
DrawableCrossFadeTransition(1000, false)
}
Glide.with(this)
.load(images[(index++ % images.size)])
.placeholder(ColorDrawable(Color.GRAY))
.error(ColorDrawable(Color.DKGRAY))
.transition(DrawableTransitionOptions.with(factory))
.into(TransitionImageTarget(binding.ivResult))
最后
- 因为显示图片 copy 了一份
Bitmap
,所以要注意一下内存占用 - Demo
- 有更好的方法希望可以分享一下