Android屏幕適配總結(jié)和思考

前言

其實(shí)網(wǎng)上已經(jīng)有很多人總結(jié)了Andorid 屏幕適配的知識. 這里總結(jié)了適配的主流方案, 通過分析思考適配的本質(zhì), 再來思考各個適配方案的優(yōu)劣. 弄清楚為什么有適配問題.

一.什么是屏幕適配?

這里說的屏幕適配就是在Android眾多機(jī)型上,能有一個相對一致的顯示表現(xiàn).Android機(jī)型分辨率,尺寸,寬高比太多了,如果不做適配后果就是顯示效果差異比較大. 舉個栗子
xml布局:

    <TextView
        android:layout_width="300px"
        android:layout_height="50px"
        android:background="@color/colorAccent"
        android:gravity="center_vertical"
        android:text="文本"/>

左側(cè)是nexus4 右側(cè)是nexus5
屏幕都是等分十等分的, 便于我們觀察差異.

圖一.png

二. 為什么需要做屏幕適配?

剛才可以看到是用px做單位的. 其實(shí)谷歌有默認(rèn)的適配方案,就是采用dp做單位來適配. 我們來看看使用dp做單位的情況是什么樣的.


圖二.png

從圖中可以看到其實(shí)使用dp做適配,基本解決了問題, 只有一些小差異,如果公司要求不高,使用dp做適配其實(shí)也可以的. 沒有問題, 甚至還有些優(yōu)勢 這個后面再分析.
現(xiàn)在主要分析為什么dp適配產(chǎn)生了一些差異, 谷歌這么牛x的公司為什么設(shè)計的方案是這這個樣子 ,好像并沒有完全解決問題, 還需要我們繼續(xù)操心適配問題. 要了解這些,就先需要了解一下基本概念.

三. 基本概念

相信各位大佬肯定猜到我要說什么了. 老生長談的幾個概念, 但是我還是說下,便于之后的分析理解

  • 像素 px: 像素就是對應(yīng)屏幕的分辨率上的像素, 比如手機(jī)是分辨率是1080*1920的,那么手機(jī)橫向就是1080個像素點(diǎn). 我們看圖一,右側(cè)的手機(jī)就是這個分辨率, 在布局中寫300px自然顯示效果大約橫向屏幕的30%寬度.不管什么適配, 其實(shí)最后都是轉(zhuǎn)為px, 因?yàn)閜x是對應(yīng)的屏幕物理像素.
  • 密度無關(guān)像素dp: 這個是谷歌為Android定制的, px = dpi/160 *dp 看到轉(zhuǎn)化公式, 問題來了,dpi是什么.
  • 屏幕像素真實(shí)密度: 就是用屏幕對象線的像素點(diǎn)數(shù)量/對角線英寸 就可以計算得出.
  • 邏輯像素密度 dpi : 這個也是密度, 和上面的密度的區(qū)別就是這個密度是廠家rom定義的. 和屏幕像素真實(shí)密度有一定的差異. getResources().getDisplayMetrics().densityDpi 可以獲取到該值;

我們看看nexus5 nexus5x 手機(jī)參數(shù)


image.png

實(shí)際運(yùn)行效果 如下圖: 這個顯示效果和上面表格計算出來的一致


圖三.png

可以看出使用dp做單位影響因素最大的就是dpi這個值了

適配問題的產(chǎn)生核心本質(zhì)就一句話:

  • 因?yàn)閐pi和實(shí)際像素密度的差異導(dǎo)致使用dp做單位,沒有很好的適配.

四. Android各個適配方案對比

一. dp 適配

dp適配是谷歌原始的適配方案, 上文也做了分析, 大家再來思考一下之前提出的問題 為什么谷歌這樣設(shè)計. 設(shè)個一個dpi的概念, 而不使用真實(shí)像素密度的?
其實(shí)dpi是為了給手機(jī)廠商靈活配置顯示效果的. 有兩個手機(jī)一個是5寸 一個是6寸 , 如果直接采用真實(shí)像素密度那么6寸手機(jī)就是5寸手機(jī)的完全放大版本.很多時候我們想著的是大屏手機(jī)可以看到更多的內(nèi)容. 而不是單純的等比例放大.
缺點(diǎn): 在不同的手機(jī)上表現(xiàn)不同, 不符合公司設(shè)計圖標(biāo)準(zhǔn).

二. 直接資源引用適配

我們在布局中寫的單位可以引用dimens資源文件. 配置不同的分辨率然后手機(jī)運(yùn)行的時候可以根據(jù)不同分辨率去對應(yīng)不同的單位. 參考鏈接:
http://blog.csdn.net/lmj623565791/article/details/45460089
優(yōu)點(diǎn): 可以很好的控制不同分辨率的顯示效果.
缺點(diǎn): Andorid手機(jī)分辨率越來越多, 維護(hù)這套多分辨率的文件十分繁瑣. 更糟糕的是, 如果沒有對應(yīng)的分辨率 不會去自動匹配相似的分辨率.
這種方案不做推薦

