Glide 占位符、选项、变换

Glide 占位符

Glide 选项

Glide 变换

一、占位符

1.1 类型

Glide 允许用户指定三种不同类型的占位符,分别在三种不同场景使用:

  • placeholder

  • error

  • fallback

1.1.1 占位符 (Placeholder)

占位符是当请求正在执行时被展示的 Drawable。当请求成功完成时,占位符会被请求到的资源替换。如果被请求的资源是从内存中加载出来的,那么占位符可能根本不会被显示。如果请求失败并且没有设置 error Drawable,则占位符将被持续展示。类似地,如果请求的 url/modelnull,并且 error Drawablefallback 都没有设置,那么占位符也会继续显示。

使用 generated API :

GlideApp.with(fragment)
  .load(url)
  .placeholder(R.drawable.placeholder)
  .into(view);

或者:

GlideApp.with(fragment)
  .load(url)
  .placeholder(new ColorDrawable(Color.BLACK))
  .into(view);

1.1.2 错误符 (Error)

error Drawable 在请求永久性失败时展示。error Drawable 同样也在请求的 url/modelnull,且并没有设置 fallback Drawable 时展示。

使用 generated API :

GlideApp.with(fragment)
  .load(url)
  .error(R.drawable.error)
  .into(view);

或者:

GlideApp.with(fragment)
  .load(url)
  .error(new ColorDrawable(Color.RED))
  .into(view);

1.1.3 后备回调符 (Fallback)

fallback Drawable 在请求的 url/modelnull 时展示。设计 fallback Drawable 的主要目的是允许用户指示 null 是否为可接受的正常情况。例如,一个 null 的个人资料 url 可能暗示这个用户没有设置头像,因此应该使用默认头像。然而,null 也可能表明这个元数据根本就是不合法的,或者取不到。 默认情况下Glide 将 null 作为错误处理,所以可以接受 null 的应用应当显式地设置一个 fallback Drawable

使用 generated API:

GlideApp.with(fragment)
  .load(url)
  .fallback(R.drawable.fallback)
  .into(view);

或者:

GlideApp.with(fragment)
  .load(url)
  .fallback(new ColorDrawable(Color.GREY))
  .into(view);

1.2 FAQ

1.2.1 占位符是异步加载的吗?

No。占位符是在主线程从 Android Resources 加载的。我们通常希望占位符比较小且容易被系统资源缓存机制缓存起来。

1.2.2 变换是否会被应用到占位符上?

No。Transformation 仅被应用于被请求的资源,而不会对任何占位符使用。

在应用中包含必须在运行时做变换才能使用的图片资源是很不划算的。相反,在应用中包含一个确切符合尺寸和形状要求的资源版本几乎总是一个更好的办法。假如你正在加载圆形图片,你可能希望在你的应用中包含圆形的占位符。另外你也可以考虑自定义一个 View 来剪裁 (clip) 你的占位符,而达到你想要的变换效果。

1.2.3 在多个不同的 View 上使用相同的 Drawable 可行么?

通常可以,但不是绝对的。任何无状态 (non-stateful) 的 Drawable(例如 BitmapDrawable)通常都是 ok 的。但是有状态的 Drawable 不一样,在同一时间多个 View 上展示它们通常不是很安全,因为多个 View 会立刻修改 (mutate) Drawable 。对于有状态的 Drawable ,建议传入一个资源 ID,或者使用 newDrawable() 来给每个请求传入一个新的拷贝。

二、选项

2.1 请求选项

Glide 中的大部分设置项都可以通过 RequestOptions 类和 apply() 方法来应用到程序中。

使用 request options 可以实现(包括但不限于):

  • 占位符 (Placeholders)

  • 转换 (Transformations)

  • 缓存策略 (Caching Strategies)

  • 组件特有的设置项,例如编码质量,或 Bitmap 的解码配置等。

例如,要应用一个 CenterCrop 转换,你可以使用以下代码:

import static com.bumptech.glide.request.RequestOptions.centerCropTransform;

Glide.with(fragment)
    .load(url)
    .apply(centerCropTransform(context))
    .into(imageView);

在这个例子中使用了对 RequestOptions 类里的方法的静态 import,这会让代码看起来更简洁流畅。

如果你想让你的应用的不同部分之间共享相同的加载选项,你也可以初始化一个新的 RequestOptions 对象,并在每次加载时传入这个对象:

RequestOptions cropOptions = new RequestOptions().centerCrop(context);
...
Glide.with(fragment)
    .load(url)
    .apply(cropOptions)
    .into(imageView);

apply() 方法可以被调用多次,因此 RequestOption 可以被组合使用。如果 RequestOptions 对象之间存在相互冲突的设置,那么只有最后一个被应用的 RequestOptions 会生效。

