android屏幕適配

1、基礎(chǔ)概念

屏幕尺寸

  • 含義:指的屏幕對(duì)角線的物理長(zhǎng)度,單位一般采用英寸(1英寸≈2.53cm)

目前市面常見的有5.0、5.5、5.7等尺寸。

屏幕分辨率

  • 含義:屏幕縱橫向的像素?cái)?shù)量,一般描述成屏幕的"寬x高”=AxB
  • 例子:1080x1920,即寬度方向上有1080個(gè)像素點(diǎn),在高度方向上有1920個(gè)像素點(diǎn)
  • Android手機(jī)常見的分辨率:320x480、480x800、720x1280、1080x1920等等

屏幕像素密度

  • 含義:每英寸長(zhǎng)度包含的像素點(diǎn)數(shù)
  • 單位:dpi(dots per ich)
  • 例子:假設(shè)設(shè)備內(nèi)每英寸有160個(gè)像素,那么該設(shè)備的屏幕像素密度=160dpi,這個(gè)160dpi在安卓中,也被當(dāng)做一個(gè)基準(zhǔn)的屏幕像素密度,此情況下1dp=1px,我們常用的px和dp互相轉(zhuǎn),用到的一個(gè)安卓api中的邏輯密度density在160dpi的時(shí)候,density=1,同理如果是320dpi,density=2;以此類推。
    • desity = 當(dāng)前屏幕像素密度/160
    • **dp = px/desity **
/**
     * px轉(zhuǎn)dp
     *
     * @param context
     * @param pxValue
     * @return
     */
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

  • 安卓常用的像素密度


    image.png

dp 介紹

  • 含義:density-independent pixel,叫dp或dip,與終端上的實(shí)際物理像素點(diǎn)無關(guān)。
  • 例子:場(chǎng)景:假如同樣都是畫一條長(zhǎng)度是屏幕一半的線,如果使用px作為計(jì)量單位,那么在240dpi手機(jī)上設(shè)置應(yīng)為240px;在160dpi的手機(jī)上應(yīng)設(shè)置為160px,二者設(shè)置就不同了;如果使用dp為單位,在這兩種分辨率下,160dp都顯示為屏幕一半的長(zhǎng)度。

sp 介紹

  • 含義:scale-independent pixels,與dp類似,但是可以根據(jù)文字大小首選項(xiàng)進(jìn)行放縮,是設(shè)置字體大小的御用單位。
  • sp 需要注意的事項(xiàng)
    • 1、當(dāng)修改系統(tǒng)字體大小時(shí),字體大小以dp為單位時(shí),大小不變;

    • 2、當(dāng)修改系統(tǒng)字體大小時(shí),字體大小以sp為單位時(shí),大小跟隨變化;


      image.png
      • 為何會(huì)有這種差異性。
        參考文章關(guān)于設(shè)置文字大小,最終會(huì)調(diào)用下面這個(gè)方法:
public static float applyDimension(int unit, float value, DisplayMetrics metrics){
    switch (unit) {    
        case COMPLEX_UNIT_PX:        
            return value;    
        case COMPLEX_UNIT_DIP://------->>dp 基于 density      
            return value * metrics.density;  
        case COMPLEX_UNIT_SP://------->>sp  基于 scaledDensity     
            return value * metrics.scaledDensity;   
        case COMPLEX_UNIT_PT:        
            return value * metrics.xdpi * (1.0f/72);    
        case COMPLEX_UNIT_IN:        
            return value * metrics.xdpi;    
        case COMPLEX_UNIT_MM:        
            return value * metrics.xdpi * (1.0f/25.4f);    
    }    
    return 0;
}

從代碼上看 dp 和 sp的差別就是 density 和 scaleDensity的區(qū)別,關(guān)于這兩個(gè)字段在源碼中可以看到注釋scaledDensity會(huì)收到用戶配置的影響,而density是基于屏幕dpi的,不會(huì)受到用戶配置影響。
這也就是為什么設(shè)置sp單位時(shí)字體大小會(huì)受到用戶配置系統(tǒng)字體的影響.

