使用Glide v4加载SVG资源

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

Android SVG矢量动画机制

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

问题背景

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

"svg": "<path d=\"M364.4,55.1...364.4,55.1z\"></path>
<path d=\"M218.6,130.1h-47.4.1-...1z\"></path>"

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

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

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1527589197284" class="icon" style="" 
viewBox="0 0 1024 1024" version="1.1" 
xmlns="http://www.w3.org/2000/svg" p-id="2045" 
xmlns:xlink="http://www.w3.org/1999/xlink"
width="200" height="200">
<defs><style type="text/css"></style></defs>
<path d="M235.097303 ...133018Z" p-id="2046"></path>
<path d="M399.609756 ... 902.017094Z" p-id="2047"></path>
<path d="M924.097562 ... 902.017094Z" p-id="2048"></path>
</svg>

通过对比可以看出来,服务器返回的xml,其实就是SVG文件中的<path>...</path>部分。我试了一下,虽然返回的信息不完整,但这段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.拷贝源码

把这四个类拷贝到工程里

下面贴上源码:
SvgDecoder.java

/** 
 * Decodes an SVG internal representation from an {@link InputStream}. 
 */ 
public class SvgDecoder implements ResourceDecoder<InputStream, SVG> {
 
  @Override 
  public boolean handles(@NonNull InputStream source, @NonNull Options options) {
    // TODO: Can we tell? 
    return true; 
  } 
 
  public Resource<SVG> 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<SVG, PictureDrawable> {
  @Nullable 
  @Override 
  public Resource<PictureDrawable> transcode(@NonNull Resource<SVG> 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<PictureDrawable> {
 
  @Override 
  public boolean onLoadFailed(GlideException e, Object model, Target<PictureDrawable> 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<PictureDrawable> 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<PictureDrawable> 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<MTSVGItem, InputStream> {
    @Nullable
    @Override
    public LoadData<InputStream> 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<InputStream> {

    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<MTSVGItem, InputStream> {
    @NonNull
    @Override
    public ModelLoader<MTSVGItem, InputStream> 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这种常用工具的了解,毫无疑问可以增加搬砖的效率。不错不错,善莫大焉~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容