2.2 Generated API

如果你正在使用 Generated API, 所有的请求选项方法都会被内联并可以直接使用:

GlideApp.with(fragment)
    .load(url)
    .centerCrop()
    .into(imageView);

2.3 过渡选项

TransitionOptions 用于决定你的加载完成时会发生什么。

使用 TransitionOption 可以应用以下变换:

  • View 淡入

  • 与占位符交叉淡入

  • 或者什么都不发生

如果不使用变换,你的图像将会“跳入”其显示位置,直接替换掉之前的图像。为了避免这种突然的改变,你可以淡入 view,或者让多个 Drawable 交叉淡入,而这些都需要使用 TransitionOptions 完成。

例如,要应用一个交叉淡入变换:

import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade;

Glide.with(fragment)
    .load(url)
    .transition(withCrossFade())
    .into(view);

不同于 RequestOptionsTransitionOptions 是特定资源类型独有的,你能使用的变换取决于你让 Glide 加载哪种类型的资源。

这样的结果是,假如你请求加载一个 Bitmap ,你需要使用 BitmapTransitionOptions,而不是 DrawableTransitionOptions 。同样,当你请求加载 Bitmap 时,你只需要做简单的淡入,而不需要做复杂的交叉淡入。

2.4 RequestBuilder

RequestBuilder 是 Glide 中请求的骨架,负责携带请求的 url 和你的设置项来开始一个新的加载过程。

使用 RequestBuilder 可以指定:

  • 你想加载的资源类型 (BitmapDrawable,或其他)

  • 你要加载的资源地址 (url/model)

  • 你想最终加载到的 View

  • 任何你想应用的(一个或多个)RequestOption 对象

  • 任何你想应用的(一个或多个)TransitionOption 对象

  • 任何你想加载的缩略图 thumbnail()

要构造一个 RequestBuilder 对象,你可以通过先调用 Glide.with() 然后再调用某一个 as 方法来完成:

RequestBuilder<Drawable> requestBuilder = Glide.with(fragment).asDrawable();

或先调用 Glide.with() 然后 load()

RequestBuilder<Drawable> requestBuilder = Glide.with(fragment).load(url);

2.4.1 选择资源类型

RequestBuilders 是特定于它们将要加载的资源类型的。默认情况下你会得到一个 Drawable RequestBuilder,但你可以使用 as... 系列方法来改变请求类型。例如,如果你调用了 asBitmap(),你就将获得一个 BitmapRequestBuilder 对象,而不是默认的 Drawable RequestBuilder

RequestBuilder<Bitmap> requestBuilder = Glide.with(fragment).asBitmap();

2.4.2 应用 RequestOptions

前面提到,可以使用 apply() 方法应用 RequestOptions,使用 transition() 方法应用 TransitionOptions

RequestBuilder<Drawable> requestBuilder = Glide.with(fragment).asDrawable();
requestBuilder.apply(requestOptions);
requestBuilder.transition(transitionOptions);

RequestBuilder 也可以被复用于开始多个请求:

RequestBuilder<Drawable> requestBuilder =
        Glide.with(fragment)
            .asDrawable()
            .apply(requestOptions);

