使用Glide v4加载SVG资源

Android使用矢量图(SVG, VectorDrawable)实践篇

Android SVG矢量动画机制

先放上两篇Android SVG使用相关的文章作为备忘。

问题背景

虽然老早就知道Android支持SVG资源,但是因为缺乏使用场景,所以这方面一直没有实践过。因而当我看到甲方的接口返回了一串这个东西时,一时间我是懵逼的:

"svg": "
"

服务器返回的svg对象是一串xml格式的文本,这是个什么鬼呢?

下面是一个完整的SVG文件内容示例:









通过对比可以看出来,服务器返回的xml,其实就是SVG文件中的...部分。我试了一下,虽然返回的信息不完整,但这段xml套入一个完整的SVG标签后,是可以正常加载出来图片的。

那要如何在手机上加载这串xml呢?

寻找解决方案

查了些Android 加载SVG的相关资料后我发现,这类文章绝大多数都在描述“加载本地SVG文件”这一使用场景(当然这也是最常见的场景),对我的窘境并没有什么帮助。不过收获还是有的,我发现StackOverFlow上早在15年就有这么一个问题:

android:load svg file from web and show it on image view

里面赞数最高的回答,描述了如何用我们的老朋友Glide加载一个SVG文件的Url。答者还很贴心的更新了新版本Glide (v4)的相关内容:

Update: For newer version please checkout the Glide Samples (https://github.com/bumptech/glide/tree/master/samples/svg)

点进去一看,非常简洁明了的范例,只需要在工程里集成AndroidSVG库,然后把范例搬进工程就能用了:

1.集成AndroidSVG

这个库是加载SVG的核心库,如果想抛开glide,单纯加载SVG的话,有这个库就够了。

2.拷贝源码

使用Glide v4加载SVG资源_第1张图片
把这四个类拷贝到工程里

下面贴上源码:
SvgDecoder.java

/** 
 * Decodes an SVG internal representation from an {@link InputStream}. 
 */ 
public class SvgDecoder implements ResourceDecoder {
 
  @Override 
  public boolean handles(@NonNull InputStream source, @NonNull Options options) {
    // TODO: Can we tell? 
    return true; 
  } 
 
  public Resource decode(@NonNull InputStream source, int width, int height,
      @NonNull Options options)
      throws IOException {
    try {
      SVG svg = SVG.getFromInputStream(source);
      svg.setDocumentWidth(width);
      svg.setDocumentHeight(height);
      return new SimpleResource<>(svg);
    } catch (SVGParseException ex) {
      throw new IOException("Cannot load SVG from stream", ex);
    } 
  } 
} 

这个类的作用是把glide通过url加载的资源转成Svg类型,转换的过程依赖AndroidSvg库提供的方法,很简单。转换出的Svg对象,可以设置渲染的像素密度、文件宽高、viewbox宽高等参数。在这里我们需要将文件宽高设置成我们加载图片的imageView的宽高,以保证图片的正常显示。

SvgDrawableTranscoder.java

/** 
 * Convert the {@link SVG}'s internal representation to an Android-compatible one 
 * ({@link Picture}). 
 */ 
public class SvgDrawableTranscoder implements ResourceTranscoder {
  @Nullable 
  @Override 
  public Resource transcode(@NonNull Resource toTranscode,
      @NonNull Options options) {
    SVG svg = toTranscode.get();
    Picture picture = svg.renderToPicture();
    PictureDrawable drawable = new PictureDrawable(picture);
    return new SimpleResource<>(drawable);
  } 
} 

很简单,调用Svg类自带的方法,完成Svg -> PictureDrawable的转换

SvgSoftwareLayerSetter.java

/** 
 * Listener which updates the {@link ImageView} to be software rendered, because 
 * {@link com.caverock.androidsvg.SVG SVG}/{@link android.graphics.Picture Picture} can't render on 
 * a hardware backed {@link android.graphics.Canvas Canvas}. 
 */ 
public class SvgSoftwareLayerSetter implements RequestListener {
 
  @Override 
  public boolean onLoadFailed(GlideException e, Object model, Target target,
      boolean isFirstResource) {
    ImageView view = ((ImageViewTarget) target).getView();
    view.setLayerType(ImageView.LAYER_TYPE_NONE, null);
    return false; 
  } 
 
  @Override 
  public boolean onResourceReady(PictureDrawable resource, Object model,
      Target target, DataSource dataSource, boolean isFirstResource) {
    ImageView view = ((ImageViewTarget) target).getView();
    view.setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null);
    return false; 
  } 
} 