關(guān)于字體大小到底什么時(shí)候用dp 什么時(shí)候用sp

  • 使用sp會(huì)鎖著系統(tǒng)文字的變化而呈現(xiàn)正相關(guān)變化,這個(gè)設(shè)計(jì)是符合安卓最初設(shè)計(jì)思想的,比如一些眼睛不好的人,可能就會(huì)把字體放大來看,如果app上的文字沒有跟著變大,這可能就會(huì)影響用戶體驗(yàn)。
    • 簡(jiǎn)單測(cè)試了下騰訊系的很多app多數(shù)頁(yè)面都不會(huì)跟隨系統(tǒng)字體大小變化,知乎會(huì)受到系統(tǒng)字體的影響。
    • 其中使用apktool簡(jiǎn)單查看了下qq的一些布局文件和dimens,發(fā)現(xiàn)字體大小,dp、sp都有引用。
  • 還有就是如果使用sp 寫的話,一些注意事項(xiàng)。
    • 首先布局高度盡量不要寫死,可以通過設(shè)置padding或者margin來控制高度,避免文字變大時(shí)候?qū)е碌恼故静蝗默F(xiàn)象。
    • 相對(duì)來說對(duì)于這種適配,相對(duì)布局體驗(yàn)會(huì)更好一些,可以定位控件之間的相對(duì)關(guān)系,避免遮擋問題。

貼幾張圖:(系統(tǒng)設(shè)置文字變大)
比如下面的父控件限死高度的,導(dǎo)致展示不全、文字過大自動(dòng)換行、沒有設(shè)置相關(guān)關(guān)系導(dǎo)致的重疊。
整體來說需要根據(jù)實(shí)際需求具體調(diào)整,盡量在保證業(yè)務(wù)邏輯正常的情況下,調(diào)優(yōu)展示效果。


image.png

屏幕尺寸大小、分辨率、像素密度之間的關(guān)系:

<span id="jump1">像素密度計(jì)算方法</span>

實(shí)際像素密度算法

image.png

計(jì)算結(jié)果不一致?

  • 問題:為什么按照上面算法獲取的dpi值,和api方法獲取的dpi不一致?
    比如我的手機(jī)360n5,在開發(fā)時(shí)我使用DisplayMetrics獲取手機(jī)的densityDpi,這個(gè)densityDpi的大小為480,但是我在官網(wǎng)看見的卻是PPI為401。他的分辨率為1080*1920,屏幕大小為5.5inch,按照上面的算法計(jì)算的話,大約就是401,因此可以肯定sdk上獲取的dpi應(yīng)該不是手機(jī)的真實(shí)dpi,那么android的dpi的計(jì)算方式又是怎樣的?
  • dpi 和 ppi到底有何區(qū)別

DPI(dots per inch)和 PPI(pixels per inch)這兩個(gè)措辭的差別,表面上看來只在于是在談「dot」還是「pixel」。
* 但實(shí)際上 dot 可以指半調(diào)印刷的墨點(diǎn),可以指噴墨打印的墨點(diǎn),可以指掃描儀的采樣點(diǎn),可以指數(shù)字圖像的最小單位(即 pixel),可以指屏幕的物理像素,可以指操作系統(tǒng)的抽象像素……在不同的語境下可以指不同的概念。
* 而 pixel 也可以指數(shù)字圖像的數(shù)據(jù) pixel,可以指屏幕物理像素,也可以指代操作系統(tǒng)的抽象像素……在不同語境下的意義也不同。
* 兩者經(jīng)?;煊茫躁P(guān)于dpi和ppi的區(qū)別,在不同的用途上面,意思也不同。

結(jié)論:在安卓機(jī)上面,我們可以理解ppi就是安卓機(jī)的真實(shí)像素密度,而dpi則是系統(tǒng)的一個(gè)內(nèi)置的值,它接近真實(shí)值,但并不是真實(shí)的像素密度,它的存在就是為了換算dp、sp的 。

  • 安卓的dpi進(jìn)一步解釋
    • Android系統(tǒng)目錄 /system/build.prop 里面有一行 ro.sf.lcd_density=480,這個(gè)就是dpi,這個(gè)值是可以變的,改變后通過sdk api拿到的dpi值就變化了,所以覺得dpi是個(gè)定義值,同一款設(shè)備,最大分辨率已經(jīng)定了,而dpi其實(shí)并不是一定的,但是這個(gè)dpi決定了我們dp、sp和px的換算關(guān)系,這點(diǎn)很重要,也是屏幕適配的關(guān)鍵。其實(shí),每部安卓手機(jī)屏幕都有一個(gè)初始的固定密度,這些數(shù)值是120、160、240、320、480,這些就是android為不同設(shè)備設(shè)定的系統(tǒng)密度。 得到實(shí)際密度以后,一般會(huì)選擇一個(gè)最近的密度作為系統(tǒng)密度,系統(tǒng)密度是出廠預(yù)置的,如440dpi的系統(tǒng)密度就是和它最接近的480dpi;如果是330dpi的設(shè)備,它的系統(tǒng)密度就是320dpi。但是,現(xiàn)在很多手機(jī)不一定會(huì)選擇這些值作為系統(tǒng)密度,而是選擇實(shí)際的dpi作為系統(tǒng)密度,這就導(dǎo)致了很多手機(jī)的dpi也不是在這些值內(nèi)。例如小米Note這樣的xxhdpi的設(shè)備他的系統(tǒng)密度并不是480,而是它的實(shí)際密度440。
    • dp到px的轉(zhuǎn)換公式:px = dp * (dpi / 160)

