【Android面試速學(xué)】安卓屏幕適配

系列介紹

臨時(shí)抱佛腳(也說急來抱佛腳) 指平時(shí)不燒香份名,遇到危難時(shí)才求佛保佑菠赚。比喻事到臨頭才慌忙想辦法應(yīng)付。

少壯沒有努力抗愁,所以現(xiàn)在知識不給力馁蒂。

抱佛腳的目的只有一個(gè)呵晚,就是斬獲自己期望中的offer.

靈魂拷問:你們 Android 開發(fā)的時(shí)候,對于 UI 稿的 px 是如何適配的远搪?

我只會:dp加上自適應(yīng)布局以及weight布局比例來適配(也就是傳統(tǒng)屏幕適配方案)劣纲。

我的ui適配知識深度止步于此。

簡直是十分非常太菜了谁鳍。

名詞解釋

dpi :像素密度是屏幕單位面積內(nèi)的像素?cái)?shù)癞季,稱為 dpi(每英寸的點(diǎn)數(shù))。通常以尺寸作為手機(jī)大小衡量單位倘潜,所以dpi計(jì)算公式為 : 對角線px/ 手機(jī)尺寸 绷柒。也就是如下圖所示

img

目錄

  1. 大家都在用的屏幕適配方案
  2. 今日頭條屏幕適配方案學(xué)習(xí)
  3. 官方屏幕適配方案
  4. 頭條方案的第三方加強(qiáng)版 AndroidAutoSize
  5. 總結(jié)

一,大家都在用的屏幕適配方案

傳統(tǒng)適配方案解釋

Android官方提供了 dp單位來適配屏幕涮因,傳統(tǒng)適配方案中废睦,我們通常會結(jié)合約束布局和weight比例來實(shí)現(xiàn)布局的還原。

dp和px的轉(zhuǎn)換

android中的dp在渲染前會將dp轉(zhuǎn)為px养泡,計(jì)算公式:

  • px = density * dp;

  • density = dpi / 160;

  • px = dp * (dpi / 160);

屏幕的dpi則是每單位尺寸像素密度嗜湃。

dpi計(jì)算公式查看名詞解釋部分。

傳統(tǒng)方案問題

由dpi計(jì)算方式可知澜掩,dp可以實(shí)現(xiàn)大部分相似寬高屏幕购披,以及相似比例像素密度的ui適配。但卻不是完全的屏幕等比關(guān)系肩榕。

同樣的20dp可能占用屏幕的寬高比例會因手機(jī)而異刚陡。

這就導(dǎo)致了不同設(shè)備之間通過約束和dp適配可能會呈現(xiàn)不同的效果。

如下圖demo中所示:

img
img
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ImageView
        android:src="@drawable/ic_launcher_background"
        android:layout_width="match_parent"
        android:scaleType="centerCrop"
        android:layout_height="200dp"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_margin="150dp"/>

</androidx.constraintlayout.widget.ConstraintLayout>

可以看到相同的布局文件株汉,在不同尺寸相同dpi的設(shè)備中筐乳,表現(xiàn)就十分迥異。
可見對于各種形狀和大小的安卓手機(jī)乔妈,ui展示變得奇怪蝙云,也就十分常見了。

傳統(tǒng)方案的優(yōu)勢

傳統(tǒng)適配方案按照像素密度來做顯示適配路召。這樣在同樣像素密度贮懈,不同屏幕大小的設(shè)備中,會有這一致的大小體驗(yàn)优训。

比如一個(gè)小屏手機(jī)中的塊朵你,在大屏設(shè)備中也是相似的大小。
這樣就可以在大屏設(shè)備中也就能顯示更多的內(nèi)容揣非,在做好多布局適配的情況下抡医,能有更符合美學(xué)的展示效果。
而不是粗暴的等比放大。

二忌傻,今日頭條開源屏幕適配方案使用

官方文章鏈接

方案目標(biāo)

從文章中可知大脉,今日頭條的目標(biāo)是,以寬度為基準(zhǔn)水孩,等比還原設(shè)計(jì)圖镰矿。

這樣在寬度大同小異的手機(jī)設(shè)備中,將會有更加優(yōu)秀的還原體驗(yàn)俘种。

方案原理

該方案是為了以寬度為基準(zhǔn)還原ui圖秤标。

前面提到 px和dp的轉(zhuǎn)換是 px = dp * density; 而想要讓所有手機(jī)的寬度都有一樣的dp。只需要修改density的值宙刘,就能轉(zhuǎn)換出想要的px苍姜。

  1. 計(jì)算出density

比如 當(dāng)以360dp為設(shè)計(jì)寬度基準(zhǔn)的時(shí)候。

需要的 density 計(jì)算方式如下:

dp 為設(shè)計(jì)dp
sW 為屏幕寬度px

  • \frac{dp}{360}=\frac{px }{ sW}
  • \frac{dp}{360}=\frac{dp*density}{sW}
  • density = \frac{sW}{360}
  1. 找到要替換density的值對象