三. 最小寬度限定符

和方案一類似, 但是不是指定分辨率的,而是指定屏幕寬度的, 這樣一來文件就要少很多了
目標(biāo)屏幕的最小寬度都大于480dp時,屏幕就會自動到帶sw480dp后綴的資源文件里去尋找相關(guān)資源文件桂塞,這里的最小寬度是指屏幕寬高的較小值谋币,每個屏幕都是固定的富纸,不會隨著屏幕橫向縱向改變而改變昆稿。

image.png

參考使用鏈接: http://www.reibang.com/p/759375113de9
優(yōu)點(diǎn): 可以很好的控制不同分辨率的顯示效果. 缺少對應(yīng)寬度的引用資源文件 ,可以默認(rèn)去匹配相近的文件.
缺點(diǎn): 還是要去維護(hù)較多的文件. 略繁瑣.

四. 谷歌退出的新的百分比設(shè)置支持庫

這個庫提供了PercentRelativeLayut percentFrameLayout 支持常用的屬性. 使用的時候設(shè)置百分比就可以用了.
實(shí)現(xiàn)原理: 通過Layoutparams 獲取child設(shè)置的百分比. 通過計算獲取這百分比應(yīng)該是多少. 測量出控件的大小.
缺點(diǎn): 使用起來比較麻煩. 以為設(shè)計圖標(biāo)注的是px.每次設(shè)置百分比的時候需要去計算.
設(shè)置百分比還是依賴父容器的.導(dǎo)致scrollView ListView 內(nèi)的高度無法使用百分比.
這種方案用的人比較少 不推薦使用.

五. 張鴻洋提出的 AutoLayout 全新適配方式.

使用px單位并不是像素.內(nèi)部經(jīng)過處理.變成相應(yīng)的百分比. 寬和高都是百分比. 寬的1px和高的1px不相同;
https://github.com/hongyangAndroid/AndroidAutoLayout
鴻洋大神的這套方案用的很多 . 雖然最后停止維護(hù)了,
優(yōu)點(diǎn): 配置簡單, 使用起來可以直接用px對著設(shè)計稿直接寫.
缺點(diǎn): 在一些復(fù)雜布局, 帶一些自定義控件的布局時候容易出現(xiàn)適配問題.
而且在實(shí)際項(xiàng)目使用的時候 有偶發(fā)失效的情況. 失效后, 界面由于px做單位. 界面元素變的很小.
這方案可以考慮使用. 畢竟在項(xiàng)目中運(yùn)用廣泛. 需要注意的是一些自定義布局的處理, 在列表中itemview需要通過工具類額外處理一下.

六. 今日頭條方案:

其實(shí)以上幾種方案都不是很好, 在項(xiàng)目中有各自缺點(diǎn). 目前最推薦的還是頭條的方案;
參考鏈接: https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA

工具類如下:

    /**
     * @param activity
     * @param application
     * @param isLandscape 是否是橫屏
     */
 public class ScreenUtils {

    /** 設(shè)計稿標(biāo)準(zhǔn) */
    private static final float width = 750f;
    private static final float high = 1334f;


    private static float textDensity = 0;
    private static float textScaledDensity = 0;


    /**
     * 今日頭條的屏幕適配方案
     * 根據(jù)當(dāng)前設(shè)備物理尺寸和分辨率去動態(tài)計算density巡李、densityDpi黄琼、scaledDensity
     * 同時也解決了用戶修改系統(tǒng)字體的情況
     * @param activity
     */
    public static void setCustomDensity(@NonNull Activity activity) {
        setCustomDensity(activity, false);
    }


    /**
     * @param activity
     * @param isLandscape 是否是橫屏
     */
    public static void setCustomDensity(@NonNull final Activity activity, boolean isLandscape) {
        final Application application = activity.getApplication();
        final DisplayMetrics displayMetrics = application.getResources().getDisplayMetrics();
        if (textDensity == 0) {
            textDensity = displayMetrics.density;
            textScaledDensity = displayMetrics.scaledDensity;
            application.registerComponentCallbacks(new ComponentCallbacks() {
                @Override
                public void onConfigurationChanged(Configuration configuration) {
                    if (configuration != null && configuration.fontScale > 0) {
                        textScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
                    }
                }

                @Override
                public void onLowMemory() {

                }
            });
        }

        final float targetDensity;
        if (isLandscape) {//橫屏
            targetDensity = displayMetrics.widthPixels / (high / 2); //當(dāng)前UI標(biāo)準(zhǔn)750*1334
        } else {
            targetDensity = displayMetrics.widthPixels / (width / 2); //當(dāng)前UI標(biāo)準(zhǔn)750*1334
        }


        final float targetScaledDensity = targetDensity * (textScaledDensity / textDensity);
        final int targetDpi = (int) (160 * targetDensity);

        displayMetrics.density = targetDensity;
        displayMetrics.scaledDensity = targetScaledDensity;
        displayMetrics.densityDpi = targetDpi;

        final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
        activityDisplayMetrics.density = targetDensity;
        activityDisplayMetrics.scaledDensity = targetScaledDensity;
        activityDisplayMetrics.densityDpi = targetDpi;
    }

}

