有關(guān)UI適配的屏幕相關(guān)概念很多,如分辨率、density、dpi、dip、dp、sp和px。
孤立地去看這些概念有點難理解,因此我找了3臺不同的android設(shè)備,希望通過實物,以及一個UI適配的實例去學(xué)習(xí)這些知識點。
首先,可以使用adb查看屏幕分辨率和屏密度
wm size //查看屏幕分辨率
wm density //查看屏密度



以下3臺設(shè)備屏幕分辨率和密度分別如下
1.1280x800,160
2.720x1280,320
3.1080x1920,480
先大致看一下相關(guān)名詞的解釋,這樣看很難看懂。
dip : 英文density-independent pixel的縮寫,意為密度無關(guān)像素。
dp :就是dip
px : 像素
dpi :dots per inch , 直接來說就是一英寸多少個像素點。常見取值 120,160,240。
density :縮放因子density。常見取值 1.5 , 1.0 。
sp :英文scale-independent pixel的縮寫,意為縮放無關(guān)像素。它是一種與密度無關(guān)的像素。
分辨率 : 橫縱2個方向的像素點的數(shù)量,常見取值 480X800 ,320X480
屏幕尺寸: 屏幕對角線的長度。電腦電視同理。
屏幕比例的問題。因為只確定了對角線長,2邊長度還不一定。所以有了4:3、16:9這種,這樣就可以算出屏幕邊長了。
粗略看了一下相關(guān)概念后,再來新建一個工程,在activity中加入以下代碼。
float density = getResources().getDisplayMetrics().density;
int densityDpi = getResources().getDisplayMetrics().densityDpi;
//獲取的像素寬高包含虛擬鍵所占空間
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getRealMetrics(dm);
int screenWidth = dm.widthPixels;
int screenHeight = dm.heightPixels;
//獲取的像素寬高不包含虛擬鍵所占空間
DisplayMetrics dm1 = getResources().getDisplayMetrics();
int width= dm1.widthPixels;
int height= dm1.heightPixels;
Log.d(TAG,"density = " + density + ", densityDpi = " + densityDpi + " screenWidth = " + screenWidth
+ " screenHeight = " + screenHeight + " width = " + width + " height = " + height);
在3臺不同設(shè)備下運行結(jié)果如下:
設(shè)備1
06-26 11:36:35.789 2014-2014/com.demo.myapplication D/MainActivity: density = 1.0, densityDpi = 160 screenWidth = 1280 screenHeight = 800 width = 1280 height = 752
設(shè)備2
2019-06-26 11:41:54.135 31444-31444/com.demo.myapplication D/MainActivity: density = 2.0, densityDpi = 320 screenWidth = 720 screenHeight = 1280 width = 720 height = 1280
設(shè)備3
2019-06-26 11:51:07.665 4530-4530/com.demo.myapplication D/MainActivity: density = 3.0, densityDpi = 480 screenWidth = 1080 screenHeight = 1920 width = 1080 height = 1920
DisplayMetrics類中注釋如下。
package android.util;
import android.os.SystemProperties;
public class DisplayMetrics {
/**
* The logical density of the display. This is a scaling factor for the
* Density Independent Pixel unit, where one DIP is one pixel on an
* approximately 160 dpi screen (for example a 240x320, 1.5"x2" screen),
* providing the baseline of the system's display. Thus on a 160dpi screen
* this density value will be 1; on a 120 dpi screen it would be .75; etc.
*
* <p>This value does not exactly follow the real screen size (as given by
* {@link #xdpi} and {@link #ydpi}, but rather is used to scale the size of
* the overall UI in steps based on gross changes in the display dpi. For
* example, a 240x320 screen will have a density of 1 even if its width is
* 1.8", 1.3", etc. However, if the screen resolution is increased to
* 320x480 but the screen size remained 1.5"x2" then the density would be
* increased (probably to 1.5).
*
* @see #DENSITY_DEFAULT
*/
public float density;
/**
* The screen density expressed as dots-per-inch. May be either
* {@link #DENSITY_LOW}, {@link #DENSITY_MEDIUM}, or {@link #DENSITY_HIGH}.
*/
public int densityDpi;
}
/**
* The absolute width of the available display size in pixels.
*/
public int widthPixels;
/**
* The absolute height of the available display size in pixels.
*/
public int heightPixels;
下面來仔細想一下分辨率、density、dpi、dip、dp和px這些定義。
1.wm density 獲取的是densityDpi 。數(shù)值為160,320,480,這個就android中定義的dpi。
2.wm size 獲取的就是 widthPixels和heightPixels。注意,這里的代碼獲取的是2種widthPixels和heightPixels,輸入wm size 命令后獲取的數(shù)據(jù)與getRealMetrics獲取的數(shù)據(jù)一致,因此wm size獲取的數(shù)據(jù)為包含底部虛擬鍵的數(shù)據(jù)。3臺設(shè)備的分辨率分別為1280x800,720x1280,1080x1920,這個就是分辨率。我手上3臺設(shè)備,1臺有底部虛擬鍵,其余2臺無底部虛擬鍵。
3.通過 getResources().getDisplayMetrics().density 獲取的density,數(shù)值分別為1.0,2.0, 3.0。
density計算公式為density = dpi / 160。
即160/ 160,320/ 160,480/ 160 后分別得到1.0,2.0, 3.0。
這里可以再驗證一下,在有root權(quán)限的情況下,可以臨時設(shè)置一下densityDpi這個數(shù)值。例如我設(shè)置為200,此時再次運行程序,結(jié)果為density = 1.25(即200/160), densityDpi = 200,可以看到,這里在改變dpi的情況下,屏幕分辨率等數(shù)值是沒有改變,density發(fā)生了改變。
輸入
wm density 200
結(jié)果
Physical density: 160
Override density: 200