通過閱讀源碼悬包,我們知道布局文件中dp轉(zhuǎn)成px

  • 首先會調(diào)用 TypedArray#getDimensionPixelSize
  • 然后調(diào)用 TypedValue#complexToDimensionPixelSize
  • 最終調(diào)用TypedValue#applyDimension
public static float applyDimension(int unit, float value,
                                   DisplayMetrics metrics)
{
    switch (unit) {
    case COMPLEX_UNIT_PX:
        return value;
    case COMPLEX_UNIT_DIP:
        return value * metrics.density;
    case COMPLEX_UNIT_SP:
        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轉(zhuǎn)換是使用的 metrics.densitymetrics.scaledDensity

頭條文章中也說衙猪,還有些其他dp轉(zhuǎn)換的場景,基本都是通過DisplayMetrics 來計(jì)算的布近。不再贅述垫释。

所以我們只需要替換resource.mMetrics 的density 和 densityDpi 以及 scaledDensity 的值就行了

  1. 替換application和activity的 resource.mMetrics

替換之后,系統(tǒng)轉(zhuǎn)換px的時(shí)候自然就會使用該density了撑瞧,和簡單就實(shí)現(xiàn)了以屏幕寬度為基準(zhǔn)的適配方案棵譬。

項(xiàng)目使用

//動態(tài)代理減少模板代碼
inline fun <reified T> noOpDelegate(): T {
    return Proxy.newProxyInstance(
        ClassLoader.getSystemClassLoader(),
        arrayOf(T::class.java)
    ) { _, _, _ ->

    } as T
}

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        fun setDisplay(resources: Resources) {
            with(resources.displayMetrics) {
                val originDensityRatio = scaledDensity / density
                density = widthPixels / 360f
                densityDpi = (density * 160).toInt()
                scaledDensity *= originDensityRatio
            }
        }
        ///修改app.resources.displayMetrics
        setDisplay(resources)
        ///字體改變回調(diào)
        registerComponentCallbacks(object : ComponentCallbacks by noOpDelegate() {
            override fun onConfigurationChanged(newConfig: Configuration) {
                if (newConfig.fontScale > 0) {
                    setDisplay(resources)
                }
            }
        })
        ///修改activity.resources.displayMetrics
        registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks by noOpDelegate() {
            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
                setDisplay(activity.resources)
            }
        })
    }
}

在添加之后,就可以看到手機(jī)完美得以360dp為全屏寬度基準(zhǔn)了

用我的小米10演示下適配前后的樣子:

img
img

可以看到效果還是挺不錯的季蚂。

字體適配

當(dāng)然文章中還提到了文本大小的適配,也就是 scaledDensity的值計(jì)算琅束。

文章中的計(jì)算原理如下:

通過計(jì)算之前scaledDensity和density的比獲得現(xiàn)在的scaledDensity

最后調(diào)用 Application#registerComponentCallbacks 注冊下 onConfigurationChanged 監(jiān)聽
扭屁,在字體變化的時(shí)候去更新scaledDensity。

代碼參考前面 項(xiàng)目使用 的代碼

頭條適配問題思考

頭條適配方案以寬度為基準(zhǔn)涩禀,適合手機(jī)設(shè)備料滥,按寬度比例還原設(shè)計(jì)圖,達(dá)到美觀的效果艾船。

然后這個(gè)方案的問題也很明顯葵腹。在大屏設(shè)備上,也是按照寬度等比還原設(shè)計(jì)圖屿岂。

這就導(dǎo)致了app大屏幕運(yùn)行就只是手機(jī)的放大版践宴,完全沒有體驗(yàn)性可言

解決方案:我思考了一下爷怀,這就需要在檢測屏幕為大屏幕的時(shí)候阻肩,主動取消該適配方案。并做對應(yīng)布局更換適配處理运授。

而更換的大屏適配方案烤惊,可以使用 下一節(jié)的 smalllest 屏幕適配方案

三乔煞,官方屏幕適配方案-限定符 or .9png

首先放上官方說明

smallestWidth 限定符

最小寬度限定符是谷歌官方支持的屏幕多布局多資源方案。

使用“最小寬度”屏幕尺寸限定符柒室,您可以為具有最小寬度(以密度無關(guān)像素 dp 或 dip 為度量單位)的屏幕提供備用布局渡贾。

比如:

    res/layout/main_activity.xml           # 用于手機(jī)設(shè)備 (小于 600dp 屏幕寬度的設(shè)備)
    res/layout-sw600dp/main_activity.xml   # 用于 7寸平板 (600dp 或者更寬屏幕的設(shè)備)

最小尺寸參考

最小寬度限定符指定屏幕兩側(cè)的最小尺寸,而不考慮設(shè)備當(dāng)前的屏幕方向雄右,因此這是一種指定布局可用的整體屏幕尺寸的簡單方法空骚。

下面是其他最小寬度值與典型屏幕尺寸的對應(yīng)關(guān)系:

  • 320dp:典型手機(jī)屏幕(240x320 ldpi、320x480 mdpi不脯、480x800 hdpi 等)府怯。

  • 480dp:約為 5 英寸的大手機(jī)屏幕 (480x800 mdpi)。

  • 600dp:7 英寸平板電腦 (600x1024 mdpi)防楷。

  • 720dp:10 英寸平板電腦(720x1280 mdpi牺丙、800x1280 mdpi 等)。

