SVG圖片在移動端的應用解決方案

AndroidUpUp發表於2019-04-01

近幾年來SVG使用得越來越多,就連Android的官方庫也加入VectorDrawable的支援。這個類就是用來支援向量圖的。SVG圖片在web端使用非常廣泛,我第一次接觸這個也是在做react-native的專案中使用的。當時我們要做一些動畫,需要從一個形狀變換成另一個形狀,這種一般都是用向量圖來做的。當時設計師就給了我一些向量圖,於是我就開始研究這個東西。

在react-native中,有專門一個庫叫react-native-svg來處理這個。不過當時要做兩個SVG形狀的動畫變化,並不是任何一個形狀都可以的,需要遵循一定的標準。設計師給我的兩個SVG檔案並不能轉換。後來是我自己根據檔案裡的一些關鍵引數自己在程式碼裡直接畫出來後,再做轉換動畫。

最近我在做的native專案中,也遇到了要用SVG的圖片。我們的專案裡要從伺服器下載SVG圖片來展示。我們要實現的這些檔案是需要伺服器動態配置的,也就是說我們不能預先打包進我們的APP裡。所以我們這裡的要提供一個解決方案,跟圖片JPG圖片一樣顯示,快取。

這個需求跟之前我遇到的那個需求是很不一樣的,之前的是設計師已經定義好圖片,我們工程師直接拿到檔案在程式裡展示,不需要考慮下載和快取之類的。這種需求其實很簡單,我們實際上大部分的需求就是這種需求,網上有很多庫可以完成這種需求。把SVG圖片跟JPG等普通圖片一樣使用,網上的方案還真不多,特別是iOS方面。。。。

要像普通圖片一樣使用,就要考慮下載,本地快取,記憶體快取。像這種需求,我們移動端都會使用專門的圖片框架,像安卓端的glide,UIL等,iOS端的SDWebImage等。但是這種庫它是預設都不會考慮SVG圖片。但是我們最好還是像使用這種框架來處理SVG圖片。最好的方式就是把SVG的支援整合到這些庫中。好在這些庫的優點就是容易擴充套件。

安卓端的解決方案

由於我們的專案是採用glide框架來處理圖片,所以這裡就只講在這個框架整合SVG圖片的展示。

實際上glide真的是一個很強大的庫,怪不得那麼多人用它(早幾年我們都是用UIL),它在它的sample例子裡就提供了SVG的展示支援svg。在這個例子裡,採用的SVG解碼方式是使用外部的解碼庫。它採用的解碼庫是androidsvg。這個庫是web端移植過來的,所以它有很好的相容性,是很不錯的庫,雖然它的star不是很多。Android就是好,有強大的Java社群,受益於這些社群,很多庫都不錯。而這方面iOS就那麼好了,這個等一下再說。

按照它提供的sample來整合SVG的支援,不是很難。但是我遇到了其他問題。因為我們專案裡的glide使用的是3.7版本,sample是基於4.8版本的。這兩個版本在API上有很大區別,變化很大。所以我必須要先升級到4.8版本。等我升級完後,接入SVG的支援,然而SVG圖片死活顯示不出來。最終發現是我的AppModule無法生成。一直在文件,查資料,還是找不到問題,我是完全按照官方文件升級和整合的。最終我發現可能是跟AndroidX相關(還不知道AndroidX是什麼的自己查)。我們專案升級到了AndroidX,它會影響一些annotation生成方式。我們的專案採用外掛式框架。我們很多通用庫是放在一個commomlibrary的Module中,glide也是。APP module就只是一個殼。但是一些annotation的宣告一定要放在APP module中才行。所以我們把

annotationProcessor 'androidx.annotation:annotation:1.0.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0' 

放在了APP module中,最終那些自動生成的程式碼才會真正的生成。這樣就可以很愉快的用glide來顯示SVG圖片了。

public class GlideSvgUtil {

    //顯示網路中的svg檔案
    public static void showSvg(ImageView imageView, String url) {
        Glide.with(imageView.getContext()).as(PictureDrawable.class).listener(new SvgSoftwareLayerSetter()).load(url).into(imageView);
    }

    //把svg放入到raw中,通過rawid來顯示
    public static void showSvgRes(ImageView imageView, int rawId) {
        Uri uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + imageView.getContext().getPackageName() + "/"
                + rawId);
        Glide.with(imageView.getContext()).as(PictureDrawable.class).listener(new SvgSoftwareLayerSetter()).load(uri).into(imageView);
    }

    //把svg的XML載入到字串中來顯示
    public static void showSvgContent(ImageView imageView, String svgContent) {
        Glide.with(imageView.getContext()).as(PictureDrawable.class).listener(new SvgSoftwareLayerSetter()).load(svgContent.getBytes()).into(imageView);
    }
}

iOS端的解決方案