for (int i = 0; i < numViews; i++) {
   ImageView view = viewGroup.getChildAt(i);
   String url = urls.get(i);
   requestBuilder.load(url).into(view);

2.4.3 缩略图 (Thumbnail) 请求

Glide 的 thumbnail() API 允许你指定一个 RequestBuilder 以与你的主请求并行启动。thumbnail() 会在主请求加载过程中展示。如果主请求在缩略图请求之前完成,则缩略图请求中的图像将不会被展示。thumbnail() API 允许你简单快速地加载图像的低分辨率版本,并且同时加载图像的无损版本,这可以减少用户盯着加载指示器,例如进度条的时间。

thumbnail() API 对本地和远程图片都适用,尤其是当低分辨率缩略图存在于 Glide 的磁盘缓存时,它们将很快被加载出来。

thumbnail() API 使用起来相对简单:

Glide.with(fragment)
  .load(url)
  .thumbnail(Glide.with(fragment)
    .load(thumbnailUrl))
  .into(imageView);

只要你的 thumbnailUrl 指向的图片比你的主 url 的分辨率更低,它将会很好地工作。相当数量的加载 API 提供了不同的指定图片尺寸的方法,它们尤其适用于 thumbnail() API。

如果你仅仅想加载一个本地图像,或者你只有一个单独的远程 URL, 你仍然可以从缩略图 API 受益。请使用 Glide 的 overridesizeMultiplier API 来强制 Glide 在缩略图请求中加载一个低分辨率图像:

int thumbnailSize = ...;
Glide.with(fragment)
  .load(localUri)
  .thumbnail(Glide.with(fragment)
    .load(localUri)
    .override(thumbnailSize))
  .into(view);

thumbnail() 方法有一个简化版本,它只需要一个 sizeMultiplier 参数。如果你只是想为你的加载相同的图片,但尺寸为 ViewTarget 的某个百分比的话特别有用:

Glide.with(fragment)
  .load(localUri)
  .thumbnail(/*sizeMultiplier=*/ 0.25f)
  .into(imageView);

2.4.4 在失败时开始新的请求

从 Glide 4.3.0 开始,你现在可以使用 error API 来指定一个 RequestBuilder,以在主请求失败时开始一次新的加载。例如,在请求 primaryUrl 失败后加载 fallbackUrl

Glide.with(fragment)
  .load(primaryUrl)
  .error(Glide.with(fragment)
      .load(fallbackUrl))
  .into(imageView);

如果主请求成功完成,这个 error RequestBuilder 将不会被启动。如果你同时指定了一个 thumbnail() 和一个 error() RequestBuilder,则这个后备的 RequestBuilder 将在主请求失败时启动,即使缩略图请求成功也是如此。

2.5 组件选项

Option 类是给 Glide 的组件添加参数的通用办法,包括 ModelLoadersResourceDecodersResourceEncodersEncoders 等等。一些 Glide 的内置组件提供了设置项,自定义的组件也可以添加设置项。

Option 通过 RequestOptions 类应用到请求上:

import static com.bumptech.glide.request.RequestOptions.option;

Glide.with(context)
  .load(url)
  .apply(option(MyCustomModelLoader.TIMEOUT_MS, 1000L))
  .into(imageView);

你也可以创建一个全新的 RequestOptions 对象:

RequestOptions options = new RequestOptions()
  .set(MyCustomModelLoader.TIMEOUT_MS, 1000L);

Glide.with(context)
  .load(url)
  .apply(options)
  .into(imageView);

或者使用 Generated API

GlideApp.with(context)
  .load(url)
  .set(MyCustomModelLoader.TIMEOUT_MS, 1000L)
  .into(imageView);

三、变换

3.1 关于变换

在 Glide 中,Transformations 可以获取资源并修改它,然后返回被修改后的资源。通常变换操作是用来完成剪裁或对位图应用过滤器,但它也可以用于转换 GIF 动画,甚至自定义的资源类型。

3.2 内置类型

Glide 提供了很多内置的变换,包括:

  • CenterCrop

  • FitCenter

  • CircleCrop

3.3 应用

通过 RequestOptions 类可以应用变换:

3.3.1 默认变换

RequestOptions options = new RequestOptions();
options.centerCrop();

Glide.with(fragment)
    .load(url)
    .apply(options)
    .into(imageView);

大多数内置的变换都有静态的 import ,这是为 API 的流畅性考虑的。例如,你可以通过静态方法应用一个 FitCenter 变换:

import static com.bumptech.glide.request.RequestOptions.fitCenterTransform;

Glide.with(fragment)
    .load(url)
    .apply(fitCenterTransform())
    .into(imageView);

如果你正在使用 Generated API,那么这些变换方法已经被内联了,所以使用起来甚至更为轻松:

GlideApp.with(fragment)
  .load(url)
  .fitCenter()
  .into(imageView);

3.3.2 多重变换

默认情况下,每个 transform() 调用,或任何特定转换方法 (fitCenter()centerCrop()bitmapTransform() 等) 的调用都会替换掉之前的变换。

如果你想在单次加载中应用多个变换,请使用 MultiTransformation 类。

使用 generated API:

Glide.with(fragment)
  .load(url)
  .transform(new MultiTransformation(new FitCenter(), new YourCustomTransformation())
  .into(imageView);

或结合使用快捷方法和 generated API:

GlideApp.with(fragment)
  .load(url)
  .transforms(new FitCenter(), new YourCustomTransformation())
  .into(imageView);

请注意,你向 MultiTransformation 的构造器传入变换参数的顺序,决定了这些变换的应用顺序。

3.4 定制变换

尽管 Glide 提供了各种各样的内置 Transformation 实现,如果你需要额外的功能,你也可以实现你自己的 Transformation

3.4.1 BitmapTransformation

如果你只需要变换 Bitmap,最好是从继承 BitmapTransformation 开始。BitmapTransformation 为我们处理了一些基础的东西,例如,如果你的变换返回了一个新修改的 Bitmap,BitmapTransformation 将负责提取和回收原始的 Bitmap。

一个简单的实现看起来可能像这样:

public class FillSpace extends BitmapTransformation {
    private static final String ID = "com.bumptech.glide.transformations.FillSpace";
    private static final String ID_BYTES = ID.getBytes(STRING_CHARSET_NAME);

    @Override
    public Bitmap transform(BitmapPool pool, Bitmap toTransform, int outWidth, int outHeight) {
        if (toTransform.getWidth() == outWidth && toTransform.getHeight() == outHeight) {
            return toTransform;
        }

        return Bitmap.createScaledBitmap(toTransform, outWidth, outHeight, /*filter=*/ true);
    }

    @Override
    public void equals(Object o) {
      return o instanceof FillSpace;
    }

    @Override
    public int hashCode() {
      return ID.hashCode();
    }

    @Override
    public void updateDiskCacheKey(MessageDigest messageDigest)
        throws UnsupportedEncodingException {
      messageDigest.update(ID_BYTES);
    }
}

尽管你的 Transformation 会比这个示例更复杂,但它应该包含了相同的基本元素和复写方法。

3.4.2 必需的方法

请特别注意,对于任何 Transformation 子类,包括 BitmapTransformation,你都有三个方法必须实现,以使得磁盘和内存缓存正确地工作:

  1. equals()

  2. hashCode()

  3. updateDiskCacheKey()

如果你的 Transformation 没有参数,通常使用一个包含完整包限定名的 static final String 来作为一个 ID,它可以构成 hashCode() 的基础,并可用于更新 updateDiskCacheKey() 传入的 MessageDigest。如果你的 Transformation 需要参数而且它会影响到 Bitmap 被变换的方式,它们也必须被包含到这三个方法中。

例如,Glide 的 RoundedCorners 变换接受一个 int,它决定了圆角的弧度。它的 equals()hashCode()updateDiskCacheKey 实现看起来像这样:

  @Override
  public boolean equals(Object o) {
    if (o instanceof RoundedCorners) {
      RoundedCorners other = (RoundedCorners) o;
      return roundingRadius == other.roundingRadius;
    }
    return false;
  }

  @Override
  public int hashCode() {
    return Util.hashCode(ID.hashCode(),
        Util.hashCode(roundingRadius));
  }

  @Override
  public void updateDiskCacheKey(MessageDigest messageDigest) {
    messageDigest.update(ID_BYTES);

    byte[] radiusData = ByteBuffer.allocate(4).putInt(roundingRadius).array();
    messageDigest.update(radiusData);
  }

原来的 String 仍然保留,但 roundingRadius 被包含到了三个方法中。这里,updateDiskCacheKey() 方法还演示了你可以如何使用 ByteBuffer 来包含基本参数到你的 updateDiskCacheKey() 实现中。

不要忘记 equals() / hashCode()!

值得重申的一点是,为了让内存缓存正常地工作你是否必须实现 equals()hashCode() 方法。很不幸,即使你没有复写这两个方法,BitmapTransformationTransformation 也能通过编译,但这并不意味着它们能正常工作。

3.5 Glide中的特殊行为

3.5.1 重用变换

Transformation 的设计初衷是无状态的。因此,在多个加载中复用 Transformation 应当总是安全的。创建一次 Transformation 并在多个加载中使用它,通常是很好的实践。

3.5.2 ImageView 的自动变换

在 Glide 中,当你为一个 ImageView 开始加载时,Glide 可能会自动应用 FitCenterCenterCrop,这取决于 view 的 ScaleType。如果 scaleTypeCENTER_CROP,Glide 将会自动应用 CenterCrop 变换。如果 scaleTypeFIT_CENTERCENTER_INSIDE,Glide 会自动使用 FitCenter 变换。

当然,你总有权利覆写默认的变换,只需要一个带有 Transformation 集合的 RequestOptions 即可。另外,你也可以通过使用 dontTransform() 确保不会自动应用任何变换。

3.5.3 自定义资源

因为 Glide 4.0 允许你指定你将解码的资源的父类型,你可能无法确切地知道将会应用何种变换。例如,当你使用 asDrawable() (或就是普通的 with(),因为 asDrawable() 是默认情形) 来加载 Drawable 资源时,你可能会得到 BitmapDrawable 子类,也有可能得到 GifDrawable 子类。

为了确保你添加到 RequestOptions 中的任何变换都会被使用,Glide将 Transformation 添加到一个 Map 中保存,其 Key 为你提供变换的资源类型。当资源被成功解码时,Glide 使用这个 Map 来取回对应的 Transformation

Glide 可以将 Bitmap Transformation 应用到 BitmapDrawableGifDrawable,以及 Bitmap 资源上,因此通常你只需要编写和应用 Bitmap Transformation。然而,如果你添加了额外的资源类型,你可能需要考虑派生 RequestOptions 类,并且,在内置的这些 Bitmap Transformations 之外,你还需要为你的自定义资源类型提供一个 Transformation

推荐阅读更多精彩内容