輸出Log如下
06-26 14:32:20.819 2014-2014/com.demo.myapplication D/MainActivity: density = 1.25, densityDpi = 200 screenWidth = 1280 screenHeight = 800 width = 1280 height = 752
4.px
像素單位,圖的尺寸單位,通常說的屏幕分辨率800*400,都是以px為單位。就簡單記住是個單位吧。
可以簡單參考這篇bitmap簡單學(xué)習(xí)記錄中的光柵圖像部分。
更詳細的可以去看計算機圖形學(xué)相關(guān)的書籍。
5.dp/dip
dip即為dp。 虛擬像素單位。 Density Independent Pixels的縮寫,以160dpi為基準。在160dpi設(shè)備 上,density為1,1dp=1px,在240dpi設(shè)備上,density為1.5,1dp=1.5px, 1 dp = density px , 以此類推。
Google公司為了解決分辨率過多的問題,在Android的開發(fā)文檔中定義了px、dp、sp,方便開發(fā)者適配不同分辨率的Android設(shè)備。簡單來說,dp是android定義的一種單位。例如給一個TextView控件定義大小,就可以用dp修飾。
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:text="Hello World!"/>
dp與px換算公式
px=dp*density
dp=px/density
當前設(shè)備 density =1.0,因此 100dp *1.0 = 100px。
- sp
scale-independent pixels(縮放無關(guān)像素)。
安卓開發(fā)用的字體大小單位。
它和dp很相似,但唯一的區(qū)別在于,Android系統(tǒng)允許用戶自定義文字尺寸大?。ㄐ。?,大,超大等),當文字尺寸是“正?!睍r,1sp=1dp=0.00625inch(英寸),當文字尺寸是“大”或“超大”時,1sp>1dp=0.00625inch (1inch = 0.0254m =2.54cm)
sp通常用來修飾textSize,即控件的字體大小。如下所示。
<TextView
android:layout_width="100dp"
android:layout_height="100dp"
android:text="Hello World!"
android:textSize="20sp"/>
sp與px換算公式:
px=sp*density
sp=px/density
這里應(yīng)該是文字尺寸是“正?!钡那闆r下的換算公式,別的大小情況下暫時不考慮。
當前設(shè)備 density =1.0,因此 100sp *1.0 = 100px。
到了這里對以上6個名詞有了一定的概念后,下面來看看實際使用需要面的的問題。
android中UI適配需要考慮的有2點:控件屬性和橫豎屏。
先以簡單的基本的ImageView和TextView控件為例來進行思考。
首先來看ImageView。
ImageView的屬性主要涉及到layout_width、layout_height以及drawable。
以android沒有定義dp為前提來看直接使用px在不同設(shè)備上顯示圖片會導(dǎo)致的問題。
隨便找的一個圖片。