注意注释内容: Listener which updates the {@link ImageView} to be software rendered, because {@link com.caverock.androidsvg.SVG SVG}/{@link android.graphics.Picture Picture} can't render on a hardware backed {@link android.graphics.Canvas Canvas}.

3.SvgModule.java在AppGlideModule进行注册

依托Glide的Generated API特性,在工程的AppGlideModule类中注册上面的组件:

@GlideModule
public class SvgModule extends AppGlideModule {
  @Override
  public void registerComponents(@NonNull Context context, @NonNull Glide glide,
      @NonNull Registry registry) {
    registry.register(SVG.class, PictureDrawable.class, new SvgDrawableTranscoder())
        .append(InputStream.class, SVG.class, new SvgDecoder());
  }

  // Disable manifest parsing to avoid adding similar modules twice.
  @Override
  public boolean isManifestParsingEnabled() {
    return false;
  }
}

注册SvgDrawableTranscoder,告诉Glide由SVG转成PictureDrawable依靠SvgDrawableTranscoder类;
注册SvgDecoder,告诉Glide由InputStream转成SVG依靠SvgDecoder类。

*参考Glide官方文档Generated API配置AppGlideModule

4.加载图片

RequestBuilder requestBuilder = GlideApp.with(context)
                .as(PictureDrawable.class)
                .transition(withCrossFade())
                .listener(new SvgSoftwareLayerSetter());
requestBuilder.load(svg).into(view);

到这一步,就可以通过Url直接加载SVG图片了。

自定义ModelLoader,实现从xml载入SVG

虽然现在可以用Glide直接加载SVG文件的Url了,但我这边需要的是从xml直接加载。好在Glide已经足够强大,可以让我们充分自定义图片加载的过程:

1.自定义ModelLoader

public class MTSvgModelLoader implements ModelLoader {
    @Nullable
    @Override
    public LoadData buildLoadData(@NonNull MTSVGItem mtigqsvgItem, int width, int height, @NonNull Options options) {
        Key diskCacheKey = new ObjectKey(mtigqsvgItem.getFullSVG());
        return new LoadData<>(diskCacheKey, new MTSvgFetcher(mtigqsvgItem.getFullSVG()));
    }

    @Override
    public boolean handles(@NonNull MTSVGItem mtigqsvgItem) {
        return true;

    }
}

MTSVGItem是我希望Glide加载的对象,而InputStream是输入出的对象。

2.自定义DataFetcher

public class MTSvgFetcher implements DataFetcher {

    private final String model;

    public MTSvgFetcher(String model) {
        this.model = model;
    }

    @Override
    public void loadData(@NonNull Priority priority, @NonNull DataCallback callback) {
        InputStream stream = new ByteArrayInputStream(model.getBytes(StandardCharsets.UTF_8));
        callback.onDataReady(stream);
    }

    @Override
    public void cleanup() {

    }

    @Override
    public void cancel() {

    }

    @NonNull
    @Override
    public Class getDataClass() {
        return InputStream.class;
    }

    @NonNull
    @Override
    public DataSource getDataSource() {
        return DataSource.LOCAL;
    }
}

DataFatcher负责切实的获取到图片的数据,通常这里要进行本地的文件读取或者下载图片的操作,但是这里我们只要把SVG的xml转换成InputStream返回就行了。

3.自定义ModelLoaderFactory

public class MTSvgModelLoaderFactory implements ModelLoaderFactory {
    @NonNull
    @Override
    public ModelLoader build(@NonNull MultiModelLoaderFactory multiFactory) {
        return new MTSvgModelLoader();
    }

    @Override
    public void teardown() {

    }
}

glide注册组件注册的是ModelLoaderFactory,因此还需要一包装一下...

4.注册ModelLoaderFactory

registry.register(SVG.class, PictureDrawable.class, new SvgDrawableTranscoder())
                .append(InputStream.class, SVG.class, new SvgDecoder())
                .append(MTSVGItem.class, InputStream.class, new MTSvgModelLoaderFactory());

到这一步,Glide就可以从我自定义的对象MTSVGItem加载出来SVG图片了。

总...结

Glide我也用了很久了,这次是第一次做自定义ModelLoader的尝试。好在Glide v4提供了清晰的文档,整个过程非常的平滑愉快。
虽说作为一个没有理想的搬砖工人,没什么深入研究技术的动力,但是加深对Glide这种常用工具的了解,毫无疑问可以增加搬砖的效率。不错不错,善莫大焉~

你可能感兴趣的:(使用Glide v4加载SVG资源)