最近在开发中发现RecyclerView刷新列表(notifyDataSetChanged)时,如果使用的是Glide加载图片,那么会出现图片闪烁、图片变形的问题,针对于此有着千奇百怪的解决方案 ~
首先我的问题已经解决,但是尝试了不少方式,特此做一下归纳 ~
问题背景
每个人的场景可能不同,仅记录我的场景,解决方式可能通用,可一试
如果对 Glide
基础方面,了解不足的话可以去Glide、Picasso的简单使用与自封工具 简单巩固下
背景 | 框架、场景 |
---|---|
列表控件 | RecyclerView |
图片框架 | Glide(4.0) |
问题场景 | 购物车+、- 数据变化 |
所遇问题 | 每次+、- 导致图片闪烁 |
刷新方式 | 整体刷新(adapter.notifyDataSetChanged() ) |
成功方式
方式千千万,吾仅通此路 ~
- 修改
RecyclerIView
刷新方式
之前
//整体刷新
adapter.notifyDataSetChanged()
改后
//局部刷新
adapter.notifyItemChanged(position);
- 帮对应的
adapter
设置一下属性
//作用等同于帮position标记tag
adapter.setHasStableIds(true);
- 对应
adapter
内重写以下方法
@Override
public long getItemId(int position) {
return position;
}
Glide
采用缓存模式加载,记得取消动画,具体如下
public static void initImageWithFileCache(Context context, String url, ImageView imageView) {
GlideApp.with(context)
.load(url)
.placeholder(R.mipmap.banner_loading)
.error(R.mipmap.banner_loading_error)
//可去可不去,这条动画并没影响到我,应该被下方设置取消了 - -
.transition(DrawableTransitionOptions.withCrossFade(100)
//全部缓存,原图与压缩图
.diskCacheStrategy(DiskCacheStrategy.ALL)
//注:是否跳过内存缓存,设置为false,如为true的话每次闪烁也正常~
.skipMemoryCache(false)
//取消Glide自带的动画
.dontAnimate()
.fitCenter()
.into(imageView);
}
解决方式
我总结了一下此问题的解决方式,除了一些通用的解决方式之外,主要体现还是体现在动画
和 position
、tag
俩方面
通用方面
针对一些基础设置,可能有效,也可能无效,但肯定不会产生新的问题 ~
Glide相关 - 上下文
常规加载图片时传入的上下文大多为当前上下文,这里我们采用Application全局的上下文,如没有的话就写个新类extend一下Application,然后出个返回上下文的方法就行 ~
Glide相关 - 勿跳过内存缓存
查看Glide加载时是否有设置 skipMemoryCache
相关属性,此部分主要判断用户是否设置内存缓存,如true则为跳过,所以记得false ~
.skipMemoryCache(false)
动画方面
动画方面主要涉及RecyclerView的自带动画和Glide的自带动画 ~
RecyclerView动画
RecyclerView
默认设置了自带的动画效果,也就是DefaultItemAnimator
默认动画类,同时在内部设置动画主要为animateChangeImpl
方法,可以从下图中看出图片闪烁的原因很大可能是 animateChangeImpl()
方法中 alpha
(透明动画)的实现导致的,所以我们可以将此类复制出来重新进行设置,具体如下 ~
方式1:设置新动画
亲试,无效,可跳过!!!
- 通过
Ctrl+Shift+F
搜索animateChangeImpl()
方法,然后找到DefaultItemAnimator
类 - 将
DefaultItemAnimator
类整体复制到一个新建类中,但是要记得删除图中俩处标记的alpha
设置 RecyclerView
通过setItemAnimator
重新设置动画效果,如下 ~
mRv.setItemAnimator(new 新建动画类());
方式2:取消自带动画
不推荐:亲试,有效,虽然闪烁场景较少,但是依旧存在闪烁问题,故有效性较低 ~
// 取消动画效果
((DefaultItemAnimator) mRv.getItemAnimator()).setSupportsChangeAnimations(false);
Glide动画
这里主要调用Glide
中的 dontAnimate()
方法用于取消 Glide
的动画效果 ~
GlideApp.with(context)
.load(url)
.placeholder(R.mipmap.banner_loading)
.error(R.mipmap.banner_loading_error)
//全部缓存,原图与压缩图
.diskCacheStrategy(DiskCacheStrategy.ALL)
//取消Glide自带的动画
.dontAnimate()
.fitCenter()
.into(imageView);
Tag方面
此方式主要是通过帮每个 postion
做标记,跳过常规的每次加载 ~
RecyclerView
为 RecyclerView
标记Tag,主要判别 url 是否改变,从而判别 ImageView
是否需要重新加载,但是仅做此设置会导致数据项重复!
setHasStableIds(true);
So Here:
adapter里面重写 getItemId 即可解决数据项重复的问题 ~
@Override
public long getItemId(int position) {
return position;
}
Glide
逻辑比较简单,首次加载ImageView通过tag判断是否加载过,如已加载过则不进行二次加载,如为首次加载则做个tag标记 ~ (无用)
public static void initImageWithFileCache(Context context, String url, ImageView imageView) {
//解决图片加载不闪烁的问题,可以在加载时候,对于已经加载过的item
if (!url.equals(imageView.getTag())) {
imageView.setTag(null);
GlideApp.with(context)
.load(url)
.placeholder(R.mipmap.banner_loading)
.error(R.mipmap.banner_loading_error)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.dontAnimate()
.skipMemoryCache(false)
.fitCenter()
.into(imageView);
imageView.setTag(url);
}
}
场景扩展
Glide设置请求头之后图片加载闪烁(缓存失效)
场景商未遇到,仅做记录,借鉴与此
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.Headers;
import java.net.URL;
import java.util.Map;
public class IMTokenGlideUrl extends GlideUrl {
private int mHashCode;
public IMTokenGlideUrl(URL url) {
super(url);
}
public IMTokenGlideUrl(String url) {
super(url);
}
public IMTokenGlideUrl(URL url, Headers headers) {
super(url, headers);
}
public IMTokenGlideUrl(String url, Headers headers) {
super(url, headers);
}
@Override
public boolean equals(Object o) {
if (o instanceof GlideUrl) {
GlideUrl other = (GlideUrl) o;
return getCacheKey().equals(other.getCacheKey())
&& !mapCompare(getHeaders(), other.getHeaders());
}
return false;
}
@Override
public int hashCode() {
if (mHashCode == 0) {
mHashCode = getCacheKey().hashCode();
if (getHeaders() != null) {
for (String s : getHeaders().keySet()) {
if (getHeaders().get(s) != null) {
mHashCode = 31 * mHashCode + getHeaders().get(s).hashCode();
}
}
}
}
return mHashCode;
}
private static boolean mapCompare(Map<String, String> map1, Map<String, String> map2) {
boolean differ = false;
if (map1 != null && map2 != null) {
if (map1.size() == map2.size()) {
for (Map.Entry<String, String> entry1 : map1.entrySet()) {
String value1 = entry1.getValue() == null ? "" : entry1.getValue();
String value2 = map2.get(entry1.getKey()) == null ? "" : map2.get(entry1.getKey());
if (!value1.equals(value2)) {
differ = true;
break;
}
}
}
} else differ = map1 != null || map2 != null;
return differ;
}
}
Glide加载圆角图片时,出现图片闪烁
场景尚未遇到,仅做记录,借鉴与此
闪烁原因:对于任何 Transformation
子类,包括 BitmapTransformation
,你都必须实现这三个方法,以使得磁盘和内存缓存正确地工作
equals()
hashCode()
updateDiskCacheKey
自定义圆角类(GlideRoundTransform)
public class GlideRoundTransform extends BitmapTransformation {
private final float radius;
private final String ID = "com. bumptech.glide.transformations.FillSpace";
private final byte[] ID_ByTES= ID.getBytes(CHARSET);
public GlideRoundTransform(int dp){
this.radius = Resources.getSystem().getDisplayMetrics().density * dp;
}
@Override
protected Bitmap transform(@NonNull BitmapPool pool, @NonNull Bitmap toTransform, int outWidth, int outHeight){
return roundCrop(pool, toTransform);
}
private Bitmap roundCrop(BitmapPool pool, Bitmap toTransform) {
if (toTransform == null) {
return null;
}
Bitmap result = pool.get(toTransform.getWidth(), toTransform.getHeight(), Bitmap.Config.ARGB_8888);
if (result == null) {
return Bitmap.createBitmap(toTransform.getWidth(), toTransform.getHeight(), Bitmap.Config.ARGB_8888);
}
Canvas canvas = new Canvas(result);
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setShader(new BitmapShader(toTransform, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
RectF rectF = new RectF(0f, 0f, toTransform.getWidth(), toTransform.getHeight());
canvas.drawRoundRect(rectF, radius, radius, paint);
return result;
}
@Override
public boolean equals(Object o){
if (o instanceof GlideRoundTransform){
GlideRoundTransform other = (GlideRoundTransform) o;
return radius == other.radius;
}
return false;
}
@Override
public int hashCode() {
return Util.hashCode(ID.hashCode(),
Util.hashCode(radius));
}
@Override
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest){
messageDigest.update(ID_ByTES);
byte[] radiusData =ByteBuffer.allocate(4).putInt((int) radius).array();messageDigest.update(radiusData);
}
}
使用方式
Glide.with(mContext)
.load(url)
.apply(new RequestOptions()
.transform( new GlideRoundTransform(4)))
.placeholder(placeimg).fallback(placeimg)
.error(placeimg)
.into(imageView);