iOS的方案還不是很好解決,我沒有發現有哪一個圖片框架是整合了SVG或者提供了整合的例子的。而且我們的同事還發現了一個問題。我們伺服器提供的SVG圖片在很多庫解碼出來後沒有了顏色,是黑白的。很詭異的問題,然而安卓端沒有這問題。我們找很多庫,像SVGKit,SwiftSVG,PocketSVG,Macaw,這些庫都是超過1000star的,都無法正常顯示。我基本確定是我們SVG檔案的相容性問題,我問我們的設計師他是怎麼生成SVG檔案的。他說是用sketch匯出來的。這些檔案在web和安卓的庫,還有甚至xcode裡都是能正常顯示的。這裡我提供一個不能正常顯示的圖片。

<svg id="圖層_1" data-name="圖層 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80">
<defs><style>.cls-1{fill:#ff9595;}.cls-2{fill:#ffcc80;}</style></defs>
<title>彩色圖示</title>
<path class="cls-1" d="M43,77A27.14,27.14,0,0,0,58.64,27.67a24.55,24.55,0,0,1-6.4,8A24.44,24.44,0,0,0,34.55,3a24.48,24.48,0,0,1,.87,6.49c0,21.19-25.57,21.72-25.57,42.37C9.85,64.9,20.47,77,43,77Z"/><path class="cls-2" d="M51,66c0-9.28-11.79-11.85-11.79-21.74A12.3,12.3,0,0,1,40,39.95,20.79,20.79,0,0,0,34.77,76.4,54.34,54.34,0,0,0,43,77c.86,0,1.7,0,2.53-.12A13.47,13.47,0,0,0,51,66Z"/>
</svg>

因為是顏色不能正常顯示,我猜肯定是defs標籤裡的內容不能正常解析。我稍微改了下檔案,就能正常顯示了。我還發現對SVG顯示支援的比較好的是Macaw,其他的貌似都有點小問題。所以我把這個問題在Macaw上提交了。我不知道他們會什麼時候修改,所以我就自己去研究他們的原始碼,準備自己來解決了。很快我就發現他們在預解析階段,解析defs標籤的時候,就沒有考慮style這種子標籤。所以我就加上去了,就兩三行程式碼(他們的程式碼架構挺好),然後就正常顯示了。後來我又到Macaw網站上看,他們已經回覆我了,並且已經支援!前後也就一個多小時!他說defs標籤裡一般不會放style標籤,不過很多其他庫支援,所以他們也就支援了。我去看他改的程式碼,幾乎跟我改一樣。所以我就放棄自己的改動,採用cocoapods直接拉取他們的master上的最新程式碼。

解碼的問題解決了,但是怎麼整合到圖片框架裡呢?我們專案是採用swift開發的,我們採用的圖片處理框架是Kingfisher。我去Kingfisher的網站上看,喵神很厲害,已經有很好的文件教我們怎麼擴充套件圖片解碼器。我一看,另一個問題來了,解碼器是在工作執行緒中進行的,並且要返回一個UIImage。Macaw這個庫並沒有提供一個可以在子執行緒中將SVG檔案轉為UIImage的方法。他們的方法是將SVG顯示在一個UIView裡然後截圖。。。也有人將這個問題提了。我看他們原始碼發現有支援的,但是沒有放出來,無法使用。

我最終也只能採用曲線救國的方法了。還是用他們的方法,我在解碼執行緒裡同步切到主執行緒中生成UIImage,然後再在子執行緒中返回這個UIImage。好訊息是,最近兩天,他們終於支援將一個檔案直接轉換為UIImage了具體方案看這裡,不過我還沒測試,他們也還沒發新版。

SVG解碼器

import Foundation
import UIKit
import Kingfisher
import Macaw

struct SVGProcessor:  ImageProcessor {
    var size = CGSize(width: 32, height: 32)
    init(_ size: CGSize) {
        if size.width == 0 || size.height == 0 {
            print("不支援size為0的情況m,將採用預設值32")
        } else {
            self.size = size
        }
    }

    let identifier: String = "com.wegene.future"

    func process(item: ImageProcessItem, options: KingfisherOptionsInfo) -> UIImage? {
        switch item {
        case .image(let image):
            print("already an image")
            return image
        case .data(let data):
            let svgContent = String.init(data: data, encoding: String.Encoding.utf8)
            var img: UIImage?
            DispatchQueue.main.sync {//現在Macaw庫暫時只支援這種方式生成UIImage,下一版他們支援後臺執行緒生成UIImage的方式,以後再做修改
                let rootNode = try! SVGParser.parse(text: svgContent!)
                let macawView = MacawView(node: rootNode, frame:CGRect(origin: CGPoint.zero, size: size))
                UIGraphicsBeginImageContextWithOptions(size, true, UIScreen.main.scale)
                macawView.layer.render(in: UIGraphicsGetCurrentContext()!)
                img =  UIGraphicsGetImageFromCurrentImageContext();
                UIGraphicsEndImageContext();
            }
            return img
        }
    }
}

UIImageView的擴充套件

extension UIImageView {
    func setSvgImage(_ url: String) {
        let processor = SVGProcessor(self.frame.size)
        let _url = URL(string: url)
        self.kf.setImage(with: _url, options:[.processor(processor)])
    }
}


小編給大家準備了高階安卓進階學習資料視訊
有需要的加群領取
群號:4112676

相關文章