查看其屬性,找到其尺寸大小。

圖片大小為790 x 1822 px。
圖片的px大小是固定的。
寫一個ImageView控件如下所示。android:layout_width 和android:layout_width單位設(shè)置為px,并且將尺寸縮放一下,除以3再約等于一下,防止圖片顯示不全。
<ImageView
android:layout_width="280px"
android:layout_height="610px"
android:src="@drawable/test"/>
在3臺設(shè)備上運行,結(jié)果分別如下。



顯然,僅僅使用px想滿足不同設(shè)備統(tǒng)一顯示效果的需求,會比較難寫。
下面來看看如何使用dp去適配。
如果使用dp的話,android中的dp在渲染前會將dp轉(zhuǎn)為px。
我手上3個設(shè)備。density分別為1.0,2.0, 3.0。要想在3臺設(shè)備上顯示效果一樣。
下面來看要如何實現(xiàn)在3臺設(shè)備上正常顯示圖片,先不看目前普遍流行的例如今日頭條UI適配法,sw適配做法等,來看看按最初的做法,應(yīng)該如何去做適配。有時候一味依賴框架,反而會忘記基礎(chǔ)。
android中屏幕尺寸和屏幕密度定義如下。
屏幕尺寸分為:small,normal,large,xlarge分別表示小,中,大,超大屏
屏幕密度分為:ldpi,mdpi,hdpi,xhdpi,它們的標準值分別是:120dpi,160dpi,240dpi,320dpi。
密度 dpi范圍
ldpi(低) ~120dpi
mdpi(中) ~160dpi
hdpi(高) ~240dpi
xhdpi(超高) ~320dpi
xxhdpi(超超高) ~480dpi
xxxhdpi(超超超高) ~640dpi
放大倍數(shù)(即縮放因子density)如下
密度 放大倍數(shù)
ldpi 0.75
mdpi 1.0
hdpi 1.5
xhdpi 2.0
xxhdpi 3.0
xxxhdpi 4.0
以下部分出處:
https://developer.android.com/training/multiscreen/screendensities?hl=zh-CN
由于運行 Android 的設(shè)備具有多種屏幕密度,您應(yīng)始終提供能夠根據(jù)各種通用密度級別(低密度、中密度、高密度和超高密度)進行定制的位圖資源。這有助于您在所有屏幕密度上獲得良好的圖形質(zhì)量和性能。
如需生成這些圖像,您應(yīng)以矢量格式的原始資源為基礎(chǔ),按以下尺寸縮放比例生成每種屏幕密度對應(yīng)的圖像:
xhdpi:2.0
hdpi:1.5
mdpi:1.0(基準)
ldpi:0.75
這意味著,如果您為 xhdpi 設(shè)備生成了一幅 200x200 的圖像,則應(yīng)分別按 150x150、100x100 和 75x75 圖像密度為 hdpi 設(shè)備、mdpi 設(shè)備和 ldpi 設(shè)備生成同一資源。
然后,將生成的圖片文件置于 res/ 下的相應(yīng)子目錄中,系統(tǒng)將自動根據(jù)運行您的應(yīng)用的設(shè)備的屏幕密度選取正確的文件:
MyProject/
res/
drawable-xhdpi/
awesomeimage.png
drawable-hdpi/
awesomeimage.png
drawable-mdpi/
awesomeimage.png
drawable-ldpi/
awesomeimage.png
之后,每當您引用 @drawable/awesomeimage 時,系統(tǒng)便會根據(jù)屏幕 dpi 選擇相應(yīng)的位圖。
就是說,我手上有一個圖片,像素大小為200x200 px(沒有就打開windows自帶畫圖軟件新建一個)。
然后點擊調(diào)整大小


