Android drawable微技巧,你不知道的drawable細(xì)節(jié)
話說(shuō)微技巧這個(gè)詞也是我自己發(fā)明的,因?yàn)閐rawable這個(gè)東西相信大家天天都在使用,每個(gè)人都再熟悉不過(guò)了,之所以叫微技巧就是對(duì)于這個(gè)我們?cè)偈煜げ贿^(guò)的技術(shù),可能還有一些你所不知道的細(xì)節(jié),那今天我們就來(lái)一起探究一下這些微小的細(xì)節(jié)吧。
大家都知道,在Android項(xiàng)目當(dāng)中,drawable文件夾都是用來(lái)放置圖片資源的,不管是jpg、png、還是9.png,都可以放在這里。除此之外,還有像selector這樣的xml文件也是可以放在drawable文件夾下面的。
但是如果你現(xiàn)在使用Android Studio來(lái)新建一個(gè)項(xiàng)目,你會(huì)發(fā)現(xiàn)有如下的目錄結(jié)構(gòu):
嗯?怎么會(huì)有這么多mipmap開(kāi)頭的文件夾,而且它們的命名規(guī)則和drawable文件夾很相似,也是hdpi、mdpi、xhdpi等等,并且里面還真是放的圖片,難道Android項(xiàng)目中放置圖片的位置已經(jīng)改了?
對(duì)于剛剛從Eclipse轉(zhuǎn)向Android Studio的開(kāi)發(fā)者們可能會(huì)對(duì)mipmap文件夾感到陌生,其實(shí)不用擔(dān)心,我們平時(shí)的編程習(xí)慣并不需要發(fā)生任何改變,因?yàn)閙ipmap文件夾只是用來(lái)放置應(yīng)用程序的icon的,僅此而已。那么在此之前,我們都是把應(yīng)用程序的icon圖標(biāo)和普通的圖片資源一起放到drawable文件夾下的,這樣看上去就會(huì)比較雜亂,有的時(shí)候想從一堆的圖片資源里面找icon半天也找不到,而文件一多也就容易出現(xiàn)漏放的情況,但恰恰Android是極度建議我們?cè)诿恳环N分辨率的文件夾下面都放一個(gè)相應(yīng)尺寸的icon的,因此將它們獨(dú)立出來(lái)專門放到mimap文件夾當(dāng)中就很好地解決了這個(gè)問(wèn)題。
另外,將icon放置在mipmap文件夾還可以讓我們程序的launcher圖標(biāo)自動(dòng)擁有跨設(shè)備密度展示的能力,比如說(shuō)一臺(tái)屏幕密度是xxhdpi的設(shè)備可以自動(dòng)加載mipmap-xxxhdpi下的icon來(lái)作為應(yīng)用程序的launcher圖標(biāo),這樣圖標(biāo)看上去就會(huì)更加細(xì)膩。
關(guān)于建議使用mipmap的原文可以參閱這篇文章:Getting Your Apps Ready for Nexus 6 and Nexus 9,當(dāng)然你還是要科學(xué)上網(wǎng)的。
除此之外,對(duì)于每種密度下的icon應(yīng)該設(shè)計(jì)成什么尺寸其實(shí)Android也是給出了最佳建議,icon的尺寸最好不要隨意設(shè)計(jì),因?yàn)檫^(guò)低的分辨率會(huì)造成圖標(biāo)模糊,而過(guò)高的分辨率只會(huì)徒增APK大小。建議尺寸如下表所示:
然后我們引用mipmap的方式和之前引用drawable的方式是完全一致的,在資源中就使用@mipmap/res_id,在代碼就使用R.mipmap.res_id。比如AndroidManifest.xml中就是這樣引用ic_launcher圖標(biāo)的:
<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application>
好的,關(guān)于mimap的內(nèi)容就講這么多,它并不是本篇文章的重點(diǎn),接下來(lái)我們來(lái)真真正正看一些drawable的微技巧。
首先我準(zhǔn)備了一張270*480像素的圖片:
將圖片命名為android_logo.png,然后把它放在drawable-xxhdpi文件夾下面。為什么要放在這個(gè)文件夾下呢?是因?yàn)槲业氖謾C(jī)屏幕的密度就是xxhdpi的。那么怎么才能知道自己手機(jī)屏幕的密度呢?你可以使用如下方法先獲取到屏幕的dpi值:
float xdpi = getResources().getDisplayMetrics().xdpi; float ydpi = getResources().getDisplayMetrics().ydpi;
其中xdpi代表屏幕寬度的dpi值,ydpi代表屏幕高度的dpi值,通常這兩個(gè)值都是近乎相等或者極其接近的,在我的手機(jī)上這兩個(gè)值都約等于403。那么403又代表著什么意思呢?我們直接參考下面這個(gè)表格就知道了:
從表中可以看出,403dpi是處于320dpi到480dpi之間的,因此屬于xxhdpi的范圍。
圖片放好了之后,下面我在布局文件中引用這張圖片,如下所示:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:id="@+id/image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_logo" /> </LinearLayout>
在ImageView控件中指定加載android_logo這張圖,并把ImageView控件的寬高都設(shè)置成wrap_content,這樣圖片有多大,我們的控件就會(huì)有多大。
現(xiàn)在運(yùn)行一下程序,效果如下所示:
由于我的手機(jī)分辨率是1080*1920像素的,而這張圖片的分辨率是270*480像素的,剛好是手機(jī)分辨率的四分之一,因此從上圖中也可以看出,android_logo圖片的寬和高大概都占據(jù)了屏幕寬高的四分之一左右,大小基本是比較精準(zhǔn)的。
到目前為止一切都挺順利的,不是嗎?下面我們嘗試做點(diǎn)改變,將android_logo.png這張圖移動(dòng)到drawable-xhdpi文件夾下,注意不是復(fù)制一份到drawable-xhdpi文件夾下,而是將圖片移動(dòng)到drawable-xhdpi文件夾下,然后重新運(yùn)行一下程序,效果如下圖所示:
嗯?怎么感覺(jué)圖片好像變大了一點(diǎn),是錯(cuò)覺(jué)嗎?
那么我們?cè)賹⑦@張圖移動(dòng)到drawable-mdpi文件夾下試試,重新運(yùn)行程序,效果如下圖所示:
這次肯定不是錯(cuò)覺(jué)了,這實(shí)在是太明顯了,圖片被放大了!
那么為什么好端端的一張圖片會(huì)被自動(dòng)放大呢?而且這放大的比例是不是有點(diǎn)太過(guò)份了。其實(shí)不然,Android所做的這些縮放操作都是有它嚴(yán)格的規(guī)定和算法的。可能有不少做了很多年Android的朋友都沒(méi)去留意過(guò)這些縮放的規(guī)則,因?yàn)檫@些細(xì)節(jié)太微小了,那么本篇的微技巧探索里面,我們就來(lái)把這些細(xì)節(jié)理理清楚。
首先解釋一下圖片為什么會(huì)被放大,當(dāng)我們使用資源id來(lái)去引用一張圖片時(shí),Android會(huì)使用一些規(guī)則來(lái)去幫我們匹配最適合的圖片。什么叫最適合的圖片?比如我的手機(jī)屏幕密度是xxhdpi,那么drawable-xxhdpi文件夾下的圖片就是最適合的圖片。因此,當(dāng)我引用android_logo這張圖時(shí),如果drawable-xxhdpi文件夾下有這張圖就會(huì)優(yōu)先被使用,在這種情況下,圖片是不會(huì)被縮放的。但是,如果drawable-xxhdpi文件夾下沒(méi)有這張圖時(shí), 系統(tǒng)就會(huì)自動(dòng)去其它文件夾下找這張圖了,優(yōu)先會(huì)去更高密度的文件夾下找這張圖片,我們當(dāng)前的場(chǎng)景就是drawable-xxxhdpi文件夾,然后發(fā)現(xiàn)這里也沒(méi)有android_logo這張圖,接下來(lái)會(huì)嘗試再找更高密度的文件夾,發(fā)現(xiàn)沒(méi)有更高密度的了,這個(gè)時(shí)候會(huì)去drawable-nodpi文件夾找這張圖,發(fā)現(xiàn)也沒(méi)有,那么就會(huì)去更低密度的文件夾下面找,依次是drawable-xhdpi -> drawable-hdpi -> drawable-mdpi -> drawable-ldpi。
總體匹配規(guī)則就是這樣,那么比如說(shuō)現(xiàn)在終于在drawable-mdpi文件夾下面找到android_logo這張圖了,但是系統(tǒng)會(huì)認(rèn)為你這張圖是專門為低密度的設(shè)備所設(shè)計(jì)的,如果直接將這張圖在當(dāng)前的高密度設(shè)備上使用就有可能會(huì)出現(xiàn)像素過(guò)低的情況,于是系統(tǒng)自動(dòng)幫我們做了這樣一個(gè)放大操作。
那么同樣的道理,如果系統(tǒng)是在drawable-xxxhdpi文件夾下面找到這張圖的話,它會(huì)認(rèn)為這張圖是為更高密度的設(shè)備所設(shè)計(jì)的,如果直接將這張圖在當(dāng)前設(shè)備上使用就有可能會(huì)出現(xiàn)像素過(guò)高的情況,于是會(huì)自動(dòng)幫我們做一個(gè)縮小的操作。所以,我們可以嘗試將android_logo這張圖移動(dòng)到drawable-xxxhdpi文件夾下面將會(huì)得到這樣的結(jié)果:
可以看到,現(xiàn)在圖片的寬和高都達(dá)到不手機(jī)屏幕的四分之一,說(shuō)明圖片確實(shí)是被縮小了。
另外,剛才在介紹規(guī)則的時(shí)候提到了一個(gè)drawable-nodpi文件夾,這個(gè)文件夾是一個(gè)密度無(wú)關(guān)的文件夾,放在這里的圖片系統(tǒng)就不會(huì)對(duì)它進(jìn)行自動(dòng)縮放,原圖片是多大就會(huì)實(shí)際展示多大。但是要注意一個(gè)加載的順序,drawable-nodpi文件夾是在匹配密度文件夾和更高密度文件夾都找不到的情況下才會(huì)去這里查找圖片的,因此放在drawable-nodpi文件夾里的圖片通常情況下不建議再放到別的文件夾里面。
圖片被放大的原因現(xiàn)在我們已經(jīng)搞清楚了,那么接下來(lái)還有一個(gè)問(wèn)題,就是放大的倍數(shù)是怎么確定的呢?很遺憾,我沒(méi)有找到相關(guān)的文檔記載,但是我自己總結(jié)出了一個(gè)規(guī)律,這里跟大家分享一下。
還是看一下剛才的 dpi范圍-密度 表格:
可以看到,每一種密度的dpi范圍都有一個(gè)最大值,這個(gè)最大值之間的比例就是圖片會(huì)被系統(tǒng)自動(dòng)放大的比例。
口說(shuō)無(wú)憑,下面我們來(lái)通過(guò)實(shí)例驗(yàn)證一下,修改布局文件中的代碼,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" > <ImageView android:id="@+id/image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/android_logo" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="獲取圖片寬高" android:onClick="buttonClick" /> </LinearLayout>
可以看到,我們添加了一個(gè)按鈕,并給按鈕注冊(cè)了一個(gè)點(diǎn)擊事件。然后在MainActivity中處理這個(gè)點(diǎn)擊事件:
public class MainActivity extends AppCompatActivity { ImageView imageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageView = (ImageView) findViewById(R.id.image); } public void buttonClick(View view) { Toast.makeText(this, "圖片寬度:" + imageView.getWidth(), Toast.LENGTH_SHORT).show(); Toast.makeText(this, "圖片高度:" + imageView.getHeight(), Toast.LENGTH_SHORT).show(); } }
這里在點(diǎn)擊事件中分別獲取圖片的寬和高并使用Toast提示出來(lái)。代碼修改這么多就可以了,然后將圖片移動(dòng)到drawable-mdpi文件夾下。
下面我們來(lái)開(kāi)始分析,mdpi密度的最高dpi值是160,而xxhdpi密度的最高dpi值是480,因此是一個(gè)3倍的關(guān)系,那么我們就可以猜測(cè),放到drawable-mdpi文件夾下的圖片在xxhdpi密度的設(shè)備上顯示會(huì)被放大3倍。對(duì)應(yīng)到android_logo這張圖,原始像素是270*480,放大3倍之后就應(yīng)該是810*1440像素。下面運(yùn)行程序,效果如下圖所示:
驗(yàn)證通過(guò)。我們?cè)賮?lái)試驗(yàn)一次,將圖片移動(dòng)到drawable-xxxhdpi目錄下。xxxhdpi密度的最高dpi值是640,480是它的0.75倍,那么我們就可以猜測(cè),放到drawable-xxxdpi文件夾下的圖片在xxhdpi密度的設(shè)備上顯示會(huì)被縮小至0.75倍。270*480的0.75倍應(yīng)該是202.5*360,由于像素不支持小數(shù)點(diǎn),那么四舍五入就應(yīng)該是203*360像素。重新運(yùn)行程序,效果如下圖所示:
再次驗(yàn)證通過(guò)。如果你有興趣的話可以使用其它幾種dpi的drawable文件夾來(lái)試一試,應(yīng)該都是適配這套縮放規(guī)則的。這樣我們就把圖片為什么會(huì)被縮放,以及具體的縮放倍數(shù)都搞明白了,drawable相關(guān)的細(xì)節(jié)你已經(jīng)探究的非常細(xì)微了。
不過(guò)本篇文章到這里還沒(méi)結(jié)束,下面我準(zhǔn)備講一講我們?cè)趯?shí)際開(kāi)發(fā)當(dāng)中會(huì)遇到的場(chǎng)景。根據(jù)Android的開(kāi)發(fā)建議,我們?cè)跍?zhǔn)備圖片資源時(shí)盡量應(yīng)該給每種密度的設(shè)備都準(zhǔn)備一套,這樣程序的適配性就可以達(dá)到最好。但實(shí)際情況是,公司的UI們通常就只會(huì)給一套圖片資源,想讓他們針對(duì)每種密度的設(shè)備都設(shè)計(jì)一套圖片資源,并且還是按照我們上面講的縮放比例規(guī)則來(lái)設(shè)計(jì),就有點(diǎn)想得太開(kāi)心了。沒(méi)錯(cuò),這個(gè)就是現(xiàn)實(shí)情況,那么在這種情況下,我們應(yīng)該將僅有的這一套圖片資源放在哪個(gè)密度的文件夾下呢?
可以這樣來(lái)分析,根據(jù)我們剛才所學(xué)的內(nèi)容,如果將一張圖片放在低密度文件夾下,那么在高密度設(shè)備上顯示圖片時(shí)就會(huì)被自動(dòng)放大,而如果將一張圖片放在高密度文件夾下,那么在低密度設(shè)備上顯示圖片時(shí)就會(huì)被自動(dòng)縮小。那我們可以通過(guò)成本的方式來(lái)評(píng)估一下,一張?jiān)瓐D片被縮小了之后顯示其實(shí)并沒(méi)有什么副作用,但是一張?jiān)瓐D片被放大了之后顯示就意味著要占用更多的內(nèi)存了。因?yàn)閳D片被放大了,像素點(diǎn)也就變多了,而每個(gè)像素點(diǎn)都是要占用內(nèi)存的。
我們?nèi)匀豢梢酝ㄟ^(guò)例子來(lái)直觀地體會(huì)一下,首先將android_logo.png圖片移動(dòng)到drawable-xxhdpi目錄下,運(yùn)行程序后我們通過(guò)Android Monitor來(lái)觀察程序內(nèi)存使用情況:
可以看到,程序所占用的內(nèi)存大概穩(wěn)定在19.45M左右。然后將android_logo.png圖片移動(dòng)到drawable-mdpi目錄下,重新運(yùn)行程序,結(jié)果如下圖所示:
現(xiàn)在漲到23.40M了,占用內(nèi)存明顯增加了。如果你將圖片移動(dòng)到drawable-ldpi目錄下,你會(huì)發(fā)現(xiàn)占用內(nèi)存會(huì)更高。
通過(guò)這個(gè)例子同時(shí)也驗(yàn)證了一個(gè)問(wèn)題,我相信有不少比較有經(jīng)驗(yàn)的Android程序員可能都遇到過(guò)這個(gè)情況,就是當(dāng)你的項(xiàng)目變得越來(lái)越大,有的時(shí)候加載一張drawable-hdpi下的圖片,程序就直接OOM崩掉了,但如果將這張圖放到drawable-xhdpi或drawable-xxhdpi下就不會(huì)崩掉,其實(shí)就是這個(gè)道理。
那么經(jīng)過(guò)上面一系列的分析,答案自然也就出來(lái)了,圖片資源應(yīng)該盡量放在高密度文件夾下,這樣可以節(jié)省圖片的內(nèi)存開(kāi)支,而UI在設(shè)計(jì)圖片的時(shí)候也應(yīng)該盡量面向高密度屏幕的設(shè)備來(lái)進(jìn)行設(shè)計(jì)。就目前來(lái)講,最佳放置圖片資源的文件夾就是drawable-xxhdpi。那么有的朋友可能會(huì)問(wèn)了,不是還有更高密度的drawable-xxxhdpi嗎?干嗎不放在這里?這是因?yàn)椋忻嫔?80dpi到640dpi的設(shè)備實(shí)在是太少了,如果針對(duì)這種級(jí)別的屏幕密度來(lái)設(shè)計(jì)圖片,圖片在不縮放的情況下本身就已經(jīng)很大了,基本也起不到節(jié)省內(nèi)存開(kāi)支的作用了。
好的,關(guān)于drawable微技巧方面的探索我們就講到這里,本篇文章中也是集合了不少我平時(shí)的工作經(jīng)驗(yàn)總結(jié),以及通過(guò)做試驗(yàn)所得出的一些結(jié)論,相信還是可以給大家?guī)?lái)不少幫助的。
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請(qǐng)查看下面相關(guān)鏈接
- 詳解 Android中Libgdx使用ShapeRenderer自定義Actor解決無(wú)法接收到Touch事件的問(wèn)題
- 詳解Android Libgdx中ScrollPane和Actor事件沖突問(wèn)題的解決辦法
- Android使用libgdx實(shí)現(xiàn)模擬方向鍵控制角色移動(dòng)的方法
- Android 游戲引擎libgdx 資源加載進(jìn)度百分比顯示案例分析
- Android指紋識(shí)別API講解,一種更快更好的用戶體驗(yàn)
- Android在Kotlin中更好地使用LitePal
- Android Studio輕松構(gòu)建自定義模板的步驟記錄
- 詳解Android 檢測(cè)權(quán)限的三種寫法
- Android最簡(jiǎn)單的狀態(tài)切換布局實(shí)現(xiàn)教程
- android自定義環(huán)形對(duì)比圖效果
- Libgdx解決部分Android機(jī)型鎖屏崩潰的方法
相關(guān)文章
AndroidApk混淆編譯時(shí),報(bào)告java.io.IOException...錯(cuò)誤解決辦法
這篇文章主要介紹了 AndroidApk混淆編譯時(shí),報(bào)告Error:Execution failed for task ‘:gviews:transformClassesAndResourcesWithProguardForRelease’.錯(cuò)誤解決辦法的相關(guān)資料,需要的朋友可以參考下2017-03-03Android應(yīng)用開(kāi)發(fā)中自定義ViewGroup的究極攻略
這里我們要演示的自定義ViewGroup中將實(shí)現(xiàn)多種方式排列和滑動(dòng)等效果,并且涵蓋子View之間Touch Event的攔截與處理等問(wèn)題,完全干貨,下面就為大家送上Android應(yīng)用開(kāi)發(fā)中自定義ViewGroup的究極實(shí)例攻略2016-05-05Android自定義viewgroup快速滑動(dòng)(4)
這篇文章主要為大家詳細(xì)介紹了Android自定義viewgroup快速滑動(dòng)的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12Android實(shí)現(xiàn)知乎選項(xiàng)卡動(dòng)態(tài)隱藏效果實(shí)例
選項(xiàng)卡相信對(duì)大家來(lái)說(shuō)應(yīng)該不陌生,最近發(fā)現(xiàn)知乎選項(xiàng)卡的動(dòng)態(tài)隱藏效果不錯(cuò),下面這篇文章主要給大家介紹了關(guān)于Android實(shí)現(xiàn)知乎選項(xiàng)卡動(dòng)態(tài)隱藏效果的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-02-02Android實(shí)現(xiàn)圖片加載進(jìn)度提示
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)圖片加載進(jìn)度提示,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06Flutter?WebView?預(yù)加載實(shí)現(xiàn)方法(Http?Server)
這篇文章主要介紹了Flutter?WebView?預(yù)加載實(shí)現(xiàn)方法,包括資源的配置,資源的下載和存儲(chǔ),版本的管理,如何根據(jù)實(shí)際url獲取對(duì)應(yīng)HttpServer?bind的url等,需要的朋友可以參考下2022-05-05Android實(shí)現(xiàn)環(huán)形進(jìn)度條代碼
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)環(huán)形進(jìn)度條的代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01解決EditText不顯示光標(biāo)的三種方法(總結(jié))
下面小編就為大家?guī)?lái)一篇解決EditText不顯示光標(biāo)的三種方法(總結(jié))。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-04-04android當(dāng)前apn的狀態(tài)以及獲取方法
在絕大多數(shù)android機(jī)器etc路徑下存放一個(gè)的apns-conf.xml文件,表示當(dāng)前機(jī)器使用的apn信息通過(guò)root機(jī)器可以push出來(lái)看看,具體路徑可以上網(wǎng)搜下,接下來(lái)介紹獲取apn的狀態(tài)的方法2013-01-01