2、適配方案

布局適配

多套布局

針對(duì)不同的屏幕尺寸,如果差異過大的話,就需要考慮多套布局的方式來處理

  • 首先需要針對(duì)不同屏幕寫布局
  • 然后需要使用尺寸限定符
    • 尺寸限定符相關(guān)簡(jiǎn)要介紹:

      • 限定符示例:


        image.png
      • 如圖示上面同名文件夾后面標(biāo)注都屬于限定符

      限定符使用方法:只需要用橫線加限定符的方式即可使用,xx-限定符。

      • 常見限定符:
      限定符(mdpi,tvdpi,hdpi)可以幫助我們判斷屏幕密度 
      限定符(land,port)可以幫助我們區(qū)分屏幕橫豎屏狀態(tài) 
      限定符(en,fr…)可以幫助我們語言和地區(qū) 
      限定符(v3,v4…)可以幫助我們區(qū)分安卓版本 
      等等
      

      具體參考文章:http://blog.csdn.net/wzy_1988/article/details/52932875

      • 關(guān)于屏幕適配我們需要重點(diǎn)關(guān)注的限定符
        • sw(n)dp:(最窄邊限制符,不受屏幕方向的影響)
          屏幕最小尺寸限定符:就是屏幕可用區(qū)域的最小尺寸,是指屏幕可用高度或?qū)挾鹊淖钚≈?,例?如果你的布局在運(yùn)行時(shí)需要的屏幕最窄邊是600dp,則你可以利用這個(gè)限定符創(chuàng)建布局資源目錄res/layout-sw600dp.只有當(dāng)屏幕的最小寬度或最小高度大于等于600dp時(shí),系統(tǒng)才會(huì)使用這些布局文件或者資源文件
        • w(n)dp:(屏幕最小寬度限定符,受到屏幕方向的影響)
          指定資源使用時(shí)需要的最小寬度.當(dāng)屏幕方向發(fā)生變化時(shí),系統(tǒng)會(huì)調(diào)整這個(gè)值,使其始終為你UI顯示的寬度.
      • 怎么計(jì)算限定符應(yīng)該是多少dp
        • 計(jì)算方法 dp = px/(當(dāng)前屏幕像素密度/160 )
        • 范例:
          • 密度:480 dp / xxhdpi / 3.0x 屏幕分辨率:1080 x 1920 px 屏幕尺寸:2.8" x 5.0" / 5.7 英寸
          • 1080px /(480/160) = 360dp,所以對(duì)應(yīng)文件夾后綴應(yīng)該是xxx-w360dp

定義布局時(shí)候注意事項(xiàng)

  • 多使用相對(duì)關(guān)系定義控件之間的關(guān)聯(lián)
  • 使用"wrap_content"、"match_parent"和"weight“來控制視圖組件的寬度和高度
  • 布局中引用的dp sp一定使用引用關(guān)系,便于后期可能存在的針對(duì)性修改。

一般提前生成一套dimens文件,例如0-400dp,0-30sp,方便使用。

dp sp 適配

dp sp能夠讓同一數(shù)值在不同的分辨率展示出大致相同的尺寸大小。但是當(dāng)設(shè)備差異較大的時(shí)候,就無能為力了。適配的問題還需要我們自己去做,一般是生成帶標(biāo)識(shí)符的多套dimens文件。

  • 適配方案一:dp方法:{"320","360", "384", "400", "411", "533", "640", "720", "768", "820"};
    • 缺點(diǎn):如果沒有默認(rèn)的dimens.xml,那就黃昏依斜陽(yáng)了,還好有提供。
    • 優(yōu)點(diǎn):沒有枚舉全部的item,可以省一些apk空間;有字體sp的適配。
  • 適配方案二:px百分比方法
    • 優(yōu)點(diǎn):針對(duì)性適配效果更精確,體驗(yàn)更好。
    • 缺點(diǎn):屏幕px的種類遠(yuǎn)多于dp的種類,文件數(shù)量多;程序for循環(huán)枚舉item項(xiàng),有部分用不上的px項(xiàng);width和height都適配了,給控件寫width尺寸時(shí)要用dimens_x.xml里面的變量值,寫height尺寸時(shí)需要用dimens_y.xml里面的變量值。