生成后,添加一個文本,就寫xhdpi 200x200了,然后保存,如下所示。

并且分別在drawable-hdpi、drawable-ldpi、drawable-mdpi和drawable-xxhdpi等目錄下新建文字內(nèi)容不同但是名稱都為test的圖片。





ImageView的android:layout_width和android:layout_height就都寫200dp,因為設(shè)備2的高度是1280,density是2.0,200dp的話在該設(shè)備上就是400px,差不多占3分之一,肉眼可見度高。
layout文件如下
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/colorPrimary"
android:gravity="center">
<ImageView
android:id="@+id/test_image_view"
android:layout_width="200dp"
android:layout_height="200dp"
android:src="@drawable/test"/>
</LinearLayout>
運行程序。
在density為1.0的設(shè)備上運行結(jié)果如下:

在density為2.0的設(shè)備上運行結(jié)果如下:

在density為3.0的設(shè)備上運行結(jié)果如下:

由于設(shè)備1為橫屏,設(shè)備2,3位豎屏,因此這里是在高度上顯示效果一致(基本高度上占除去導(dǎo)航欄和狀態(tài)欄后屏高的三分之一),在寬度上顯示效果不一致。
一般來說橫屏的UI適配是需要新建一個layout布局文件的。
//以下文字部分出自android編程權(quán)威指南
創(chuàng)建水平模式布局具體步驟如下:
在項目工具窗口中,右鍵單擊res目錄后選擇New → Android resource directory菜單項。創(chuàng)建資源目錄界面列出了資源類型及其對應(yīng)的資源特征。從資源類型(Resource type)列表中選擇layout,保持Source Set的main選項不變。接下來選中待選資源特征列表中的Orientation,
然后單擊>>按鈕將其移動至已選資源特征區(qū)域。



將activity_main.xml文件從res/layout目錄復(fù)制至res/layout-land目錄?,F(xiàn)在我們有了一個水平模式布局以及一個默認布局(豎直模式)。注意,兩個布局文件必須具有相同的文件名,這樣它們才能以同一個資源ID被引用。
通常來說,橫屏和豎屏UI從設(shè)計上就不太一樣,需要改變排版之類的東西。
為了與默認的布局文件相區(qū)別,我們簡單修改一個水平模式布局文件,把layout-land下的activity_main.xml中的LinearLayout下的 android:gravity="center"去掉,我這里假設(shè)橫屏的UI設(shè)計就是這樣設(shè)計的,最后文件如下:
<?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"
android:orientation="vertical"
android:background="@color/colorPrimary">
<ImageView
android:id="@+id/test_image_view"
android:layout_width="200dp"
android:layout_height="200dp"
android:src="@drawable/test"/>
</LinearLayout>
此時在3臺設(shè)備上運行,可以發(fā)現(xiàn)僅僅設(shè)備1上運行結(jié)果發(fā)生了改變(因為設(shè)備1是橫屏,設(shè)備2和3都是豎屏,android會自動適配land下的xml文件,如果有的話)。

