1. 程式人生 > >Glide坑遇記:寬度鋪滿高度自適應 & GIF載入之坑

Glide坑遇記:寬度鋪滿高度自適應 & GIF載入之坑

有一段時間沒有更新文章了,但登入 簡書 發現時不時也有新的點贊和關注,在這裡十分感謝大家的認可,也為這段時間的靜默表示抱歉。這段時間確實有點忙,自己一直在利用 一些平臺(這裡不說出平臺名稱了,一來避嫌,二來現在對這些平臺提供的資料也沒太多好感了) 提供的介面,敲寫一個生活工具類的App,一方面是使用 Retrofit + RxJava + okhttp 這種比較熱門的框架結構熟悉一下,一方面是集體測試一下 GitHub 平時不太使用的熱門庫。現在這個專案還在開發中,上線後會通知大家,也會在適當的時候把這個專案的原始碼開源到 GitHub,同時也會寫一些這個專案用到的大家感興趣的技術的介紹文章。好了,囉嗦的夠多了,開始今天的正題...

Glide 的基本使用可以檢視下面這些文章:

1、Glide 實現 ImageView 寬度填滿,高度自適應的效果

要說這個,就要先說一下大家在平時用到 ImageView 實現寬度填滿,高度自適應的方法。

ImageView 寬度填滿,高度自適應常用在:

  1. ListView 列表佈局的條目中(RecycleView 同理),比如實現 item 中的圖片充滿螢幕,高度根據具體圖片比例自適應,商品詳情中常常用到。
  2. GridView 網格佈局的條目中,假如 item 有兩列,想讓每一列的 item 中的圖片佔用螢幕的一半。
  3. 其他使用單獨圖片也想達到這種效果的場景。

這裡提供兩種實現方法,也都是大家都知道:

1、重寫 onMeasure 方法

public class ResizableImageView extends ImageView {  
  
    public ResizableImageView(Context context) {  
        super(context);  
    }  
  
    public ResizableImageView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
    }  
  
    @Override  
    protected void onMeasure