再結(jié)合屏幕方向限定符

如下:

 res/layout-land/main_activity.xml           # For handsets in landscape
 res/layout-sw600dp/main_activity.xml         # For 7” tablets

這樣就能完成大多數(shù)場景下的屏幕適配工作

.9 png 九宮格位圖

普通位圖在放大或者縮小時(shí)候复局,會失真冲簿,被拉伸擠壓。

解決方案是使用九宮格位圖亿昏,這種特殊格式的 PNG 文件會指示哪些區(qū)域可以拉伸峦剔,哪些區(qū)域不可以拉伸,以及安全的內(nèi)容區(qū)域角钩。

九宮格位圖基本上是一種標(biāo)準(zhǔn)的 PNG 文件吝沫,但帶有額外的 1 像素邊框,指示應(yīng)拉伸哪些像素(并且?guī)в?.9.png 擴(kuò)展名递礼,而不只是 .png)惨险。

安卓框架默認(rèn)支持。

這種開發(fā)者基本上很常用脊髓。不再贅述辫愉,不清楚的可以百度。

屏幕分辨率限定符(不建議使用)

該限定符官方文檔并沒有說明将硝,我也找到了一篇文章恭朗,從解析源碼處入手分析了該限定符的用法和邏輯。鏈接在這依疼。該作者是個(gè)大佬痰腮,直接扒了框架的源碼,羨慕大佬的厲害中律罢。

本人以前使用的時(shí)候也是云里霧里的诽嘉。

資源文件夾用法如下 :

- values-480x320
- layout-480x320

現(xiàn)在總結(jié)一下改文章的結(jié)論:

  1. 分辨率限定的優(yōu)先級排序十分靠后,僅僅先于平臺版本。
  2. 分辨率限定會排除任一維度大于實(shí)際分辨率的配置虫腋。比如有 2020x1080骄酗、1080x740以及960x540限定的資源。一臺1920x1080的手機(jī)悦冀,會排除掉2020x1080的資源趋翻,匹配1080x740或960x540中的一個(gè)資源。

限定分辨率總結(jié):

這個(gè)限定符生效邏輯十分詭異盒蟆,有悖常理踏烙,不是只適配特定分辨率,而是會影響到所有完全大于該分辨率的屏幕历等,用了得不償失讨惩,建議不要使用。

四寒屯,頭條方案的第三方加強(qiáng)版 AndroidAutoSize

首先送上的是作者的介紹文章

AndoridAutoSize庫是作者根據(jù)頭條屏幕適配方案荐捻,經(jīng)過不斷的優(yōu)化和擴(kuò)展完善的一個(gè)屏幕適配庫。里面支持了 dp寡夹、sp处面、pt、in菩掏、mm 等各種單位進(jìn)行布局魂角。
因?yàn)闆]有詳細(xì)查閱該庫源碼,所以不在這里進(jìn)行贅述智绸。寫在這里作為一個(gè)備選方案以備后續(xù)使用學(xué)習(xí)

五野揪,總結(jié)

在手機(jī)場景下,可以使用頭條的適配方案完美還原手機(jī)的設(shè)計(jì)稿瞧栗。

而在大屏或者特殊屏幕尺寸場景斯稳,可以使用sw 和 方向限定符結(jié)合,使用不同的layout 或者dimens文件夾沼溜,將會有不錯的結(jié)果平挑。

通過這次屏幕適配方案游添,我也有不少收獲

  1. 了解了 sw 限定符的規(guī)則
  2. 了解了分辨率限定符的規(guī)則
  3. 了解了今日頭條屏幕適配的原理
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末系草,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子唆涝,更是在濱河造成了極大的恐慌找都,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件廊酣,死亡現(xiàn)場離奇詭異能耻,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門晓猛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饿幅,“玉大人,你說我怎么就攤上這事戒职±醵鳎” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵洪燥,是天一觀的道長磕秤。 經(jīng)常有香客問我,道長捧韵,這世上最難降的妖魔是什么市咆? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮再来,結(jié)果婚禮上蒙兰,老公的妹妹穿的比我還像新娘。我一直安慰自己其弊,他們只是感情好癞己,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著梭伐,像睡著了一般痹雅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上糊识,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天绩社,我揣著相機(jī)與錄音,去河邊找鬼赂苗。 笑死愉耙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的拌滋。 我是一名探鬼主播朴沿,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼败砂!你這毒婦竟也來了赌渣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤昌犹,失蹤者是張志新(化名)和其女友劉穎坚芜,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體斜姥,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鸿竖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年沧竟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缚忧。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡悟泵,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出闪水,到底是詐尸還是另有隱情魁袜,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布敦第,位于F島的核電站峰弹,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏芜果。R本人自食惡果不足惜鞠呈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望右钾。 院中可真熱鬧蚁吝,春花似錦、人聲如沸舀射。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脆烟。三九已至山林,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間邢羔,已是汗流浹背驼抹。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拜鹤,地道東北人框冀。 一個(gè)月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像敏簿,于是被迫代替她去往敵國和親明也。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

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