android UI適配簡單記錄一

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

wm size  //查看屏幕分辨率
wm density //查看屏密度
1
2
3

以下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
image.png

輸出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。

  1. 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)致的問題。
隨便找的一個圖片。


test.jpg

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


image.png

圖片大小為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é)果分別如下。

1
2
3

顯然,僅僅使用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)整大小


image.png
image.png

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


test.png

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

image.png
test.png
test.png
test.png
test.png

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é)果如下:


1

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


2

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


3

由于設(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ū)域。

image.png

image.png
image.png

將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文件,如果有的話)。

1.png

//寫到這里其實我已經(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)先匹配最適合的圖片→查找密度高的目錄(升序)→查找密度低的目錄(降序)。
image.png

看完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é)果分別如下所示


1
2
3

對比發(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容