Android进阶之路 - RecyclerView、Glide刷新图片时闪烁

最近在开发中发现RecyclerView刷新列表(notifyDataSetChanged)时,如果使用的是Glide加载图片,那么会出现图片闪烁、图片变形的问题,针对于此有着千奇百怪的解决方案 ~

首先我的问题已经解决,但是尝试了不少方式,特此做一下归纳 ~

问题背景

每个人的场景可能不同,仅记录我的场景,解决方式可能通用,可一试

列表控件:RecyclerView
图片框架:Glide(4.0)
问题场景:购物车+、- 数据变化
所遇问题:每次+、- 导致图片闪烁
刷新方式:整体刷新(adapter.notifyDataSetChanged())

成功方式

方式千千万,吾仅通此路 ~

  1. 改变RecyclerIView的刷新方式

之前

 //整体刷新
 adapter.notifyDataSetChanged()

改后

 //局部刷新
 adapter.notifyItemChanged(position);
  1. 帮对应的adapter设置一下属性
 //作用等同于帮position标记tag
 adapter.setHasStableIds(true);
  1. 对应adapter内重写以下方法
    @Override
    public long getItemId(int position) {
    
    
        return position;
    }
  1. 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:设置新动画

亲试,无效

  1. 通过 Ctrl+Shift+F 搜索animateChangeImpl()方法,然后找到DefaultItemAnimator类
  2. 将DefaultItemAnimator类整体复制到一个新建类中,但是要记得删除图中俩处标记的alpha设置
  3. 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);

猜你喜欢

转载自blog.csdn.net/qq_20451879/article/details/114526304