//寫到這里其實我已經(jīng)斷斷續(xù)續(xù)花了1天多的時間。
到這里為止,應(yīng)該對drawable的適配有了一定的了解了。
drawable適配流程總結(jié)可以參考以下部分。
以下部分出處:
玩轉(zhuǎn)Android drawable圖片適配
https://blog.csdn.net/myoungmeng/article/details/54090891
Android系統(tǒng)適配原則
Android為了更好地優(yōu)化應(yīng)用在不同屏幕密度下的用戶體驗,在項目的res目錄下可以創(chuàng)建drawab-[density](density為6種通用密度名)目錄,開發(fā)者在進行APP開發(fā)時,針對不同的屏幕密度,將圖片放置于對應(yīng)的drawable-[density]目錄,Android系統(tǒng)會依據(jù)特定的原則來查找各drawable目錄下的圖片。
查找流程為:
1. 先查找和屏幕密度最匹配的文件夾。如當前設(shè)備屏幕密度dpi為160,則會優(yōu)先查找drawable-mdpi目錄;如果設(shè)備屏幕密度dpi為420,則會優(yōu)先查找drawable-xxhdpi目錄。
2. 如果在最匹配的目錄沒有找到對應(yīng)圖片,就會向更高密度的目錄查找,直到?jīng)]有更高密度的目錄。例如,在最匹配的目錄drawable-mdpi中沒有查找到,就會查找drawable-hdpi目錄,如果還沒有查找到,就會查找drawable-xhdpi目錄,直到?jīng)]有更高密度的drawable-[density]目錄。
3. 如果一直往高密度目錄均沒有查找,Android就會查找drawable-nodpi目錄。drawable-nodpi目錄中的資源適用于所有密度的設(shè)備,不管當前屏幕的密度如何,系統(tǒng)都不會縮放此目錄中的資源。因此,對于永遠不希望系統(tǒng)縮放的資源,最簡單的方法就是放在此目錄中;同時,放在該目錄中的資源最好不要再放到其他drawable目錄下了,避免得到非預(yù)期的效果。
4. 如果在drawable-nodpi目錄也沒有查找到,系統(tǒng)就會向比最匹配目錄密度低的目錄依次查找,直到?jīng)]有更低密度的目錄。例如,最匹配目錄是xxhdpi,更高密度的目錄和nodpi目錄查找不到后,就會依次查找drawable-xhdp、drawable-hdpi、drawable-mdpi、drawable-ldpi。
舉個例子,假如當前設(shè)備的dpi是320,系統(tǒng)會優(yōu)先去drawable-xhdpi目錄查找,如果找不到,會依次查找xxhdpi → xxxhdpi → hdpi → mdpi → ldpi。對于不存在的drawable-[density]目錄直接跳過,中間任一目錄查找到資源,則停止本次查找。
總結(jié)一下圖片查找過程:優(yōu)先匹配最適合的圖片→查找密度高的目錄(升序)→查找密度低的目錄(降序)。

看完ImageView,接下來來看TextView。
TextView的基本屬性有 text,textSize,layout_width和layout_height。
為方便觀察,先給TextView增加一個外框。
通過shape來設(shè)置背景圖片
首先一個textview_border.xml文件放在drawable文件夾里面
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<solid android:color="#ffffff" />
<stroke android:width="1dip" android:color="#4fa5d5"/>
</shape>
為要添加邊框的TextView添加一個background
android:background="@drawable/textview_border"
如果不考慮margin和padding等屬性或與其他控件同時使用的情況的時候,TextView自身的顯示情況,與text,textSize,layout_width和layout_height等有關(guān)。
這里先不考慮layout_width和layout_height變化的情況。在layout_width和layout_height以及text一定的情況下,需要控制android:textSize去適應(yīng)屏幕。
layout中定義如下。
<TextView
android:text="test1"
android:layout_width="150dp"
android:layout_height="150dp"
android:textSize="50px"
android:gravity="center"
android:background="@drawable/textview_border" />
<TextView
android:text="test2"
android:layout_width="150dp"
android:layout_height="150dp"
android:textSize="50dp"
android:gravity="center"
android:background="@drawable/textview_border" />
<TextView
android:text="test3"
android:layout_width="150dp"
android:layout_height="150dp"
android:textSize="50sp"
android:gravity="center"
android:background="@drawable/textview_border"/>
運行后結(jié)果分別如下所示



