系列介紹
臨時(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ī)尺寸 绷柒。也就是如下圖所示
目錄
- 大家都在用的屏幕適配方案
- 今日頭條屏幕適配方案學(xué)習(xí)
- 官方屏幕適配方案
- 頭條方案的第三方加強(qiáng)版 AndroidAutoSize
- 總結(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中所示:
<?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苍姜。
-
計(jì)算出density
比如 當(dāng)以360dp為設(shè)計(jì)寬度基準(zhǔn)的時(shí)候。
需要的 density 計(jì)算方式如下:
dp
為設(shè)計(jì)dp
sW
為屏幕寬度px
-
找到要替換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.density
和 metrics.scaledDensity
頭條文章中也說衙猪,還有些其他dp轉(zhuǎn)換的場景,基本都是通過DisplayMetrics 來計(jì)算的布近。不再贅述垫释。
所以我們只需要替換resource.mMetrics 的density 和 densityDpi 以及 scaledDensity 的值就行了
-
替換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演示下適配前后的樣子:
可以看到效果還是挺不錯的季蚂。
字體適配
當(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é)論:
- 分辨率限定的優(yōu)先級排序十分靠后,僅僅先于平臺版本。
- 分辨率限定會排除任一維度大于實(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é)果平挑。
通過這次屏幕適配方案游添,我也有不少收獲
- 了解了 sw 限定符的規(guī)則
- 了解了分辨率限定符的規(guī)則
- 了解了今日頭條屏幕適配的原理