Android 屏幕適配

1. 基本概念

android中dp在渲染之前會將dp轉為px可缚,計算公式:

px = density * dp;
density = dpi / 160;
px = dp * (dpi / 160);

//相關源碼:TypedValue.java
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(dip):Density independent pixels 設備無關像素
  • dpi:dots per inch贸桶,一英寸多少個像素點。常見取值120刃泌、160顾彰、240缤沦,一般稱作像素密度塘偎,簡稱密度疗涉。根據(jù)屏幕真實的分辨率和尺寸來計算的,每個設備都可能不一樣吟秩。dpi=sqrt(寬^2 + 高^2 單位px)/屏幕尺寸(單位:英寸 inch)
  • density:直接翻譯是密度的意思咱扣,值等于dpi/160。意思是1dp占當前設備多少像素
  • scaledDensity:顯示屏上顯示的字體的比例因子涵防。未調(diào)節(jié)系統(tǒng)字體大小時闹伪,與density的值是一樣的。調(diào)整了字體大小的話壮池,則會增大或縮小
  • 屏幕尺寸:屏幕對角線的長度祭往。電腦電視同理

2. 常見方案

2.1 ConstraintLayout

  • 利用bias屬性,確立View在水平方向或者垂直方向的位置百分比
  • Ratio火窒,寬高比
  • Circle,圓形定位
  • layout_constraintWidth_percent和layout_constraintHeight_percent:將此維度的大小設置為父級的百分比
  • Guideline的layout_constraintGuide_percent,距離父親寬度或高度的百分比(取值范圍0-1)
  • ...

利用ConstraintLayout不僅可以幫助我們減少嵌套驮肉,更輕松地完成UI代碼的編寫熏矿,而且還能解決基礎的適配問題,簡直是Android開發(fā)一大利器离钝。

2.2 多dimens基于dp的適配方案

在res文件夾中創(chuàng)建多套values文件夾

  • values
  • values-sw320dp
  • values-sw360dp
  • values-sw384dp
  • values-sw400dp
  • values-sw432dp
  • values-sw480dp
  • values-sw533dp
  • values-sw600dp

values后面的sw指的是smallest width票编,也就是最小寬度。Android系統(tǒng)在運行時會自動識別屏幕的可用最小寬度卵渴,然后根據(jù)識別的結果去資源文件中查找相對應的資源文件中的屬性值慧域。這種方式容錯機制比較好,比如一個手機的最小寬度是350dp浪读,那么系統(tǒng)在res中沒有找到values-sw350dp文件夾昔榴,就會向下依次查找最接近的最小寬度文件夾,比如上面的values-sw320dp碘橘。雖然不是特別精確互订,但效果也沒有相差太遠。

2.3 字體大小用dp還是sp

如果app中的字體大小需要滿足用戶修改系統(tǒng)字體大小時痘拆,也跟著變化仰禽,那么需要將字體大小設置為sp。如果沒有這種需求,那么字體大小設置為dp也不失為一種很好的方案吐葵,字體大小不會跟隨系統(tǒng)字體大小變化而變化规揪,可在一定程度上保證UI效果。

2.4 今日頭條方案

今日頭條方案原文:一種極低成本的Android屏幕適配方式 https://mp.weixin.qq.com/s/d9QCoBP6kV9VSWvVldVVwA

根據(jù)公式px=dp*density,如果設計圖寬是360dp温峭,想要保住在所有設備計算得出的px值都正好是屏幕寬度的話猛铅,我們只能修改density的值。

從源碼中得知诚镰,density是DisplayMetrics中的成員變量奕坟,在TypedValue#applyDimension方法(dp到px的轉換都得經(jīng)過這里)中轉換長度時起著至關重要的作用。DisplayMetrics是通過Resources#getDisplayMetrics中獲得的清笨,我們可以直接修改 Activity 或者 Application 的 Context -> Resources -> DisplayMetrics -> density月杉。修改density之后,dp永遠就是360dp了抠艾,我們在xml中將View寬度寫成180dp苛萎,那么這個View在所有設備上都是占用屏幕寬度的一半。

核心代碼如下:

//-----------------------今日頭條方案 start-----------------------

//如果DisplayMetrics#scaledDensity和DisplayMetrics#density設置為同樣的值检号,
// 從而某些用戶在系統(tǒng)中修改了字體大小失效了腌歉,但是我們還不能直接用原始的scaledDensity,
// 直接用的話可能導致某些文字超過顯示區(qū)域齐苛,因此我們可以通過計算之前scaledDensity和density(從Resources.getSystem()中獲惹谈恰)的比獲得現(xiàn)在的scaledDensity
//使用Resources.getSystem()的話,就不用監(jiān)聽字體大小變化,它能感知到。

fun setCustomDensity(activity: Activity, application: Application) {
    val appDisplayMetrics = application.resources.displayMetrics

                                                //假設  設計圖寬度為360
    val targetDensity = appDisplayMetrics.widthPixels / 360f
    val targetScaleDensity = targetDensity * (Resources.getSystem().displayMetrics.scaledDensity / Resources.getSystem().displayMetrics.density)
    //dpi = density*160
    val targetDensityDpi = (160 * targetDensity).toInt()

    appDisplayMetrics.density = targetDensity
    //與字體大小有關
    appDisplayMetrics.scaledDensity = targetScaleDensity
    appDisplayMetrics.densityDpi = targetDensityDpi

    val activityDisplayMetrics = activity.resources.displayMetrics
    activityDisplayMetrics.density = targetDensity
    activityDisplayMetrics.scaledDensity = targetScaleDensity
    activityDisplayMetrics.densityDpi = targetDensityDpi
}
//-----------------------今日頭條方案 end-----------------------