使用如下:

public class MainActivity extends AppCompatActivity {
    private TextView tv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //在設(shè)置布局之前調(diào)用工具類方法
        ScreenUtils.setCustomDensity(this);

        setContentView(R.layout.activity_main);
        tv = (TextView) findViewById(R.id.tv);
        int densityDpi = getResources().getDisplayMetrics().densityDpi;
        tv.setText("邏輯像素密度 dpi  " + densityDpi);

    }
}

看看實(shí)際運(yùn)行效果:
實(shí)際效果中可以看到很好的完成了適配


圖片四.png

之前我們分析了dp不太適配的原因. 所以今日頭條方案簡答暴力.
既然rom中設(shè)置的dpi不是實(shí)際像素密度. 那么我直接把dpi給你改成實(shí)際像素密度.
果然夠簡單暴力的...
工具類比較簡單,復(fù)制就可以用. 就不上傳github了.

五. 最后總結(jié)和思考:

實(shí)際開發(fā)中我們直接使用今日頭條方案就可好了, 可以滿足需求.
似乎我們已經(jīng)找到了完美的適配方案了.
但是? 但是了. 但是這個所謂的適配的真的就是谷歌的個各個手機(jī)廠商想看到的么?
廠商定義dpi沒有按照實(shí)際的來定義 而是做了調(diào)整, 調(diào)整之后的好處就是就是希望在大屏手機(jī)上可以看更多的內(nèi)容. 在圖三中我們發(fā)現(xiàn)nexus-5x 比nexus-5 屏幕大一些 dpi值廠商設(shè)置的也diy低一些, 更具公式px=dpi/160*dp 得知這樣px就變小, 內(nèi)容占的地方就小, 可以容納更多內(nèi)容. 這就是為了讓大屏手機(jī)看到更多的內(nèi)容. 為了驗(yàn)證這個猜想我們再看看更大手機(jī)的顯示情況

圖五.png

圖五中左側(cè)是通過工具類轉(zhuǎn)換后的顯示, 完成了所謂的適配.
右側(cè)是本來的樣子. 可以看到右側(cè)中textview占據(jù)的屏幕空間確實(shí)要小一些.

圖六.png

圖六中的表格看到越是大屏手機(jī), 廠商對dpi的標(biāo)定比實(shí)際小的越多, 這樣就可以讓屏幕顯示更多的內(nèi)容.特別是看電子書之類的內(nèi)容時候體驗(yàn)更明顯, 大屏手機(jī)一頁可以顯示更多的內(nèi)容. 大屏幕的優(yōu)勢就凸顯出來了.

但是我們開發(fā)的普通應(yīng)用為了在大屏小屏上一致的體驗(yàn). 就不得不去修改dpi為實(shí)際值了.
以上是我的對Android中適配的一些思考和總結(jié), 希望對大家有幫助~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末崭参,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子慰于,更是在濱河造成了極大的恐慌钮科,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件婆赠,死亡現(xiàn)場離奇詭異绵脯,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)休里,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門蛆挫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人妙黍,你說我怎么就攤上這事璃吧。” “怎么了废境?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵畜挨,是天一觀的道長筒繁。 經(jīng)常有香客問我,道長巴元,這世上最難降的妖魔是什么毡咏? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮逮刨,結(jié)果婚禮上呕缭,老公的妹妹穿的比我還像新娘。我一直安慰自己修己,他們只是感情好恢总,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著睬愤,像睡著了一般片仿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上尤辱,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天砂豌,我揣著相機(jī)與錄音,去河邊找鬼光督。 笑死阳距,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的结借。 我是一名探鬼主播筐摘,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼船老!你這毒婦竟也來了蓄拣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤努隙,失蹤者是張志新(化名)和其女友劉穎球恤,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體荸镊,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡咽斧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了躬存。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片张惹。...
    茶點(diǎn)故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖岭洲,靈堂內(nèi)的尸體忽然破棺而出宛逗,到底是詐尸還是另有隱情,我是刑警寧澤盾剩,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布雷激,位于F島的核電站替蔬,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏屎暇。R本人自食惡果不足惜承桥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望根悼。 院中可真熱鬧凶异,春花似錦、人聲如沸挤巡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽矿卑。三九已至喉恋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間粪摘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工绍坝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留徘意,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓轩褐,卻偏偏與公主長得像椎咧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子把介,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容