圖片適配

使用點(diǎn)九圖

  • 9patch圖片的作用就是在圖片拉伸的時(shí)候保證其不會(huì)失真,讓圖片在指定的位置拉伸和在指定的位置顯示內(nèi)容,這樣圖片的邊邊角角就不會(huì)出現(xiàn)失真了。

范例:


image.png

具體參考:點(diǎn)九圖的制作

多套圖片

  • 傳統(tǒng)方式切多套圖片,放在對(duì)應(yīng)標(biāo)識(shí)符的文件夾里面(缺點(diǎn):會(huì)導(dǎo)致apk包過大)
  • 只放一套xhdpi的圖(Android會(huì)根據(jù)屏幕密度自動(dòng)選擇對(duì)應(yīng)的資源文件進(jìn)行渲染加載)
    比如說,SDK檢測(cè)到你手機(jī)的分辨率是320x480(dpi=160),會(huì)優(yōu)先到drawable-mdpi文件夾下找對(duì)應(yīng)的圖片資源;但假設(shè)你只在xhpdi文件夾下有對(duì)應(yīng)的圖片資源文件(mdpi文件夾是空的),那么SDK會(huì)去xhpdi文件夾找到相應(yīng)的圖片資源文件,然后將原有大像素的圖片自動(dòng)縮放成小像素的圖片,于是大像素的圖片照樣可以在小像素分辨率的手機(jī)上正常顯示。
    具體請(qǐng)看http://blog.csdn.net/xiebudong/article/details/37040263
    所以理論上來說只需要提供一種分辨率規(guī)格的圖片資源就可以了。
    一般選擇xhdpi的,這一套可以適配市面上大多數(shù)手機(jī),向下縮放,向上擴(kuò)展表現(xiàn)都不會(huì)有太大問題。

注意事項(xiàng):

  • 通過apktool簡(jiǎn)單分析幾個(gè)知名的app,看看別人到底使用了幾套。
    • 微信基本都是xxh為主了
    • 拼多多還是xh xxh都有
  • 隨著安卓設(shè)備的發(fā)展,分辨率越來越高,可能后期xxh更為合適
    • 可以同時(shí)放入xxh的圖標(biāo),避免后期加入的麻煩。
    • 為了防止包體過大,打包時(shí)候可以臨時(shí)刪除xxh文件夾
    • 也可以推廣期一套圖片資源,用戶主動(dòng)升級(jí)時(shí)候可以升級(jí)攜帶xxh資源,體驗(yàn)可能更好一些。

代碼動(dòng)態(tài)適配

某些布局要求嚴(yán)格寬高比的地方,可以考慮代碼的動(dòng)態(tài)設(shè)置寬高;或者為了保持美觀又防止過高的一些pop、dialog也需要代碼動(dòng)態(tài)按照當(dāng)前屏幕比例動(dòng)態(tài)設(shè)置寬高。

  • 范例
view.getLayoutParams().height = Utils.getRealHeight(mContext, 720, 300);

  /**
     * 獲取控件準(zhǔn)確的高度(針對(duì)滿屏的情況)
     *
     * @param context
     * @param width   寬度(可以是相對(duì)值,僅僅用來計(jì)算寬高比例)
     * @param height  高度(可以是相對(duì)值,僅僅用來計(jì)算寬高比例)
     * @return 真正的高度
     */
    public static int getRealHeight(Context context, int width, int height) {
        //寬高比
        float aspectRatio = (float) width / (float) height;
        return (int) (Utils.getScreenWidth(context) / aspectRatio);
    }

 /**
     * 根據(jù)當(dāng)前寬度基準(zhǔn)算真實(shí)高度
     *
     * @param context
     * @param height     寬度基準(zhǔn)下的高度
     * @param totalWidth 寬度基準(zhǔn)
     * @return
     */
    public static int getRealHeightWithBenchmark(Context context, float height, float totalWidth) {
        return (int) ((Utils.getScreenWidth(context) / totalWidth) * height);
    }


  • 范例2:(針對(duì)自適應(yīng)的一些布局,防止過高情況)
//布局渲染完成后回調(diào)設(shè)置,防止獲取不到寬高度
ViewTreeObserver vto = mRecyclerView.getViewTreeObserver();
        vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                mRecyclerView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                if (mRecyclerView.getHeight() > Utils.getScreenHeight(mContext) / 2) {
                    mRecyclerView.getLayoutParams().height = Utils.getScreenHeight(mContext) / 2;
                }
            }
        });

3、實(shí)際適配過程

參考文章:http://blog.csdn.net/wangwangli6/article/details/63258270

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

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

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