Android实战场景 - 刷新列表时Glide加载图片闪烁

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

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

问题背景

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

如果对 Glide 基础方面,了解不足的话可以去Glide、Picasso的简单使用与自封工具 简单巩固下

背景框架、场景
列表控件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);
    }

解决方式

我总结了一下此问题的解决方式,除了一些通用的解决方式之外,主要体现还是体现在动画positiontag 俩方面

通用方面

针对一些基础设置,可能有效,也可能无效,但肯定不会产生新的问题 ~

  • 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);
  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

远方那座山

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值