Android使用矢量圖(SVG, VectorDrawable)實(shí)踐篇
先放上兩篇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.拷貝源碼
下面貼上源碼:
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ò)翼抠,善莫大焉~