(int widthMeasureSpec, int heightMeasureSpec)
{ Drawable d = getDrawable(); if(d!=null){ int width = MeasureSpec.getSize(widthMeasureSpec); //高度根據使得圖片的寬度充滿螢幕計算而得 int height = (int) Math.ceil((float) width * (float) d.getIntrinsicHeight() / (float) d.getIntrinsicWidth()); setMeasuredDimension(width, height); }else{ super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } }

在佈局檔案 xml 中,ImageView 不用對顯示方式進行設定(使用預設就行)。

2、設定 ImageView 的屬性

<ImageView
        android:id="@+id/iv_ocnyang"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:adjustViewBounds="true"
        android:scaleType="fitXY"
        />

fitXY 這種圖片的顯示方式的效果是:根據 ImageView 設定的大小拉伸圖片以填充滿空間,(單獨設定此屬性時)圖片會變形。
adjustViewBounds 是限制圖片在顯示時保持原圖比例。(和 fitXY 顯示方式合用能到達自適應的效果)

通過這上面兩種方式顯示圖片一般都能夠寬度充滿高度自適應的效果,可是當你用 Glide 請求顯示網路圖片的時候,你會很失望的發現上面的設定失效了同時圖片也變形了。

那麼這時候是哪裡出了問題了呢?(下面只做一個籠統的分析,具體可以看這個連結: Glide使用及注意的地方
其實如果你熟知 Glide 的話,可能你還記得,Glide 在載入圖片的時候,載入的大小會和 ImageView 的大小保持一致。也就是 ImageView 的大小決定了 Glide 載入圖片的尺寸。而這裡我們的 ImageView 設定的高度是 wrap_content,Glide 就無法準確的載入圖片的大小了。

那這個時候怎麼才能保證按原圖的比例來自適應高度顯示呢?

這裡有兩種方式:

  1. 你已經知道圖片(或其他方式提前知道)圖片的比例,然後在用 Glide 請求圖片時限制圖片的載入大小,即設定 override(int width, int height) 。這時候載入到的圖片是原圖比例,顯示的時候雖然有拉伸/壓縮但都會儲存原比例的。這種方式適用於你載入的圖片大小都比較規範固定的時候。

  2. 當然,你請求的圖片源並不一定大小都一致。那這時候就可以使用下面這種方式了。這種方式的原理是,先使用 Glide 把圖片的原圖請求載入過來,然後再按原圖來顯示圖片。

    Glide.with(mContext)
    .load(url)
    .asBitmap()
    .into(new SimpleTarget<Bitmap>() {
    @Override
    public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
    ivOcnyang.setImageBitmap(resource);
    }
    });

這兩種方法中,其實更加提倡的是第一種方式,因為這種方式不會造成任何負面的影響。但第二種方式,由於Glide載入圖片時是以全解析度載入的,當載入圖片過大且圖片很多時,可能造成 OOM。同時第二種方式使用在列表上覆用時會造成條目錯亂錯位。

2、Glide 載入 Gif 圖片的那些坑

我們知道,對比其他幾大圖片載入框架,我們更青睞 Glide 有一部分原因就是因為它能顯示動態圖,畢竟像下面這種圖是讓我最高興最無法拒絕的。

每看到星期五,這兩個字我就莫名的興奮

2.1、載入 Gif 圖片慢或者顯示不出來

這是一個公認的問題了,在 Glide 的 issue 上有人提出過,並且作者也給出了解決方案
載入 GIF 時需要呼叫 asGif() 方法,同時設定特別的快取策略,呼叫 diskCacheStrategy() 將快取策略設定為 SOURCE(快取原圖) 或者 NONE(不做快取)。

Glide 在載入 GIF 時不呼叫 asGif() 方法也是能正常顯示動畫的。但建議呼叫 asGif()。

if (imgUrl.toUpperCase().endsWith(".GIF")) {
            Glide.with(mContext)
                    .load(imgUrl)
                    .asGif()
                    .override(width, height)
                    .placeholder(placeholderImg)
                    .error(errorImg)
                    .dontAnimate() //去掉顯示動畫
                    .centerCrop()
                    .diskCacheStrategy(DiskCacheStrategy.SOURCE) //DiskCacheStrategy.NONE
                    .into(ivOcnyang);
        } else {
            Glide.with(mContext)
                    .load(imgUrl)
                    .override(width, height)
                    .placeholder(placeholderImg)
                    .error(errorImg)
                    .crossFade()
                    .centerCrop()
                    .into(ivOcnyang);
        }

2.2、動態GIF圖片顯示的次數

可能你有時有需求,需要設定動態圖的顯示一定次數時停止。

Glide.with(mContext)
        .load(imgUrl)
        .asGif()
        .override(width,height)
        .placeholder(placeholderImg)
        .error(errorImg)
        .dontAnimate()
        .centerCrop()
        .diskCacheStrategy(DiskCacheStrategy.SOURCE)
        .into(new GlideDrawableImageViewTarget(ivOcnyang, 3));

這裡的 GlideDrawableImageViewTarget(ImageView view, int maxLoopCount) 的第二個引數 maxLoopCount 就是你要迴圈的次數。

2.3、將 GIF 作為 Bitmap 顯示

如果要顯示的圖片列表包含多種影象型別, 有圖片和 GIF, 全都強制判斷 GIF 有時是不可行的. 我們可以將 GIF 先作為 Bitmap 載入第一幀影象. 然後給使用者一個提示, 當用戶點選時, 再使用 GIF 方式重新載入。

Glide  
    .with(context)
    .load(gifUrl)
    .asBitmap()
    .into(imageViewGifAsBitmap);

3、Glide圖片和預設圖交替過程中,預設圖閃爍一下

這是比較坑的一點,如果佔位圖要比原圖大有時會出現這種問題。其實有時候你會發現佔位圖和 loading 圖的設定有時會造成各種問題,有時可能影響圖片顯示不正常。

解決方法:
去掉動畫:dontAnimate()

有時,使用 CircleImageView 載入圖片時顯示不正常,有可能是CircleImageView引起的與佔位圖和顯示動畫的衝突,解決方法同上,亦或去掉佔位圖。

4、CenterCrop與Transformer的共存問題

這個問題是在網格佈局和瀑布流佈局中使用 .centerCrop,所以必須要在 ImageView 中去設定 scaleType 為 centerCrop。
但是,當你同時給圖片設定圓角類 Transformer 時,即在 Glide 載入圖片時給 .transform() 配置了一個圓角矩形,如果同時 ImageView 的 scaleType 設定了 centerCrop,那圓角就沒有了。

解決方法,設定兩個 Transformer:

...
.transform(new CenterCrop(getContext())
          ,new GlideRoundTransform(getContext(), 25))
...
public class GlideRoundTransform extends BitmapTransformation {

    private static float radius = 0f;

    public GlideRoundTransform(Context context) {
        this(context, 4);
    }

    public GlideRoundTransform(Context context, int dp) {
        super(context);
        this.radius = Resources.getSystem().getDisplayMetrics().density * dp;
    }

    @Override protected Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
        return roundCrop(pool, toTransform);
    }

    private static Bitmap roundCrop(BitmapPool pool, Bitmap source) {
        if (source == null) return null;

        Bitmap result = pool.get(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);
        if (result == null) {
            result = Bitmap.createBitmap(source.getWidth(), source.getHeight(), Bitmap.Config.ARGB_8888);
        }

        Canvas canvas = new Canvas(result);
        Paint paint = new Paint();
        paint.setShader(new BitmapShader(source, BitmapShader.TileMode.CLAMP, BitmapShader.TileMode.CLAMP));
        paint.setAntiAlias(true);
        RectF rectF = new RectF(0f, 0f, source.getWidth(), source.getHeight());
        canvas.drawRoundRect(rectF, radius, radius, paint);
        return result;
    }

    @Override
    public String getId() {
        return getClass().getName() + Math.round(radius);
    }
}

5、得到類似 You cannot start a load for a destroyed activity 這樣的異常

解決這個辦法只需在使用 Glide 時記住:不要再非主執行緒裡面使用 Glide 載入圖片,如果真的使用了,請把 context 引數換成 getApplicationContext。

6、一些使用技巧

1. 列表預載入
如果你想讓列表預載入的話,不妨試一下ListPreloader這個類。

2. 列表滑動時取消請求
當列表在滑動的時候,呼叫 pauseRequests() 取消請求,滑動停止時,呼叫 resumeRequests() 恢復請求。

Glide.with(context).resumeRequests()
Glide.with(context).pauseRequests()

3. 清除所有載入請求
當你想清除掉所有的圖片載入請求時,可以使用 Glide.clear() 這個方法。

4. Glide特效轉換庫
glide-transformations 一個基於Glide的transformation庫,擁有裁剪,著色,模糊,濾鏡等多種轉換效果。

5. Palette 庫
GlidePalette 一個在Glide載入時很方便使用Palette的庫。