解釋?zhuān)?/p>
創(chuàng)新互聯(lián)建站專(zhuān)注于江夏企業(yè)網(wǎng)站建設(shè),響應(yīng)式網(wǎng)站開(kāi)發(fā),成都商城網(wǎng)站開(kāi)發(fā)。江夏網(wǎng)站建設(shè)公司,為江夏等地區(qū)提供建站服務(wù)。全流程按需定制開(kāi)發(fā),專(zhuān)業(yè)設(shè)計(jì),全程項(xiàng)目跟蹤,創(chuàng)新互聯(lián)建站專(zhuān)業(yè)和態(tài)度為您提供的服務(wù)
1、首先創(chuàng)建一個(gè)Bitmap圖片,并指定大??;
2、在該圖片上創(chuàng)建一個(gè)新的畫(huà)布Canvas,然后在畫(huà)布上繪制,并保存即可;
3、需要保存的目錄File,注意如果寫(xiě)的目錄如“/sdcard/akai/”如果不存在的話,要先創(chuàng)建(file.mkdirs()),否則FileOutputStream會(huì)報(bào)錯(cuò)No found;
4、需要添加權(quán)限:uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/
什么是畫(huà)布呢 其實(shí)通過(guò)字面意思的理解就是用來(lái)繪畫(huà)的地方,那么android里的畫(huà)布是神馬樣子的呢?
在自定義畫(huà)布中常用到下面3個(gè)類(lèi)
Canvas
這些繪圖方法中的每一個(gè)都需要指定一個(gè)Paint對(duì)象來(lái)渲染它
Paint
Paint也稱(chēng)為"刷子",Paint可以指定如何將基本圖形繪制到位圖上。
Paint類(lèi)相當(dāng)于一個(gè)筆刷和調(diào)色板。它可以選擇如何使用上面描述的draw方法來(lái)渲染繪 制在畫(huà)布上的基本圖形。通過(guò)修改Paint對(duì)象,可以在繪圖的時(shí)候控制顏色、樣式、字體和特殊效果。最簡(jiǎn)單地,setColor可以讓你選擇一個(gè)Paint的顏色,而Paint對(duì)象的樣式(使用setStyle控制)則可以決定是繪制繪圖對(duì)象的輪廓(STROKE),還是只填充每一部 分(FILL),或者是兩者都做(STROKE_AND_FILL)除了這些簡(jiǎn)單的控制之外,Paint類(lèi)還支持透明度,另外,它也可以通過(guò)使用各種各樣的陰影、過(guò)濾器和效果進(jìn)行修改,從而提供由更豐富的、復(fù)雜的畫(huà)筆和顏料組成的調(diào)色板。
從繼承View類(lèi)(或其子類(lèi))開(kāi)始,并定義onDraw()回調(diào)方法。系統(tǒng)會(huì)調(diào)用該方法來(lái)完 成View對(duì)象自己的繪制請(qǐng)求。這也是通過(guò)Canvas對(duì)象來(lái)執(zhí)行所有的圖形繪制調(diào)用的地方,這個(gè)Canvas對(duì)象是由onDraw()回調(diào)方法傳入的。
Android框架只在必要的時(shí)候才會(huì)調(diào)用onDraw()方法,每次請(qǐng)求應(yīng)用程序準(zhǔn)備完成圖形 繪制任務(wù)時(shí),必須通過(guò)調(diào)用invalidate()方法讓該View對(duì)象失效。這表明可以在該View 對(duì)象上進(jìn)行圖形繪制處理了,然后Android系統(tǒng)會(huì)調(diào)用該View對(duì)象的onDraw()方(盡 管不保證該回調(diào)方法會(huì)立即被調(diào)用)。
在定制的View組件的onDraw()方法內(nèi)部,使用給定的Canvas對(duì)象來(lái)完成所有的圖形繪制處理(如Canvas.draw…()方法或把該Canvas對(duì)象作為參數(shù)傳遞給其他類(lèi)的draw() 方法)。一旦onDraw()方法被執(zhí)行完成,Android框架就會(huì)使用這個(gè)Canvas對(duì)象來(lái)繪制一個(gè)有系統(tǒng)處理的Bitmap對(duì)象。
下面是Paint一些常用方法:
Bitmap
Bitmap繪圖的表面也稱(chēng)位圖(這里詳細(xì)說(shuō)哈位圖的功能)。
從資源中獲取位圖:
通過(guò)Resource的函數(shù):InputStream openRawResource(int id)獲取得到資源文件的數(shù)據(jù)流后,可以通過(guò)2種方式獲得bitmap
使用BitmapDrawable :
使用BitmapDrawable(InputStream is)構(gòu)造一個(gè)BitmapDrawable;
使用BitmapDrawable類(lèi)的getBitmap()獲取得到位圖;
使用BitmapFactory使用BitmapFactory類(lèi)decodeStream(InputStream is)解碼位 圖資源,獲取位圖BitmapFactory的所有函數(shù)都是static,這個(gè)輔助類(lèi)可以通過(guò)資 源ID、路徑、文件、數(shù)據(jù)流等方式來(lái)獲取位圖。
獲取位圖的信息
一般獲取位圖信息包括:位圖大小、透明度、顏色格式等等,這些信息呢可以通過(guò) 三-一方法獲取得到Bitmap就迎刃而解了,Android SDK中對(duì)Bitmap有詳細(xì)說(shuō)明,大家可以去詳細(xì)了解哈。
顯示位圖
顯示位圖需要使用核心類(lèi)Canvas,可以直接通過(guò)Canvas類(lèi)的drawBirmap()顯示位圖,或者借助于BitmapDrawable來(lái)將Bitmap繪制到Canvas,下面的實(shí)例中會(huì)詳細(xì)列舉到
位圖的縮放
位圖的縮放,在Android SDK中提供了2種方法:
1:將一個(gè)位圖按照需求重畫(huà)一遍,畫(huà)后的位圖就是我們需要的了,與位圖的顯示幾乎 一樣:
drawBitmap(Bitmap bitmap, Rect src, Rectdst, Paint paint)
2:在原有位圖的基礎(chǔ)上,縮放原位圖,創(chuàng)建一個(gè)新的位圖:
createBitmap(Bitmap source, int x, int y,int width, int height, Matrix m, boolean filter)
位圖旋轉(zhuǎn)
位圖的旋轉(zhuǎn),離不開(kāi)Matrix。Android SDK提供了Matrix類(lèi),可以通過(guò)各種接口來(lái)設(shè)置 矩陣
android 處理圖片工具
截取視頻幀并轉(zhuǎn)化為Bitmap
您好,
1、用美圖看看打開(kāi)照片的原文件,右擊百鼠標(biāo)出現(xiàn)菜單,點(diǎn)擊最下方的“圖片信息”,就可以看到此圖片是用哪個(gè)品牌的度設(shè)備(相機(jī)與手機(jī))拍攝的,還有具體型號(hào),拍攝日期,焦距感。
2、在電腦上用專(zhuān)業(yè)的圖像瀏覽軟件打開(kāi)照片,看屬性,里面的拍攝信息很詳細(xì),包括相機(jī)的型號(hào),拍攝時(shí)間等等,一目了然問(wèn)。這樣的軟件很多,例如ACDSee等。
3、現(xiàn)在數(shù)碼技術(shù)是光電轉(zhuǎn)化,核心是一個(gè)感光元件答,手機(jī)上的可能都沒(méi)小拇指甲蓋大,單反的大拇指頭那么大把,比如同樣是一千兩百萬(wàn)像素,這么多像素點(diǎn)分在兩顆不同的原件上。
4、單專(zhuān)反上的就大,就可以更好地抑制強(qiáng)光的紫邊,高感光度的噪點(diǎn),同時(shí)色彩還原也好。當(dāng)然了,單反畫(huà)質(zhì)好,鏡頭是更主要的因素。
我們每天花很多時(shí)間盯著手機(jī)屏幕,不知道你有沒(méi)有好奇過(guò):
這時(shí)候來(lái)了一位Android程序員(當(dāng)然也可以是iOS或者是前端程序員)說(shuō): 這里顯示的其實(shí)是一個(gè)View樹(shù),我們看到的都是大大小小的View。
。。。聽(tīng)起來(lái)很有道理,我們也經(jīng)常指著屏幕說(shuō)這個(gè)View怎么怎么樣,可問(wèn)題又來(lái)了:
程序員老兄又來(lái)了: 屏幕當(dāng)然不能識(shí)別View,它作為一個(gè)硬件,只能根據(jù)收到的數(shù)據(jù)改變每個(gè)像素單元的數(shù)據(jù),這樣整體來(lái)看,用戶就發(fā)現(xiàn)屏幕上的內(nèi)容變化了。至于View的內(nèi)容是如何一步一步轉(zhuǎn)化成屏幕可是識(shí)別的數(shù)據(jù)的,簡(jiǎn)單講可以分成三步:
。。。聽(tīng)起來(lái)很有道理,可問(wèn)題又來(lái)了:
那可就說(shuō)來(lái)話長(zhǎng)了。。。
對(duì)于 measure layout 和 draw ,Android工程師(大都)非常熟悉,我們常常在執(zhí)行了 onDraw() 方法后,一個(gè)讓人自豪的自定義View就顯示出來(lái)了。在實(shí)際的Android繪制流程中,第一步就是通過(guò) measure layout 和 draw 這些步驟準(zhǔn)備了下面的材料:
在Android的繪制中,我們使用Canvas API進(jìn)行來(lái)告訴表示畫(huà)的內(nèi)容,如 drawCircle() drawColor() drawText() drawBitmap() 等,也是這些內(nèi)容最終呈現(xiàn)在屏幕上。
在當(dāng)前應(yīng)用中,View樹(shù)中所有元素的材料最終會(huì)封裝到 DisplayList 對(duì)象中(后期版本有用 RenderNode 對(duì) DisplayList 又做了一層封裝,實(shí)現(xiàn)了更好的性能),然后發(fā)送出去,這樣第一階段就完成了。
當(dāng)然就有一個(gè)重要的問(wèn)題:
會(huì)將Bitmap復(fù)制到下一個(gè)階段(準(zhǔn)確地講就是復(fù)制到GPU的內(nèi)存中)。
現(xiàn)在大多數(shù)設(shè)備使用了GPU硬件加速,而GPU在渲染來(lái)自Bitmap的數(shù)據(jù)時(shí)只能讀取GPU內(nèi)存中的數(shù)據(jù), 所以需要賦值Bitmap到GPU內(nèi)存,這個(gè)階段對(duì)應(yīng)的名稱(chēng)叫 Syncupload 。另外,硬件加速并不支持所有Canvas API,如果自定義View使用了不支持硬件加速的Canvas API(參考 Android硬件加速文檔 ),為了避免出錯(cuò)就需要對(duì)View進(jìn)行軟件繪制,其處理方式就是生成一個(gè)Bitmap,然后復(fù)制到GPU進(jìn)行處理。
這時(shí)可能會(huì)有問(wèn)題:如果Bitmap很多或者單個(gè)Bitmap尺寸很大,這個(gè)過(guò)程可能會(huì)時(shí)間比較久,那有什么辦法嗎?
當(dāng)然有(做作。。。)
關(guān)于Bitmap這里再多說(shuō)一句:
Bitmap的內(nèi)存管理一直是Android程序員很關(guān)心的問(wèn)題,畢竟它是個(gè)很占內(nèi)存的大胖子,在Android3.0~Android7.0,Bitmap內(nèi)存放在Java堆中,而android系統(tǒng)中每個(gè)進(jìn)程的Java堆是有嚴(yán)格限制的,處理不好這些Bitmap內(nèi)存,容易導(dǎo)致頻繁GC,甚至觸發(fā)Java堆的 OutOfMemoryError 。從Android8.0開(kāi)始,bitmap的像素?cái)?shù)據(jù)放入了native內(nèi)存,于是Java Heap的內(nèi)存問(wèn)題暫時(shí)緩解了。
Tip:
現(xiàn)在材料已經(jīng)備好,我們要真正地畫(huà)東西了。
接下來(lái)就要把東西畫(huà)出來(lái)了,畫(huà)出來(lái)的過(guò)程就是把前面的材料轉(zhuǎn)化成一個(gè)堆像素?cái)?shù)據(jù)的過(guò)程,也叫 柵格化 ,那這個(gè)活兒誰(shuí)來(lái)干呢?
候選人只有兩個(gè):
大部分情況下,都是GPU來(lái)干這個(gè)活兒,因?yàn)镚PU真的特別快?。?!
所謂的“畫(huà)”,對(duì)于計(jì)算機(jī)來(lái)講就是處理圖像,其實(shí)就是根據(jù)需要(就是DisplayList中的命令)對(duì)數(shù)據(jù)做一些特定類(lèi)型的數(shù)學(xué)運(yùn)算,最后輸出結(jié)果的過(guò)程。我們看到的每一幀精美界面,(幾乎)都是GPU吭哧吭哧"算"出來(lái)的,這個(gè)就有疑問(wèn)了:
我們簡(jiǎn)單地聊聊CPU與GPU的區(qū)別:
CPU的核心數(shù)通常是幾個(gè),單個(gè)核心的主頻高,功能強(qiáng)大,擅長(zhǎng)串行處理復(fù)雜的流程;
GPU ( Graphics Processing Unit ) 有成百上千個(gè)核心,單個(gè)核心主頻低,功能有限,擅長(zhǎng)(利用超多核心)大量并行簡(jiǎn)單運(yùn)算;正如它的名字一樣,GPU就是為圖像繪制這個(gè)場(chǎng)景量身定做的硬件(所以使用GPU也叫硬件加速),后來(lái)也被用到挖礦和神經(jīng)網(wǎng)絡(luò)中。
圖片肯定沒(méi)有視頻直觀,我們從感性的角度感受一下GPU到底有多快,我想下面的視頻看過(guò)就不會(huì)忘掉,你會(huì)被GPU折服:
Mythbusters Demo GPU versus CPU
看這個(gè)視頻,我們對(duì)于“加速”應(yīng)該有了更深刻的印象,這里不再進(jìn)一步分析CPU和GPU更微觀的差別(因?yàn)椴欢?,我想已經(jīng)講明白為什們GPU更快了。
另外,在GPU開(kāi)始繪制之前,系統(tǒng)也做了一些優(yōu)化(對(duì)DisplayList中的命令進(jìn)行預(yù)處理),讓整個(gè)繪制流程更加高效:
第二步的具體過(guò)程還是很復(fù)雜的,比如涉及到Alpha繪制,相關(guān)的優(yōu)化會(huì)失效,詳情查看文章 為什么alpha渲染性能低 .
至于畫(huà)在哪里,我們現(xiàn)在理解為一個(gè)緩沖(Buffer)中就可以了,具體的機(jī)制放在第三步講。
到此,我們已經(jīng)畫(huà)(繪制)完了圖像內(nèi)容,把這個(gè)內(nèi)容發(fā)送出去,第二步的任務(wù)就完成了。
Tip:
我們知道,除了我們的應(yīng)用界面,手機(jī)屏幕上同時(shí)顯示著其他內(nèi)容,比如SystemUI(狀態(tài)欄、導(dǎo)航欄)或者另外的懸浮窗等,這些內(nèi)容都需要顯示到屏幕上。所以要先 把這些界面的內(nèi)容合成,然后再顯示到屏幕 。
在講合成圖像之前,我們有必要知道這些界面圖像(Buffer)是怎么傳遞的:
Android圖形架構(gòu)中,使用生產(chǎn)者消費(fèi)者模型來(lái)處理圖像數(shù)據(jù),其中的圖像緩沖隊(duì)列叫 BufferQueue , 隊(duì)列中的元素叫 Graphic Buffer ,隊(duì)列有生產(chǎn)者也有消費(fèi)者;每個(gè)應(yīng)用通常會(huì)對(duì)應(yīng)一個(gè) Surface ,一個(gè) Surface 對(duì)應(yīng)著一個(gè)緩沖隊(duì)列,每個(gè)隊(duì)列中 Graphic Buffer 的數(shù)量不超過(guò)3個(gè), 上面兩步后繪制的圖像數(shù)據(jù)最終會(huì)放入一個(gè) Graphic Buffer ,應(yīng)用自身就是隊(duì)列的生產(chǎn)者( BufferQueue 在Android圖形處理中有廣泛的應(yīng)用,當(dāng)前只討論界面繪制的場(chǎng)景)。
每個(gè) Graphic Buffer 本身體積很大,在從生產(chǎn)者到消費(fèi)者的傳遞過(guò)程中不會(huì)進(jìn)行復(fù)制的操作,都是用匿名共享內(nèi)存的方式,通過(guò)句柄來(lái)跨進(jìn)程傳遞。
我們可以通過(guò)以下命令來(lái)查看手機(jī)當(dāng)前用到的 Graphic Buffer 情況:
關(guān)于上面的命令,你可能會(huì)好奇這個(gè) SurfaceFlinger 是什么東西?。?/p>
上文提到過(guò)每個(gè)應(yīng)用(一般)對(duì)應(yīng)一個(gè) Surface ,從字面意思看, SurfaceFlinger 就是把應(yīng)用的 Surface 投射到目的地。
實(shí)際上, SurfaceFlinger 就是界面(Buffer)合成的負(fù)責(zé)人,在應(yīng)用界面繪制的場(chǎng)景, SurfaceFlinger 充當(dāng)了 BufferQueue 的消費(fèi)者。繪制好的 Graphic Buffer 會(huì)進(jìn)入(queue)隊(duì)列, SurfaceFlinger 會(huì)在合適的時(shí)機(jī)(這個(gè)時(shí)機(jī)下文討論),從隊(duì)列中取出(acquire)Buffer數(shù)據(jù)進(jìn)行處理。
我們知道,除了我們的應(yīng)用界面,手機(jī)屏幕上同時(shí)顯示著其他內(nèi)容,比如SystemUI(狀態(tài)欄、導(dǎo)航欄)或者另外的懸浮窗等,這些部分的都有各自的Surface,當(dāng)然也會(huì)往對(duì)應(yīng)的 BufferQueue 中生產(chǎn) Graphic Buffer 。
如下圖所示, SurfaceFlinger 獲取到所有Surface的最新Buffer之后,會(huì)配合HWComposer進(jìn)行處理合成,最終把這些Buffer的數(shù)據(jù)合成到一個(gè) FrameBuffer 中,而FrameBuffer的數(shù)據(jù)會(huì)在另一個(gè)合適的時(shí)機(jī)(同樣下文討論)迅速地顯示到屏幕上,這時(shí)用戶才觀察到屏幕上的變化。
關(guān)于上圖中的 HWComposer ,它是Android HAL接口中的一部分,它定義了上層需要的能力,讓由硬件提供商來(lái)實(shí)現(xiàn),因?yàn)椴煌钠聊挥布顒e很大,讓硬件提供商驅(qū)動(dòng)自己的屏幕,上層軟件無(wú)需關(guān)心屏幕硬件的兼容問(wèn)題。
事實(shí)上,如果你觀察足夠仔細(xì)的話,可能對(duì)上圖還有疑問(wèn):
同學(xué)你觀察很仔細(xì)(...),事實(shí)上,這是 SurfaceFlinger 合成過(guò)程中重要的細(xì)節(jié),對(duì)于不同 Surface 的Buffer, 合成的方法有兩種:
顯然第一種方法是最高效的,但為了保證正確性,Android系統(tǒng)結(jié)合了兩種方法。具體實(shí)現(xiàn)上, SurfaceFlinger 會(huì)詢問(wèn)( prepare ) HWComposer 是否支持直接合成,之后按照結(jié)果做對(duì)應(yīng)處理。
有的朋友憋不住了:
Good question! (太做作了。。。)
為了保證最好的渲染性能,上面各個(gè)步驟之間并不是串行阻塞運(yùn)行的關(guān)系,所以有一個(gè)機(jī)制來(lái)調(diào)度每一步的觸發(fā)時(shí)機(jī),不過(guò)在此之前,我們先講介紹一個(gè)更基礎(chǔ)的概念:
屏幕刷新率
刷新率是屏幕的硬件指標(biāo),單位是Hz(赫茲),意思是屏幕每秒可以刷新的次數(shù)。
回到問(wèn)題,既然屏幕這個(gè)硬件每隔一段時(shí)間(如60Hz屏幕是16ms)就刷新一次,最佳的方案就是屏幕刷新時(shí)開(kāi)始新一輪的繪制流程,讓一次繪制的流程盡可能占滿整個(gè)刷新周期,這樣掉幀的可能性最小?;谶@樣的思考,在Android4.1(JellyBean)引入 VSYNC(Vertical Synchronization - 垂直同步信號(hào))
收到系統(tǒng)發(fā)出的VSYNC信號(hào)后, 有三件事會(huì)同時(shí)執(zhí)行(并行) :
下圖描述了沒(méi)有掉幀時(shí)的VSYNC執(zhí)行流程,現(xiàn)在我們可以直接回答問(wèn)題了: 合適的時(shí)機(jī)就是VSYNC信號(hào) 。
從上圖可以看出,在一次VSYNC信號(hào)發(fā)出后,屏幕立即顯示2個(gè)VSYNC周期(60Hz屏幕上就是32ms)之前開(kāi)始繪制的圖像,這當(dāng)然是延遲,不過(guò)這個(gè)延遲非常穩(wěn)定, 只要前面的繪制不掉鏈子 ,界面也是如絲般順滑。當(dāng)然,Android還是推出一種機(jī)制讓延遲可以縮小到1個(gè)VSYNC周期,詳情可參考 VSYNC-offset 。
實(shí)際上,系統(tǒng)只會(huì)在需要的時(shí)候才發(fā)出VSYNC信號(hào),這個(gè)開(kāi)關(guān)由SurfaceFlinger來(lái)管理。應(yīng)用也只是在需要的時(shí)候才接收VSYNC信號(hào),什么時(shí)候需要呢?也就是應(yīng)用界面有變化,需要更新了,具體的流程可以參考 View.requestLayout() 或 View.invalidate() 到 Choreographer (編舞者)的調(diào)用過(guò)程。這個(gè)過(guò)程會(huì)注冊(cè)一次VSYNC信號(hào),下一次VSYNC信號(hào)發(fā)出后應(yīng)用就能收到了,然后開(kāi)始新的繪制工作;想要再次接收VSYNC信號(hào)就需要重新注冊(cè),可見(jiàn),應(yīng)用界面沒(méi)有改變的時(shí)候是不會(huì)進(jìn)行刷新的。
我們可以看到,無(wú)論是VSYNC開(kāi)關(guān),還是應(yīng)用對(duì)VSYNC信號(hào)的單次注冊(cè)邏輯,都是秉承著按需分配的原則,這樣的設(shè)計(jì)能夠帶來(lái)Android操作系統(tǒng)更好的性能和更低的功耗。
Tip:
終于。。。說(shuō)完了
我們簡(jiǎn)單回顧一下,
更形象一點(diǎn)就是:
之所以有這一節(jié),是因?yàn)殡S著Android版本的更替,渲染方案也發(fā)生了很多變化。為了簡(jiǎn)化表達(dá),我們前文都以當(dāng)前最新的方案來(lái)講解,事實(shí)上,部分流程的實(shí)現(xiàn)方式在不同版本可能會(huì)有較大的變化,甚至在之前版本沒(méi)有實(shí)現(xiàn)方案,這里我盡可能詳細(xì)地列出Android版本更迭過(guò)程中與渲染相關(guān)的更新(包括監(jiān)控工具)。
如果你居然能讀到這里,那我猜你對(duì)下面的參考文章也會(huì)感興趣:
;feature=emb_logo
;t=177s
;index=64list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE
Android Developer Backstage - Android Rendering
Android Developer Backstage - Graphics Performance
1.遍歷sdcard文件夾(指定層次深度 searchDeep ),如果文件夾發(fā)現(xiàn)圖片 , 添加到已搜索到圖片的文件列表中,并跳入下一個(gè)文件夾搜索
2.使用 ContentResolver 搜索 添加搜索標(biāo)簽(png,jpg,jpeg,gif 等) 優(yōu)點(diǎn):更快速
壓縮調(diào)用
第一步--
采樣率壓縮:設(shè)置 BitmapFactory.Options.inSampleSize 大小
第二步--
PNG:尺寸壓縮( Config:ARGB_4444 ,工具: Canvas );
JPG:尺寸壓縮( Config:ARGB_565 ,工具: Canvas )+壓縮質(zhì)量( bitmap.compress() )
注 :
1.GIF不做壓縮處理
2.尺寸壓縮:改變寬高(png,jpg)
3.壓縮質(zhì)量:改變文件大小(適用jpg,png無(wú)效)
對(duì)于Android開(kāi)發(fā)者來(lái)說(shuō),我們或多或少有了解過(guò)Android圖像顯示的知識(shí)點(diǎn),剛剛學(xué)習(xí)Android開(kāi)發(fā)的人會(huì)知道,在Actvity的onCreate方法中設(shè)置我們的View后,再經(jīng)過(guò)onMeasure,onLayout,onDraw的流程,界面就顯示出來(lái)了;對(duì)Android比較熟悉的開(kāi)發(fā)者會(huì)知道,onDraw流程分為軟件繪制和硬件繪制兩種模式,軟繪是通過(guò)調(diào)用Skia來(lái)操作,硬繪是通過(guò)調(diào)用Opengl ES來(lái)操作;對(duì)Android非常熟悉的開(kāi)發(fā)者會(huì)知道繪制出來(lái)的圖形數(shù)據(jù)最終都通過(guò)GraphiBuffer內(nèi)共享內(nèi)存?zhèn)鬟f給SurfaceFlinger去做圖層混合,圖層混合完成后將圖形數(shù)據(jù)送到幀緩沖區(qū),于是,圖形就在我們的屏幕顯示出來(lái)了。
但我們所知道的Activity或者是應(yīng)用App界面的顯示,只屬于Android圖形顯示的一部分。同樣可以在Android系統(tǒng)上展示圖像的WebView,F(xiàn)lutter,或者是通過(guò)Unity開(kāi)發(fā)的3D游戲,他們的界面又是如何被繪制和顯現(xiàn)出來(lái)的呢?他們和我們所熟悉的Acitvity的界面顯示又有什么異同點(diǎn)呢?我們可以不借助Activity的setView或者InflateView機(jī)制來(lái)實(shí)現(xiàn)在屏幕上顯示出我們想要的界面嗎?Android系統(tǒng)顯示界面的方式又和IOS,或者Windows等系統(tǒng)有什么區(qū)別呢?……
去探究這些問(wèn)題,比僅僅知道Acitvity的界面是如何顯示出來(lái)更加的有價(jià)值,因?yàn)橄胍卮疬@些問(wèn)題,就需要我們真正的掌握Android圖像顯示的底層原理,當(dāng)我們掌握了底層的顯示原理后,我們會(huì)發(fā)現(xiàn)WebView,F(xiàn)lutter或者未來(lái)會(huì)出現(xiàn)的各種新的圖形顯示技術(shù),原來(lái)都是大同小異。
我會(huì)花三篇文章的篇幅,去深入的講解Android圖形顯示的原理,OpenGL ES和Skia的繪制圖像的方式,他們?nèi)绾问褂?,以及他們?cè)贏ndroid中的使用場(chǎng)景,如開(kāi)機(jī)動(dòng)畫(huà),Activity界面的軟件繪制和硬件繪制,以及Flutter的界面繪制。那么,我們開(kāi)始對(duì)Android圖像顯示原理的探索吧。
在講解Android圖像的顯示之前,我會(huì)先講一下屏幕圖像的顯示原理,畢竟我們圖像,最終都是在手機(jī)屏幕上顯示出來(lái)的,了解這一塊的知識(shí)會(huì)讓我們更容易的理解Android在圖像顯示上的機(jī)制。
圖像顯示的完整過(guò)程,分為下面幾個(gè)階段:
圖像數(shù)據(jù)→CPU→顯卡驅(qū)動(dòng)→顯卡(GPU)→顯存(幀緩沖)→顯示器
我詳細(xì)介紹一下這幾個(gè)階段:
實(shí)際上顯卡驅(qū)動(dòng),顯卡和顯存,包括數(shù)模轉(zhuǎn)換模塊都是屬于顯卡的模塊。但為了能能詳細(xì)的講解經(jīng)歷的步驟,這里做了拆分。
當(dāng)顯存中有數(shù)據(jù)后,顯示器又是怎么根據(jù)顯存里面的數(shù)據(jù)來(lái)進(jìn)行界面的顯示的呢?這里以LCD液晶屏為例,顯卡會(huì)將顯存里的數(shù)據(jù),按照從左至右,從上到下的順序同步到屏幕上的每一個(gè)像素晶體管,一個(gè)像素晶體管就代表了一個(gè)像素。
如果我們的屏幕分辨率是1080x1920像素,就表示有1080x1920個(gè)像素像素晶體管,每個(gè)橡素點(diǎn)的顏色越豐富,描述這個(gè)像素的數(shù)據(jù)就越大,比如單色,每個(gè)像素只需要1bit,16色時(shí),只需要4bit,256色時(shí),就需要一個(gè)字節(jié)。那么1080x1920的分辨率的屏幕下,如果要以256色顯示,顯卡至少需要1080x1920個(gè)字節(jié),也就是2M的大小。
剛剛說(shuō)了,屏幕上的像素?cái)?shù)據(jù)是從左到右,從上到下進(jìn)行同步的,當(dāng)這個(gè)過(guò)程完成了,就表示一幀繪制完成了,于是會(huì)開(kāi)始下一幀的繪制,大部分的顯示屏都是以60HZ的頻率在屏幕上繪制完一幀,也就是16ms,并且每次繪制新的一幀時(shí),都會(huì)發(fā)出一個(gè)垂直同步信號(hào)(VSync)。我們已經(jīng)知道,圖像數(shù)據(jù)都是放在幀緩沖中的,如果幀緩沖的緩沖區(qū)只有一個(gè),那么屏幕在繪制這一幀的時(shí)候,圖像數(shù)據(jù)便沒(méi)法放入幀緩沖中了,只能等待這一幀繪制完成,在這種情況下,會(huì)有很大了效率問(wèn)題。所以為了解決這一問(wèn)題,幀緩沖引入兩個(gè)緩沖區(qū),即 雙緩沖機(jī)制 。雙緩沖雖然能解決效率問(wèn)題,但會(huì)引入一個(gè)新的問(wèn)題。當(dāng)屏幕這一幀還沒(méi)繪制完成時(shí),即屏幕內(nèi)容剛顯示一半時(shí),GPU 將新的一幀內(nèi)容提交到幀緩沖區(qū)并把兩個(gè)緩沖區(qū)進(jìn)行交換后,顯卡的像素同步模塊就會(huì)把新的一幀數(shù)據(jù)的下半段顯示到屏幕上,造成畫(huà)面撕裂現(xiàn)象。
為了解決撕裂問(wèn)題,就需要在收到垂直同步的時(shí)候才將幀緩沖中的兩個(gè)緩沖區(qū)進(jìn)行交換。Android4.1黃油計(jì)劃中有一個(gè)優(yōu)化點(diǎn),就是CPU和GPU都只有收到垂直同步的信號(hào)時(shí),才會(huì)開(kāi)始進(jìn)行圖像的繪制操作,以及緩沖區(qū)的交換工作。
我們已經(jīng)了解了屏幕圖像顯示的原理了,那么接著開(kāi)始對(duì)Android圖像顯示的學(xué)習(xí)。
從上一章已經(jīng)知道,計(jì)算機(jī)渲染界面必須要有GPU和幀緩沖。對(duì)于Linux系統(tǒng)來(lái)說(shuō),用戶進(jìn)程是沒(méi)法直接操作幀緩沖的,但我們想要顯示圖像就必須要操作幀緩沖,所以Linux系統(tǒng)設(shè)計(jì)了一個(gè)虛擬設(shè)備文件,來(lái)作為對(duì)幀緩沖的映射,通過(guò)對(duì)該文件的I/O讀寫(xiě),我們就可以實(shí)現(xiàn)讀寫(xiě)屏操作。幀緩沖對(duì)應(yīng)的設(shè)備文件于/dev/fb* ,*表示對(duì)多個(gè)顯示設(shè)備的支持, 設(shè)備號(hào)從0到31,如/dev/fb0就表示第一塊顯示屏,/dev/fb1就表示第二塊顯示屏。對(duì)于Android系統(tǒng)來(lái)說(shuō),默認(rèn)使用/dev/fb0這一個(gè)設(shè)幀緩沖作為主屏幕,也就是我們的手機(jī)屏幕。我們Android手機(jī)屏幕上顯示的圖像數(shù)據(jù),都是存儲(chǔ)在/dev/fb0里,早期AndroidStuio中的DDMS工具實(shí)現(xiàn)截屏的原理就是直接讀取/dev/fb0設(shè)備文件。
我們知道了手機(jī)屏幕上的圖形數(shù)據(jù)都存儲(chǔ)在幀緩沖中,所以Android手機(jī)圖像界面的原理就是將我們的圖像數(shù)據(jù)寫(xiě)入到幀緩沖內(nèi)。那么,寫(xiě)入到幀緩沖的圖像數(shù)據(jù)是怎么生成的,又是怎樣加工的呢?圖形數(shù)據(jù)是怎樣送到幀緩沖去的,中間經(jīng)歷了哪些步驟和過(guò)程呢?了解了這幾個(gè)問(wèn)題,我們就了解了Android圖形渲染的原理,那么帶著這幾個(gè)疑問(wèn),接著往下看。
想要知道圖像數(shù)據(jù)是怎么產(chǎn)生的,我們需要知道 圖像生產(chǎn)者 有哪些,他們分別是如何生成圖像的,想要知道圖像數(shù)據(jù)是怎么被消費(fèi)的,我們需要知道 圖像消費(fèi)者 有哪些,他們又分別是如何消費(fèi)圖像的,想要知道中間經(jīng)歷的步驟和過(guò)程,我們需要知道 圖像緩沖區(qū) 有哪些,他們是如何被創(chuàng)建,如何分配存儲(chǔ)空間,又是如何將數(shù)據(jù)從生產(chǎn)者傳遞到消費(fèi)者的,圖像顯示是一個(gè)很經(jīng)典的消費(fèi)者生產(chǎn)者的模型,只有對(duì)這個(gè)模型各個(gè)模塊的擊破,了解他們之間的流動(dòng)關(guān)系,我們才能找到一條更容易的路徑去掌握Android圖形顯示原理。我們看看谷歌提供的官方的架構(gòu)圖是怎樣描述這一模型的模塊及關(guān)系的。
如圖, 圖像的生產(chǎn)者 主要有MediaPlayer,CameraPrevier,NDK,OpenGl ES。MediaPlayer和Camera Previer是通過(guò)直接讀取圖像源來(lái)生成圖像數(shù)據(jù),NDK(Skia),OpenGL ES是通過(guò)自身的繪制能力生產(chǎn)的圖像數(shù)據(jù); 圖像的消費(fèi)者 有SurfaceFlinger,OpenGL ES Apps,以及HAL中的Hardware Composer。OpenGl ES既可以是圖像的生產(chǎn)者,也可以是圖像的消費(fèi)者,所以它也放在了圖像消費(fèi)模塊中; 圖像緩沖區(qū) 主要有Surface以及前面提到幀緩沖。
Android圖像顯示的原理,會(huì)僅僅圍繞 圖像的生產(chǎn)者 , 圖像的消費(fèi)者 , 圖像緩沖區(qū) 來(lái)展開(kāi),在這一篇文章中,我們先看看Android系統(tǒng)中的圖像消費(fèi)者。
SurfaceFlinger是Android系統(tǒng)中最重要的一個(gè)圖像消費(fèi)者,Activity繪制的界面圖像,都會(huì)傳遞到SurfaceFlinger來(lái),SurfaceFlinger的作用主要是接收?qǐng)D像緩沖區(qū)數(shù)據(jù),然后交給HWComposer或者OpenGL做合成,合成完成后,SurfaceFlinger會(huì)把最終的數(shù)據(jù)提交給幀緩沖。
那么SurfaceFlinger是如何接收?qǐng)D像緩沖區(qū)的數(shù)據(jù)的呢?我們需要先了解一下Layer(層)的概念,一個(gè)Layer包含了一個(gè)Surface,一個(gè)Surface對(duì)應(yīng)了一塊圖形緩沖區(qū),而一個(gè)界面是由多個(gè)Surface組成的,所以他們會(huì)一一對(duì)應(yīng)到SurfaceFlinger的Layer中。SurfaceFlinger通過(guò)讀取Layer中的緩沖數(shù)據(jù),就相當(dāng)于讀取界面上Surface的圖像數(shù)據(jù)。Layer本質(zhì)上是 Surface和SurfaceControl的組合 ,Surface是圖形生產(chǎn)者和圖像消費(fèi)之間傳遞數(shù)據(jù)的緩沖區(qū),SurfaceControl是Surface的控制類(lèi)。
前面在屏幕圖像顯示原理中講到,為了防止圖像的撕裂,Android系統(tǒng)會(huì)在收到VSync垂直同步時(shí)才會(huì)開(kāi)始處理圖像的繪制和合成工作,而Surfaceflinger作為一個(gè)圖像的消費(fèi)者,同樣也是遵守這一規(guī)則,所以我們通過(guò)源碼來(lái)看看SurfaceFlinger是如何在這一規(guī)則下,消費(fèi)圖像數(shù)據(jù)的。
SurfaceFlinger專(zhuān)門(mén)創(chuàng)建了一個(gè)EventThread線程用來(lái)接收VSync。EventThread通過(guò)Socket將VSync信號(hào)同步到EventQueue中,而EventQueue又通過(guò)回調(diào)的方式,將VSync信號(hào)同步到SurfaceFlinger內(nèi)。我們看一下源碼實(shí)現(xiàn)。
上面主要是SurfaceFlinger初始化接收VSYNC垂直同步信號(hào)的操作,主要有這幾個(gè)過(guò)程:
經(jīng)過(guò)上面幾個(gè)步驟,我們接收VSync的初始化工作都準(zhǔn)備好了,EventThread也開(kāi)始運(yùn)轉(zhuǎn)了,接著看一下EventThread的運(yùn)轉(zhuǎn)函數(shù)threadLoop做的事情。
threadLoop主要是兩件事情
mConditon又是怎么接收VSync的呢?我們來(lái)看一下
可以看到,mCondition的VSync信號(hào)實(shí)際是DispSyncSource通過(guò)onVSyncEvent回調(diào)傳入的,但是DispSyncSource的VSync又是怎么接收的呢?在上面講到的SurfaceFlinger的init函數(shù),在創(chuàng)建EventThread的實(shí)現(xiàn)中,我們可以發(fā)現(xiàn)答案—— mPrimaryDispSync 。
DispSyncSource的構(gòu)造方法傳入了mPrimaryDispSync,mPrimaryDispSync實(shí)際是一個(gè)DispSyncThread線程,我們看看這個(gè)線程的threadLoop方法
DispSyncThread的threadLoop會(huì)通過(guò)mPeriod來(lái)判斷是否進(jìn)行阻塞或者進(jìn)行VSync回調(diào),那么mPeriod又是哪兒被設(shè)置的呢?這里又回到SurfaceFlinger了,我們可以發(fā)現(xiàn)在SurfaceFlinger的 resyncToHardwareVsync 函數(shù)中有對(duì)mPeriod的賦值。
可以看到,這里最終通過(guò)HWComposer,也就是硬件層拿到了period。終于追蹤到了VSync的最終來(lái)源了, 它從HWCompser產(chǎn)生,回調(diào)至DispSync線程,然后DispSync線程回調(diào)到DispSyncSource,DispSyncSource又回調(diào)到EventThread,EventThread再通過(guò)Socket分發(fā)到MessageQueue中 。
我們已經(jīng)知道了VSync信號(hào)來(lái)自于HWCompser,但SurfaceFlinger并不會(huì)一直監(jiān)聽(tīng)VSync信號(hào),監(jiān)聽(tīng)VSync的線程大部分時(shí)間都是休眠狀態(tài),只有需要做合成工作時(shí),才會(huì)監(jiān)聽(tīng)VSync,這樣即保證圖像合成的操作能和VSync保持一致,也節(jié)省了性能。SurfaceFlinger提供了一些主動(dòng)注冊(cè)監(jiān)聽(tīng)VSync的操作函數(shù)。
可以看到,只有當(dāng)SurfaceFlinger調(diào)用 signalTransaction 或者 signalLayerUpdate 函數(shù)時(shí),才會(huì)注冊(cè)監(jiān)聽(tīng)VSync信號(hào)。那么signalTransaction或者signalLayerUpdate什么時(shí)候被調(diào)用呢?它可以由圖像的生產(chǎn)者通知調(diào)用,也可以由SurfaceFlinger根據(jù)自己的邏輯來(lái)判斷是否調(diào)用。
現(xiàn)在假設(shè)App層已經(jīng)生成了我們界面的圖像數(shù)據(jù),并調(diào)用了 signalTransaction 通知SurfaceFlinger注冊(cè)監(jiān)聽(tīng)VSync,于是VSync信號(hào)便會(huì)傳遞到了MessageQueue中了,我們接著看看MessageQueue又是怎么處理VSync的吧。
MessageQueue收到VSync信號(hào)后,最終回調(diào)到了SurfaceFlinger的 onMessageReceived 中,當(dāng)SurfaceFlinger接收到VSync后,便開(kāi)始以一個(gè)圖像消費(fèi)者的角色來(lái)處理圖像數(shù)據(jù)了。我們接著看SurfaceFlinger是以什么樣的方式消費(fèi)圖像數(shù)據(jù)的。
VSync信號(hào)最終被SurfaceFlinger的onMessageReceived函數(shù)中的INVALIDATE模塊處理。
INVALIDATE的流程如下:
handleMessageTransaction的處理比較長(zhǎng),處理的事情也比較多,它主要做的事情有這些
handleMessageRefresh函數(shù),便是SurfaceFlinger真正處理圖層合成的地方,它主要下面五個(gè)步驟。
我會(huì)詳細(xì)介紹每一個(gè)步驟的具體操作
合成前預(yù)處理會(huì)判斷Layer是否發(fā)生變化,當(dāng)Layer中有新的待處理的Buffer幀(mQueuedFrames0),或者mSidebandStreamChanged發(fā)生了變化, 都表示Layer發(fā)生了變化,如果變化了,就調(diào)用signalLayerUpdate,注冊(cè)下一次的VSync信號(hào)。如果Layer沒(méi)有發(fā)生變化,便只會(huì)做這一次的合成工作,不會(huì)注冊(cè)下一次VSync了。
重建Layer棧會(huì)遍歷Layer,計(jì)算和存儲(chǔ)每個(gè)Layer的臟區(qū), 然后和當(dāng)前的顯示設(shè)備進(jìn)行比較,看Layer的臟區(qū)域是否在顯示設(shè)備的顯示區(qū)域內(nèi),如果在顯示區(qū)域內(nèi)的話說(shuō)明該layer是需要繪制的,則更新到顯示設(shè)備的VisibleLayersSortedByZ列表中,等待被合成
rebuildLayerStacks中最重要的一步是 computeVisibleRegions ,也就是對(duì)Layer的變化區(qū)域和非透明區(qū)域的計(jì)算,為什么要對(duì)變化區(qū)域做計(jì)算呢?我們先看看SurfaceFlinger對(duì)界面顯示區(qū)域的分類(lèi):
還是以這張圖做例子,可以看到我們的狀態(tài)欄是半透明的,所以它是一個(gè)opaqueRegion區(qū)域,微信界面和虛擬按鍵是完全不透明的,他是一個(gè)visibleRegion,除了這三個(gè)Layer外,還有一個(gè)我們看不到的Layer——壁紙,它被上方visibleRegion遮擋了,所以是coveredRegion
對(duì)這幾個(gè)區(qū)域的概念清楚了,我們就可以去了解computeVisibleRegions中做的事情了,它主要是這幾步操作:
SVG 圖片是一種可支持任意縮放的圖片格式,使用 xml 定義,使用 canvas 中 path 路徑來(lái)完成繪制,和我們傳統(tǒng)使用的 BitMap位圖有很大的區(qū)別。
SVG 在前端早就普及了,在android 中是 google 是在5.0之后開(kāi)始支持的,14年出來(lái)之后兼容是個(gè)大問(wèn)題,隨著2016.2 V7包 23.2.0版本的發(fā)布才算是有個(gè)相對(duì)完善的兼容使用方案。
SVG 的概念我就不寫(xiě)了,拿來(lái)主義啦,原文: Android Vector曲折的兼容之路
不瞎逼逼,我們先來(lái)看一看 android 中的 SVG 矢量圖是個(gè)什么東東
看到?jīng)]有,這就是一個(gè) SVG 矢量圖片,就是一個(gè) xml 文件,右邊是預(yù)覽,先說(shuō)下,這東西的好處:縮放不失真,體積小。這一個(gè) SVG 圖片只有970個(gè)字節(jié)...強(qiáng)大吧,比 png 格式的圖片強(qiáng)的沒(méi)邊了吧,png 我們還得適配,做多套,然后一個(gè)一個(gè)改名字復(fù)制到工程里,有了 SVG 媽媽再也不擔(dān)心我們寫(xiě)作業(yè)啦...
這里需要解釋下這里的幾個(gè)標(biāo)簽:
這里有一分詳細(xì)的屬性說(shuō)明:
好了下面開(kāi)始介紹 SVG 啦
首先,需要講解兩個(gè)概念——SVG和Vector。
SVG,即Scalable Vector Graphics 矢量圖,這種圖像格式在前端中已經(jīng)使用的非常廣泛了
Vector,在Android中指的是Vector Drawable,也就是Android中的矢量圖
因此,可以說(shuō)Vector就是Android中的SVG實(shí)現(xiàn),因?yàn)锳ndroid中的Vector并不是支持全部的SVG語(yǔ)法,也沒(méi)有必要,因?yàn)橥暾腟VG語(yǔ)法是非常復(fù)雜的,但已經(jīng)支持的SVG語(yǔ)法已經(jīng)夠用了,特別是Path語(yǔ)法,幾乎是Android中Vector的標(biāo)配
Android以一種簡(jiǎn)化的方式對(duì)SVG進(jìn)行了兼容,這種方式就是通過(guò)使用它的Path標(biāo)簽,通過(guò)Path標(biāo)簽,幾乎可以實(shí)現(xiàn)SVG中的其它所有標(biāo)簽,雖然可能會(huì)復(fù)雜一點(diǎn),但這些東西都是可以通過(guò)工具來(lái)完成的,所以,不用擔(dān)心寫(xiě)起來(lái)會(huì)很復(fù)雜。
Path指令解析如下所示:
支持的指令:
M = moveto(M X,Y) :將畫(huà)筆移動(dòng)到指定的坐標(biāo)位置
L = lineto(L X,Y) :畫(huà)直線到指定的坐標(biāo)位置
H = horizontal lineto(H X):畫(huà)水平線到指定的X坐標(biāo)位置
V = vertical lineto(V Y):畫(huà)垂直線到指定的Y坐標(biāo)位置
C = curveto(C X1,Y1,X2,Y2,ENDX,ENDY):三次貝賽曲線
S = smooth curveto(S X2,Y2,ENDX,ENDY)
Q = quadratic Belzier curve(Q X,Y,ENDX,ENDY):二次貝賽曲線
T = smooth quadratic Belzier curveto(T ENDX,ENDY):映射
A = elliptical Arc(A RX,RY,XROTATION,FLAG1,FLAG2,X,Y):弧線
Z = closepath():關(guān)閉路徑
坐標(biāo)軸為以(0,0)為中心,X軸水平向右,Y軸水平向下
所有指令大小寫(xiě)均可。大寫(xiě)絕對(duì)定位,參照全局坐標(biāo)系;小寫(xiě)相對(duì)定位,參照父容器坐標(biāo)系
指令和數(shù)據(jù)間的空格可以省略
同一指令出現(xiàn)多次可以只用一個(gè)
注意,’M’處理時(shí),只是移動(dòng)了畫(huà)筆, 沒(méi)有畫(huà)任何東西。 它也可以在后面給出上同時(shí)繪制不連續(xù)線。
關(guān)于這些語(yǔ)法,開(kāi)發(fā)者需要的并不是全部精通,而是能夠看懂即可,其它的都可以交給工具來(lái)實(shí)現(xiàn)。
這里有一篇 Android vector標(biāo)簽 PathData 畫(huà)圖超詳解 詳細(xì)描述了 SVG 中 path 的繪制
好了概念性的東西說(shuō)完了,我們來(lái)看看
SVG 的使用分2種,一種是靜態(tài) SVG 矢量圖,就是本文的主角,本章節(jié)主要談?wù)摰臇|西,另一種是 SVG 矢量動(dòng)畫(huà),是SVG 的高級(jí)應(yīng)用,是給靜態(tài) SVG 加上objectAnimator 動(dòng)畫(huà),應(yīng)用的很廣泛,是實(shí)現(xiàn) android icon 動(dòng)態(tài)交互的核心做法。
上面的SVG 圖大家都看到了,我們就是寫(xiě)一個(gè) xml 的文件,里面承載的標(biāo)簽都是描述如何繪制我們想要的圖案的,畫(huà)布大小,顏色,路徑等,然后交給系統(tǒng)去繪制。
現(xiàn)在讓我們來(lái)看看 SVG 在 andorid 中如何應(yīng)用,如何兼容5.0以下版本。
SVG 雖然早早就在前端使用了,但是 android 上開(kāi)始支持 SVG 的使用還是從5.0開(kāi)始的,在5.0以上系統(tǒng)的使用很簡(jiǎn)單,和之前一樣使用 PNG 圖片一樣
首先 android 中的 SVG 圖片的承載方式是一個(gè) xml 文件,所以UI 給我們的 SVG 圖片是不能直接使用的,這里 google 給我們提供加載方式
Android studio 在 2.3.3 的版本中可以直接使用 svg,新建一個(gè) SVGDemo項(xiàng)目,新建 Vector Asset 文件:app- main - New - Vector Asset 如圖所示:
我們選擇 Local File 就是選擇本地svg文件進(jìn)行導(dǎo)入,對(duì)文件命名后點(diǎn)擊 Next -Finish 在 drawable目錄 下就添加了一個(gè).xml的文件
好了這樣一個(gè) svg 圖片我們算是加入到我們的工程里里了,可以直接使用了。當(dāng)然在此之前我們把 SVG 圖片放在那個(gè) drawable 文件夾呢。對(duì)于這個(gè)問(wèn)題就要說(shuō)一下了:
有一點(diǎn)需要解釋一下,svg 矢量圖文件我們放在drawable 根目錄即可。android 系統(tǒng)不會(huì)根據(jù)你把 svg 矢量圖存放在不同的 drawable 文件夾,對(duì)圖片進(jìn)行分辨率上的縮放,因此我們不用像使用 PNG 圖片時(shí)準(zhǔn)備多套圖片了。我們導(dǎo)入 SVG 圖片默認(rèn)存放的地址就是 drawable根目錄,所以我們就放這里就好了,當(dāng)然也可以自己寫(xiě)SVG 圖片,都是 xml 的,自己寫(xiě)完 path 路徑后都是可以查看預(yù)覽的,一般也不會(huì)自己寫(xiě),都是UI 的活。
這樣就 ok啦,5.0以上的系統(tǒng)SVG你就像一般 png 圖片一樣使用就好啦,你可以試一下。
SVG 在 4.x 版本上的兼容根據(jù) SVG 使用范圍的變化,配置也是逐步增加的
這時(shí) imageview 就不行了,我們需要使用 AppCompatActivity 或是 AppCompatImageView,這時(shí)我們需要導(dǎo)入 V7 包
gradle 需要如下配置:
系統(tǒng)會(huì)在 4.x 版本時(shí)對(duì) SVG 自動(dòng)生成相應(yīng)的 drawable 圖,此時(shí) SVG 是沒(méi)有無(wú)限拉伸特性的,gradle 的配置目的是去這個(gè)
舉個(gè)例子:
資源設(shè)置不能用 src 了,必須使用 srcCompat ,這時(shí)我們能看到圖而不是去 SVG 的特性了
這時(shí)上面的設(shè)置就不夠了,我們?cè)?view 所在的 activity 或是全局添加下面的設(shè)置
然后這還不夠,我們必須給 SVG 圖片添加一個(gè)容器,比如 selector,這樣我們才能正常使用,比如給 textview 設(shè)置圖片,自定義屬性設(shè)置圖片
這個(gè) vc_halfstart_24dp 就是 SVG 圖片
這個(gè)我們必須要添加官方的 vectorDrawable 支持庫(kù)了,最低支持到 23.2.0
這樣基本就沒(méi)啥問(wèn)題了
SVG 配合自定義 view 的話,就得我們讀取 SVG 然后轉(zhuǎn)換成 path 路徑來(lái)畫(huà)了,SVG 實(shí)質(zhì)上也是 xml 文件,所以解析 xml 文件的思路也使用,當(dāng)然還有其他一些 SVG 轉(zhuǎn) path 的思路
SVG前戲—讓你的View多姿多彩 一文中提供了一些思路,大家不妨去看看