這個方案有一些缺陷:

  • 因為修改了DisplayMetrics#density的dp適配凹蜂,所以會導致系統(tǒng)View尺寸和原先不一致馍驯,比如Dialog、Toast玛痊、尺寸汰瘫,同樣,三方View的大小也會和原來的效果不一致擂煞。解決:在使用之前取消適配混弥,使用完再恢復適配。
  • DisplayMetrics#density可能會被還原对省,比如界面中有WebView蝗拿,它的初始化會還原DisplayMetrics#density的值,導致適配失效蒿涎。解決:重寫setOverScrollMode方法蛹磺,在里面恢復適配。
  • 不同寬度的手機看到的內(nèi)容是一樣多的同仆。解決:可以使用sw的方案來解決萤捆。

雖然這些缺陷都有解決方案,但總覺得不夠完美,于是柯基于2018年12月提出了一種新的解決方案俗或。

2.5 柯基方案

柯基方案原文:Android 屏幕適配終結者 https://blankj.com/2018/12/18/android-adapt-screen-killer/

這個方案解決了今日頭條方案的缺陷市怎,同時無侵入性,靈活性高辛慰,非常nice区匠。

柯基方案的原理和頭條方案差不多,頭條基于dp帅腌,而柯基基于pt驰弄。所以柯基是修改DisplayMetrics#xdpi,而不是DisplayMetrics#density速客。

核心代碼:

//----------------------柯基方案  start -----------------------
//Android 屏幕適配終結者   https://blankj.com/2018/12/18/android-adapt-screen-killer/
fun adaptWidth(resources: Resources, designWidth: Int): Resources {
    val newXdpi = resources.displayMetrics.widthPixels * 72f / designWidth
    applyDisplayMetrics(resources, newXdpi)
    return resources
}

private fun applyDisplayMetrics(resources: Resources, newXdpi: Float) {
    resources.displayMetrics.xdpi = newXdpi
    App.getAppContext().resources.displayMetrics.xdpi = newXdpi
}
//----------------------柯基方案  end -----------------------

今日頭條方案:修改density=屏幕寬度/設計圖寬度戚篙。柯基方案:修改xdpi = 屏幕寬度*72/設計圖寬度溺职,為什么要乘以72岔擂?因為TypedValue里面算pt的時候除了72。這種方案最后在xml中寫pt其實和上面今日頭條方案的效果是一樣的浪耘。

簡單舉個例子乱灵,下面是2個TextView,它們會將屏幕占滿七冲,并且每個TextView的寬度是屏幕寬度的一半痛倚。

<?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="horizontal">

    <TextView
        android:layout_width="180pt"
        android:layout_height="180pt"
        android:background="@color/colorPrimary"
        android:text="按鈕1"
        android:textSize="14pt" />

    <TextView
        android:id="@+id/tv_toast"
        android:layout_width="180pt"
        android:layout_height="180pt"
        android:background="@color/colorAccent"
        android:text="按鈕2"
        android:textSize="14pt" />

</LinearLayout>

使用這種方案,屏幕適配基本上就沒什么大問題了澜躺。demo地址:https://github.com/xfhy/AllInOne/tree/master/app/src/main/java/com/xfhy/allinone/view/adaptation
Android屏幕適配深度解析視頻教程
https://www.bilibili.com/video/BV1uR4y1n7Gj?spm_id_from=333.999.0.0

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蝉稳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子苗踪,更是在濱河造成了極大的恐慌,老刑警劉巖削锰,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件通铲,死亡現(xiàn)場離奇詭異,居然都是意外死亡器贩,警方通過查閱死者的電腦和手機颅夺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛹稍,“玉大人吧黄,你說我怎么就攤上這事∷艚悖” “怎么了拗慨?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我赵抢,道長剧蹂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任烦却,我火速辦了婚禮宠叼,結果婚禮上,老公的妹妹穿的比我還像新娘其爵。我一直安慰自己冒冬,他們只是感情好,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布摩渺。 她就那樣靜靜地躺著简烤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪证逻。 梳的紋絲不亂的頭發(fā)上乐埠,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機與錄音囚企,去河邊找鬼丈咐。 笑死,一個胖子當著我的面吹牛龙宏,可吹牛的內(nèi)容都是我干的棵逊。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼银酗,長吁一口氣:“原來是場噩夢啊……” “哼辆影!你這毒婦竟也來了?” 一聲冷哼從身側響起黍特,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蛙讥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后灭衷,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體次慢,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年翔曲,在試婚紗的時候發(fā)現(xiàn)自己被綠了迫像。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡瞳遍,死狀恐怖闻妓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情掠械,我是刑警寧澤由缆,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布注祖,位于F島的核電站,受9級特大地震影響犁功,放射性物質發(fā)生泄漏氓轰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一浸卦、第九天 我趴在偏房一處隱蔽的房頂上張望署鸡。 院中可真熱鬧,春花似錦限嫌、人聲如沸靴庆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽炉抒。三九已至,卻和暖如春稚叹,著一層夾襖步出監(jiān)牢的瞬間焰薄,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工扒袖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留塞茅,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓季率,卻偏偏與公主長得像求泰,于是被迫代替她去往敵國和親腾节。 傳聞我的和親對象是個殘疾皇子贯被,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359

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