使用Glide v4加載SVG資源

Android使用矢量圖(SVG, VectorDrawable)實(shí)踐篇

Android SVG矢量動(dòng)畫機(jī)制

先放上兩篇Android SVG使用相關(guān)的文章作為備忘祥得。

問題背景

雖然老早就知道Android支持SVG資源识腿,但是因?yàn)槿狈κ褂脠?chǎng)景较坛,所以這方面一直沒有實(shí)踐過隙券。因而當(dāng)我看到甲方的接口返回了一串這個(gè)東西時(shí)嗅绸,一時(shí)間我是懵逼的:

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

服務(wù)器返回的svg對(duì)象是一串xml格式的文本麻裁,這是個(gè)什么鬼呢渐夸?

下面是一個(gè)完整的SVG文件內(nèi)容示例:

<?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>

通過對(duì)比可以看出來嗤锉,服務(wù)器返回的xml,其實(shí)就是SVG文件中的<path>...</path>部分墓塌。我試了一下瘟忱,雖然返回的信息不完整,但這段xml套入一個(gè)完整的SVG標(biāo)簽后苫幢,是可以正常加載出來圖片的访诱。

那要如何在手機(jī)上加載這串xml呢?

尋找解決方案

查了些Android 加載SVG的相關(guān)資料后我發(fā)現(xiàn)韩肝,這類文章絕大多數(shù)都在描述“加載本地SVG文件”這一使用場(chǎng)景(當(dāng)然這也是最常見的場(chǎng)景)触菜,對(duì)我的窘境并沒有什么幫助。不過收獲還是有的哀峻,我發(fā)現(xiàn)StackOverFlow上早在15年就有這么一個(gè)問題:

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

里面贊數(shù)最高的回答涡相,描述了如何用我們的老朋友Glide加載一個(gè)SVG文件的Url哲泊。答者還很貼心的更新了新版本Glide (v4)的相關(guān)內(nèi)容:

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

點(diǎn)進(jìn)去一看,非常簡(jiǎn)潔明了的范例漾峡,只需要在工程里集成AndroidSVG庫(kù)攻旦,然后把范例搬進(jìn)工程就能用了:

1.集成AndroidSVG

這個(gè)庫(kù)是加載SVG的核心庫(kù)喻旷,如果想拋開glide生逸,單純加載SVG的話,有這個(gè)庫(kù)就夠了且预。

2.拷貝源碼

把這四個(gè)類拷貝到工程里

下面貼上源碼:
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);
    } 
  } 
} 

這個(gè)類的作用是把glide通過url加載的資源轉(zhuǎn)成Svg類型槽袄,轉(zhuǎn)換的過程依賴AndroidSvg庫(kù)提供的方法,很簡(jiǎn)單遍尺。轉(zhuǎn)換出的Svg對(duì)象,可以設(shè)置渲染的像素密度涮拗、文件寬高乾戏、viewbox寬高等參數(shù)。在這里我們需要將文件寬高設(shè)置成我們加載圖片的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);
  } 
} 

很簡(jiǎn)單,調(diào)用Svg類自帶的方法就漾,完成Svg -> PictureDrawable的轉(zhuǎn)換

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; 
  } 
} 

注意注釋內(nèi)容: 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進(jìn)行注冊(cè)

依托Glide的Generated API特性呐能,在工程的AppGlideModule類中注冊(cè)上面的組件:

@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;
  }
}

注冊(cè)SvgDrawableTranscoder,告訴Glide由SVG轉(zhuǎn)成PictureDrawable依靠SvgDrawableTranscoder類抑堡;
注冊(cè)SvgDecoder摆出,告訴Glide由InputStream轉(zhuǎn)成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,實(shí)現(xiàn)從xml載入SVG

雖然現(xiàn)在可以用Glide直接加載SVG文件的Url了有缆,但我這邊需要的是從xml直接加載象踊。好在Glide已經(jīng)足夠強(qiáng)大,可以讓我們充分自定義圖片加載的過程:

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加載的對(duì)象妒貌,而InputStream是輸入出的對(duì)象通危。

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負(fù)責(zé)切實(shí)的獲取到圖片的數(shù)據(jù),通常這里要進(jìn)行本地的文件讀取或者下載圖片的操作灌曙,但是這里我們只要把SVG的xml轉(zhuǎn)換成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注冊(cè)組件注冊(cè)的是ModelLoaderFactory,因此還需要一包裝一下...

4.注冊(cè)ModelLoaderFactory

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

到這一步在刺,Glide就可以從我自定義的對(duì)象MTSVGItem加載出來SVG圖片了逆害。

總...結(jié)

Glide我也用了很久了头镊,這次是第一次做自定義ModelLoader的嘗試。好在Glide v4提供了清晰的文檔魄幕,整個(gè)過程非常的平滑愉快相艇。
雖說作為一個(gè)沒有理想的搬磚工人,沒什么深入研究技術(shù)的動(dòng)力纯陨,但是加深對(duì)Glide這種常用工具的了解坛芽,毫無疑問可以增加搬磚的效率。不錯(cuò)不錯(cuò)翼抠,善莫大焉~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末咙轩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子阴颖,更是在濱河造成了極大的恐慌活喊,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件量愧,死亡現(xiàn)場(chǎng)離奇詭異钾菊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)偎肃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門煞烫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人软棺,你說我怎么就攤上這事红竭。” “怎么了喘落?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵茵宪,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我瘦棋,道長(zhǎng)稀火,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任赌朋,我火速辦了婚禮凰狞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘沛慢。我一直安慰自己赡若,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布团甲。 她就那樣靜靜地躺著逾冬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上身腻,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天产还,我揣著相機(jī)與錄音,去河邊找鬼嘀趟。 笑死脐区,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的她按。 我是一名探鬼主播牛隅,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼尤溜!你這毒婦竟也來了倔叼?” 一聲冷哼從身側(cè)響起汗唱,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤宫莱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后哩罪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體授霸,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年际插,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碘耳。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡框弛,死狀恐怖辛辨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瑟枫,我是刑警寧澤斗搞,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站慷妙,受9級(jí)特大地震影響僻焚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜膝擂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一虑啤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧架馋,春花似錦狞山、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至办绝,卻和暖如春伊约,著一層夾襖步出監(jiān)牢的瞬間姚淆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國(guó)打工屡律, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腌逢,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓超埋,卻偏偏與公主長(zhǎng)得像搏讶,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子霍殴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容