我們?cè)陧?xiàng)目中,處理頁(yè)面布局和繪制的時(shí)候,常常會(huì)考慮如何更好的做到多種機(jī)型和屏幕的最大程度的支持灶似,亦即屏幕適配的問(wèn)題。我為何以“Android應(yīng)用如何支持多屏幕”為題瑞你,而非用“Android應(yīng)用如何做屏幕適配”為題呢酪惭?這是因?yàn)樵诠俜轿臋n中,便是以“支持多種屏幕”為題來(lái)講解的者甲,為了統(tǒng)一吧春感,固以此為題。
一虏缸、支持多種屏幕的原因
1鲫懒、Android 可在各種具有不同屏幕尺寸和密度的設(shè)備上運(yùn)行。
2刽辙、基于國(guó)情和現(xiàn)狀窥岩,各種屏幕尺寸和密度的設(shè)備機(jī)型各不相同。
3宰缤、對(duì)于應(yīng)用颂翼,Android 系統(tǒng)在不同設(shè)備中提供一致的開(kāi)發(fā)環(huán)境,可以處理大多數(shù)工作慨灭,將每個(gè)應(yīng)用的用戶界面調(diào)整為適應(yīng)其顯示的屏幕朦乏。
4、為了提高開(kāi)發(fā)效率氧骤,避免重復(fù)開(kāi)發(fā)項(xiàng)目呻疹。
二、術(shù)語(yǔ)和概念
1筹陵、屏幕尺寸:
按屏幕對(duì)角測(cè)量的實(shí)際物理尺寸刽锤。
為簡(jiǎn)便起見(jiàn)镊尺,Android 將所有實(shí)際屏幕尺寸分組為四種通用尺寸:小、正常姑蓝、大和超大鹅心。
2吕粗、屏幕密度:
屏幕物理區(qū)域中的像素量纺荧;通常稱為 dpi(每英寸 點(diǎn)數(shù))。例如颅筋,與“正持嫦荆”或“高”密度屏幕相比,“低”密度屏幕在給定物理區(qū)域的像素較少议泵。
為簡(jiǎn)便起見(jiàn)占贫,Android 將所有屏幕密度分組為六種通用密度: 低、中先口、高型奥、超高、超超高和超超超高碉京。
3厢汹、方向:
從用戶視角看屏幕的方向,即橫屏還是豎屏谐宙,分別表示屏幕的縱橫比是寬還是高烫葬。
請(qǐng)注意,不僅不同的設(shè)備默認(rèn)以不同的方向操作凡蜻,而且方向在運(yùn)行時(shí)可隨著用戶旋轉(zhuǎn)設(shè)備而改變搭综。
4、分辨率:
屏幕上物理像素的總數(shù)划栓。添加對(duì)多種屏幕的支持時(shí)兑巾, 應(yīng)用不會(huì)直接使用分辨率;而只應(yīng)關(guān)注通用尺寸和密度組指定的屏幕 尺寸及密度忠荞。
5蒋歌、密度無(wú)關(guān)像素 (dp):
在定義 UI 布局時(shí)應(yīng)使用的虛擬像素單位,用于以密度無(wú)關(guān)方式表示布局維度 或位置钻洒。
密度無(wú)關(guān)像素等于 160 dpi 屏幕上的一個(gè)物理像素奋姿,這是系統(tǒng)為“中”密度屏幕假設(shè)的基線密度。在運(yùn)行時(shí)素标,系統(tǒng)根據(jù)使用中屏幕的實(shí)際密度按需要以透明方式處理 dp 單位的任何縮放 称诗。dp 單位轉(zhuǎn)換為屏幕像素很簡(jiǎn)單: px = dp * (dpi / 160)。 例如头遭,在 240 dpi 屏幕上寓免,1 dp 等于 1.5 物理像素癣诱。在定義應(yīng)用的 UI 時(shí)應(yīng)始終使用 dp 單位 ,以確保在不同密度的屏幕上正常顯示 UI袜香。
三撕予、支持的屏幕范圍
從 Android 1.6(API 級(jí)別 4)開(kāi)始,Android 支持多種屏幕尺寸和密度蜈首,反映設(shè)備可能具有的多種不同屏幕配置实抡。 您可以使用 Android 系統(tǒng)的功能優(yōu)化應(yīng)用在各種屏幕配置下的用戶界面 ,確保應(yīng)用不僅正常渲染欢策,而且在每個(gè)屏幕上提供 最佳的用戶體驗(yàn)吆寨。
具體內(nèi)容見(jiàn)下表:
四、如何支持多種屏幕
支持多種屏幕的目標(biāo)是創(chuàng)建一款在 Android 系統(tǒng)支持的通用屏幕尺寸上都可以 正常運(yùn)行且顯示良好的應(yīng)用踩寇。
概括:
1啄清、在 XML 布局文件中指定尺寸時(shí)使用 wrap_content、match_parent 或 dp 單位 俺孙。
2辣卒、不要在應(yīng)用代碼中使用硬編碼的像素值
3、不要使用 AbsoluteLayout(已棄用)
4睛榄、為不同屏幕密度提供替代位圖可繪制對(duì)象
詳解:
1荣茫、對(duì)布局尺寸使用 wrap_content、match_parent 或 dp 單位
為 XML 布局文件中的視圖定義 android:layout_width 和 android:layout_height 時(shí)懈费,使用"wrap_content"计露、"match_parent" 或 dp單位可確保在當(dāng)前設(shè)備屏幕上為視圖提供適當(dāng)?shù)某叽纭?/p>
例如,layout_width="100dp"
的視圖在中密度屏幕上測(cè)出寬度為 100 像素憎乙,在高密度屏幕上系統(tǒng)會(huì)將其擴(kuò)展至 150 像素寬票罐, 因此視圖在屏幕上占用的物理空間大約相同。
類(lèi)似地泞边,您應(yīng)選擇 sp(縮放獨(dú)立的像素)來(lái)定義文本 大小该押。sp 縮放系數(shù)取決于用戶設(shè)置,系統(tǒng)會(huì)像處理 dp
一樣縮放大小阵谚。
2蚕礼、不要在應(yīng)用代碼中使用硬編碼的像素值
由于性能的原因和簡(jiǎn)化代碼的需要,Android 系統(tǒng)使用像素作為 表示尺寸或坐標(biāo)值的標(biāo)準(zhǔn)單位梢什。這意味著奠蹬,視圖的尺寸在代碼中始終以像素表示,但始終基于當(dāng)前的屏幕密度嗡午。
例如囤躁,如果 myView.getWidth()
返回 10,則表示視圖在 當(dāng)前屏幕上為 10 像素寬,但在更高密度的屏幕上狸演,返回的值可能是 15言蛇。如果 在應(yīng)用代碼中使用像素值來(lái)處理預(yù)先未針對(duì) 當(dāng)前屏幕密度縮放的位圖,您可能需要縮放代碼中使用的像素值宵距,以與未縮放的位圖來(lái)源匹配腊尚。
如果應(yīng)用在運(yùn)行時(shí)操作位圖或處理像素值,請(qǐng)參閱有關(guān)其他密度注意事項(xiàng)的一節(jié)
3满哪、不要使用 AbsoluteLayout
與其他布局小工具不同婿斥,AbsoluteLayout會(huì)強(qiáng)制 使用固定位置放置其子視圖,很容易導(dǎo)致 在不同顯示屏上顯示效果不好的用戶界面翩瓜。因此受扳,AbsoluteLayout在 Android 1.5(API 級(jí)別 3)上便已棄用。
您應(yīng)改用 RelativeLayout兔跌,它會(huì)使用相對(duì)定位來(lái)放置其子視圖。例如峡蟋,您可以指定按鈕小部件顯示在文本小工具的“右邊”坟桅。
4、使用尺寸和密度特定資源
雖然系統(tǒng)會(huì)根據(jù)當(dāng)前屏幕配置擴(kuò)展布局蕊蝗,但您在不同的屏幕尺寸上可能要調(diào)整 UI仅乓,以及提供針對(duì)不同密度優(yōu)化的可繪制對(duì)象。
如果需要精確控制應(yīng)用在不同 屏幕配置上的外觀蓬戚,請(qǐng)?jiān)谂渲锰囟ǖ?資源目錄中調(diào)整您的布局和位圖可繪制對(duì)象夸楣。例如,考慮要顯示在中密度和高密度屏幕上的圖標(biāo)子漩。只需創(chuàng)建兩種不同大小的圖標(biāo) (例如中密度使用 100x100豫喧,高密度使用 150x150),然后使用適當(dāng)?shù)南薅ǚ?以適當(dāng)?shù)姆较蚍胖脙蓚€(gè)變體:
res/drawable-mdpi/icon.png //for medium-density screens
res/drawable-hdpi/icon.png //for high-density screens
如需了解有效配置限定符的更多信息幢泼,請(qǐng)參閱使用配置限定符紧显。
額外的
5、在清單中顯式聲明您的應(yīng)用支持哪些屏幕尺寸
通過(guò)聲明您的應(yīng)用支持哪些屏幕尺寸缕棵,可確保只有 其屏幕受支持的設(shè)備才能下載您的應(yīng)用孵班。聲明對(duì)不同屏幕尺寸的支持也可影響系統(tǒng)如何在較大 屏幕上繪制您的應(yīng)用 — 特別是,您的應(yīng)用是否在屏幕兼容模式中運(yùn)行招驴。
要聲明應(yīng)用支持的屏幕尺寸篙程,應(yīng)在清單文件中包含 <supports-screens> 元素。
6别厘、使用可繪制對(duì)象
默認(rèn)情況下虱饿,Android 會(huì)縮放位圖可繪制對(duì)象(.png、.jpg 和 .gif 文件)和九宮格可繪制對(duì)象(.9.png 文件),使它們以適當(dāng)?shù)奈锢沓叽顼@示在每部設(shè)備上郭厌。
一些通用的按鈕和簡(jiǎn)單的圖標(biāo)袋倔,可以通過(guò)九宮格可繪制對(duì)象(.9.png 文件)或者通過(guò)xml(使用shape等)進(jìn)行繪制,則可在不同設(shè)備上進(jìn)行適當(dāng)?shù)目s放折柠,已達(dá)到無(wú)損效果宾娜。
注:您只需要為位圖文件(.png、.jpg 或 .gif)和九宮格文件 (.9.png) 提供密度特定的可繪制對(duì)象扇售。如果您使用 XML 文件定義形狀前塔、顏色或其他可繪制對(duì)象資源,應(yīng)該將一個(gè)副本放在默認(rèn)可繪制對(duì)象目錄中 (drawable/)承冰。
五华弓、屏幕常用適配總結(jié):
UI適配 — 常用方式:
- 1、單位適配:
- 在XML布局中盡量使用wrap_content困乒、match_parent 或 dp 單位 寂屏。
- 在代碼中不要使用硬編碼的像素值。
- 2娜搂、位圖資源適配:
- 提供不同屏幕密度的圖片資源迁霎,若考慮包體積,一般選用drawable-xxxhdpi
- 使用
.9
的圖片自適應(yīng)- svg矢量圖(可縮放)
- 3百宇、布局控件組件適配:
- 盡量使用約束布局
ConstraintLayout
- 使用 Fragment 將界面組件模塊化考廉,可復(fù)用
- 4、布局資源配置適配:即 創(chuàng)建備用布局
- 創(chuàng)建特定于屏幕尺寸的布局
- 使用最小寬度限定符携御,即sw限定符適配昌粤,系統(tǒng)識(shí)別屏幕可用高度和寬度的最小尺寸的dp值(其實(shí)就是手機(jī)的寬度值)
- 使用可用寬/高度限定符,如創(chuàng)建目錄:
layout-h600dp
等- 添加屏幕方向限定符:提供橫向(layout-land)或縱向(layout-port)的布局目錄
- 5啄刹、系統(tǒng)屬性配置適配:
- 使用預(yù)縮放的配置值:
使用ViewConfiguration
類(lèi)來(lái)獲取 Android 系統(tǒng)常用的距離涮坐、速度和時(shí)間。
例如鸵膏,可通過(guò)getScaledTouchSlop()
來(lái)獲取框架用作滾動(dòng)閾值的距離(以像素為單位)- 聲明最大屏幕尺寸:
<supports-screens>
Android 可自動(dòng)調(diào)整應(yīng)用以適應(yīng)大屏幕的方式膊升。
若有特殊需求,可通過(guò)在<supports-screens>
清單標(biāo)記中指定largestWidthLimitDp
屬性- 限制應(yīng)用僅支持平板電腦或電視:使用
<supports-screens>
清單元素來(lái)阻止手機(jī)設(shè)備下載您的應(yīng)用谭企。- 限制應(yīng)用僅支持特定尺寸和密度:使用
<compatible-screens>
清單元素來(lái)指定應(yīng)用支持的確切屏幕尺寸和密度廓译。【不推薦】
參考鏈接:
官方:設(shè)備兼容性
UI適配 — 修改density值
-
density
值:
代表 1dp 占當(dāng)前設(shè)備多少像素债查,即density = dpi / 160
px = density * dp;
density = dpi / 160;
px = dp * (dpi / 160);
-
ppi
:像素密度
指每英寸包含的物理像素的數(shù)量非区。
ppi 是設(shè)備在物理上的屬性值,取決于屏幕自身盹廷,計(jì)算公式如下所示征绸。被除數(shù)和除數(shù)都屬于客觀不可改變的值,所以 ppi 也是 無(wú)法修改 的,是硬件上一個(gè)客觀存在無(wú)法改變的值
dpi
: 屏幕像素密度
在 軟件概念 上單位距離對(duì)應(yīng)的像素總數(shù)管怠。
是手機(jī)在出廠時(shí)就會(huì)被寫(xiě)入系統(tǒng)配置文件中的一個(gè)屬性值淆衷,一般情況下 用戶是無(wú)法修改 該值的,但在開(kāi)發(fā)者模式中有修改該值的入口渤弛,是 軟件上可修改 的一個(gè)值祝拯。說(shuō)明:
Android系統(tǒng)渲染UI前,最終都是通過(guò)轉(zhuǎn)換成px來(lái)計(jì)算的她肯,通過(guò)調(diào)用TypedValue
的applyDimension
方法佳头。系統(tǒng)的轉(zhuǎn)換代碼如下:
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;
}
圖片中也會(huì)用到 density
:
@Nullable
public static Bitmap decodeResourceStream(@Nullable Resources res, @Nullable TypedValue value,
@Nullable InputStream is, @Nullable Rect pad, @Nullable Options opts) {
validate(opts);
if (opts == null) {
opts = new Options();
}
if (opts.inDensity == 0 && value != null) {
final int density = value.density;
if (density == TypedValue.DENSITY_DEFAULT) {
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
} else if (density != TypedValue.DENSITY_NONE) {
opts.inDensity = density;
}
}
// 此處用到了densityDpi
if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}
return decodeStream(is, pad, opts);
}
- 核心原理:
即今日頭條適配方案,該方案的核心在于:將不同尺寸分辨率手機(jī)的寬度dp值改成一個(gè)統(tǒng)一的值晴氨,從而解決屏幕適配的問(wèn)題康嘉。
density = 當(dāng)前設(shè)備屏幕總寬度(單位為像素)/ 設(shè)計(jì)圖總寬度(單位為 dp)
private static final float defaultWidth = 360;
private static float appDensity;
private static float appScaleDensity;
public static void setCustomDensity(Application application, Activity activity){
DisplayMetrics displayMetrics = application.getResources().getDisplayMetrics();
if (appDensity == 0){
appDensity = displayMetrics.density;
appScaleDensity = displayMetrics.scaledDensity;
//設(shè)置修改系統(tǒng)字體以后的監(jiān)聽(tīng)
application.registerComponentCallbacks(new ComponentCallbacks() {
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
if (newConfig != null && newConfig.fontScale >0){
appScaleDensity = application.getResources().getDisplayMetrics().scaledDensity;
}
}
@Override
public void onLowMemory() {
}
});
}
final float targetDensity = displayMetrics.widthPixels/defaultWidth;
final float targetScaleDensity = targetDensity *(appScaleDensity/appDensity);
final int targetDensityDpi = (int) (targetDensity * 160);
displayMetrics.density = targetDensity;
displayMetrics.scaledDensity = targetScaleDensity;
displayMetrics.densityDpi = targetDensityDpi;
final DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
activityDisplayMetrics.density = targetDensity;
activityDisplayMetrics.scaledDensity = targetScaleDensity;
activityDisplayMetrics.densityDpi = targetDensityDpi;
}
不足之處:
對(duì)于平板適配來(lái)說(shuō)不太友好,本質(zhì)上就是自動(dòng)拉伸控件的效果籽前。三方庫(kù):
AutoSize
參考鏈接:
一種極低成本的Android屏幕適配方式
Android 簡(jiǎn)單好用的屏幕適配方案
Android 屏幕適配