對比發(fā)現(xiàn)3張圖實際顯示的test2和test3全部都大小基本一致(拿手指對比測量,沒有用尺子測)。這里可以看到sp很好地解決了不同density下字體顯示大小一致的問題。
雖然3臺設(shè)備density不一致,但是運行程序后顯示的文字的物理大小完全一致。驗證了dp和sp都是密度無關(guān)像素單位。1dp單位在設(shè)備屏幕上總是等于1/160英寸。
一般情況下dp=sp,但是由于android支持自定義字體尺寸(這里字體尺寸應(yīng)該是在系統(tǒng)設(shè)置里面可以設(shè)置的),因此在某些情況下dp不等于sp,所以textSize推薦sp。
至此,通過ImageView和TextView的屬性對px、dp、sp、density、dpi等名詞應(yīng)該都有有了一定的了解。
下面再來考慮更復(fù)雜一些的布局情況,因為一個xml布局文件中通常會存在多個控件。
下面請看這里:
android UI適配簡單記錄二
http://www.itdecent.cn/p/47f37e003edf
參考鏈接:
android使用adb命令查看設(shè)備尺寸和密度https://www.cnblogs.com/zhaoqingyue/p/5887683.html
android利用adb修改手機的分辨率和dpi
https://www.cnblogs.com/Sir-Lin/p/7993828.html
Android 目前最穩(wěn)定和高效的UI適配方案
http://www.itdecent.cn/p/a4b8e4c5d9b0
分辨率,dpi,dp,與最終顯示大小的四角關(guān)系
http://www.itdecent.cn/p/ac325e1446df
dpi 、 dip 、分辨率、屏幕尺寸、px、density 關(guān)系以及換算https://www.cnblogs.com/yaozhongxiao/p/3842908.html
android獲取屏幕密度dpi
https://blog.csdn.net/u013366008/article/details/50895441
Android屏幕密度(Density)和分辨率的關(guān)系
https://blog.csdn.net/feng88724/article/details/6599821
屏幕適配以及DisplayMetrics解析
https://blog.csdn.net/weixin_36194487/article/details/80404044
Android屏幕適配
http://www.itdecent.cn/p/77e20195d931
android適配(一) 之dp、dip、dpi、px、sp簡介及相關(guān)換算
https://blog.csdn.net/qq_23042121/article/details/53118853
兩分鐘理解Android中PX、DP、SP的區(qū)別
https://blog.csdn.net/donkor_/article/details/77680042
px、dp與sp的區(qū)別以及換算
https://www.cnblogs.com/libertycode/p/5247421.html
今日頭條適配方案解讀即常用適配方案總結(jié)
http://www.itdecent.cn/p/d2150109217f
Android適配--最詳細的限定符屏幕適配方案解析 附帶values-Dimens文件生成工具
https://blog.csdn.net/qq_30993595/article/details/85280936
Android 屏幕適配方案
https://blog.csdn.net/lmj623565791/article/details/45460089
騷年你的屏幕適配方式該升級了!-今日頭條適配方案
http://www.itdecent.cn/p/55e0fca23b4f?utm_source=oschina-app
適配不同的屏幕
http://hukai.me/android-training-course-in-chinese/basics/supporting-devices/screens.html
http://developer.android.com/training/basics/supporting-devices/screens.html
Android 適配(drawable文件夾)圖片適配(二)https://www.cnblogs.com/huihuizhang/p/9473698.html
玩轉(zhuǎn)Android drawable圖片適配
https://blog.csdn.net/myoungmeng/article/details/54090891
https://developer.android.com/guide/topics/resources/providing-resources.html#BestMatch
適配不同的屏幕
http://hukai.me/android-training-course-in-chinese/basics/supporting-devices/screens.html
http://developer.android.com/training/basics/supporting-devices/screens.html
android 為TextView添加邊框
https://blog.csdn.net/jwzhangjie/article/details/9404823