引言 ? ?
????一直很想寫文章,無奈自己才疏學淺,遲遲沒有想寫的沖動。而且相比較之下,大神們寫的博客已經很好了,認為自己沒必要再花費精力去寫文章。話雖如此,但隨著學習時間的推移,發現自己有時候記憶會產生碎片化。因此還是決定通過寫文章來縷清思路,也為自己的學習留下筆記,畢竟好記性不如爛筆頭。
1 為什么要進行Android屏幕適配?
官網解釋:
????Android 可在各種具有不同屏幕尺寸和密度的設備上運行。對于應用,Android 系統在不同設備中提供一致的開發環境,可以處理大多數工作,將每個應用的用戶界面調整為適應其顯示的屏幕。 同時,系統提供 API,可用于控制應用適用于特定屏幕尺寸和密度的 UI,以針對不同屏幕配置優化 UI 設計。 例如,您可能想要不同于手機 UI 的平板電腦 UI。
????雖然系統為使您的應用適用于不同的屏幕,會進行縮放和大小調整,但您應針對不同的屏幕尺寸和密度優化應用。 這樣可以最大程度優化所有設備上的用戶體驗,用戶會認為您的應用實際上是專為他們的設備而設計,而不是簡單地拉伸以適應其設備屏幕。
? ? ? ? ?通俗的講就是Android系統手機屏幕碎片化嚴重,為了提高用戶體驗,需要盡可能的對所有產商的手機屏幕進行適配,讓用戶在感官上和使用上都有好的體驗。
2 術語和概念
2.1 屏幕尺寸(size)
? ? 按屏幕對角測量的實際物理尺寸。單位:英寸,1英寸 = 2.54cm。
? ? Android 將所有實際屏幕尺寸分組為四種通用尺寸:小(small)、 正常(normal)、大(large)和超大(xlarge)。
? ? ? ? ?2018年市面上常見的手機屏幕尺寸有:5.0、5.2、5.5、6寸等等,它們所對應的通用尺寸為large。
2.2 屏幕密度dpi和ppi
????2.2.1 dpi
????屏幕物理區域中的像素量,通常稱為 dpi(dots per inch,每英寸點數)。
? ? Android 將所有屏幕密度分組為六種通用密度: 低(ldpi)、中(mdpi)、高(hdpi)、超高(xhdpi)、超超高(xxhdpi)和超超超高(xxxhdpi)。
? ? ? ? ?一般情況下,240×320的屏幕是低密度120dpi,即ldpi;320×480的屏幕是中密度160dpi,即mdpi;480×800的屏幕是高密度240dpi,即hdpi;720×1280的屏幕是超高密度320dpi,即xhdpi;1080×1920的屏幕是超超高密度480dpi,即xxhdpi。
? ? 2.2.2 ppi
? ?屏幕實際區域中的像素量,每英寸所擁有的像素點的數量ppi(pixels per inch),ppi數值越高顯示越細膩。
? ?ppi可以通過圖2-3公式計算。
? ? ? ? ?如圖2-4,通過公式計算得出:sqrt(1080*1080+1920*1920) / 5 = 440ppi。
2.3 方向
????從用戶視角看屏幕的方向,即橫屏或豎屏,分別表示屏幕的縱橫比是寬還是高。請注意, 不僅不同的設備默認以不同的方向操作,而且方向在運行時可隨著用戶旋轉設備而改變。
2.4 分辨率
????屏幕上物理像素(px)的總數。
? ? 例如:1080*1920px的屏幕,屏幕橫向有1080個像素,屏幕縱向有1920個像素。像素總數為?1080*1920 =?2073600?個像素。
2.5 密度無關像素 (dp)
????在定義UI布局時應使用的虛擬像素單位,用于以密度無關方式表示布局維度或位置。
????密度無關像素等于 160 dpi 屏幕上的一個物理像素,這是 系統為“中”密度屏幕假設的基線密度。在運行時,系統根據使用中屏幕的實際密度按需要以透明方式處理 dp 單位的任何縮放 。
????dp 單位轉換為屏幕像素很簡單:px = dp * (dpi / 160)。
????例如,在 240 dpi 屏幕上,1 dp 等于 1.5 物理像素。在定義應用的 UI 時應始終使用 dp 單位 ,以確保在不同密度的屏幕上正常顯示 UI。
2.6 sp
? ? 和dp類似,在定義字體大小時使用sp,字體可根據屏幕進行縮放。
3 系統如何顯示最佳效果
1. 系統使用適當的備用資源
????根據當前屏幕的尺寸和密度,系統將使用您的應用中提供的任何尺寸和密度特定資源。例如,如果設備有高密度屏幕,并且應用請求可繪制對象資源,系統將查找與設備配置最匹配的可繪制對象資源目錄。根據可用的其他備用資源,包含?hdpi?限定符(例如?drawable-hdpi/)的資源目錄可能是最佳匹配項,因此系統將使用此目錄中的可繪制對象資源。
2. 向上或向下擴展資源
????如果沒有匹配的資源,系統將使用默認資源,并按需要向上或向下擴展,以匹配當前的屏幕尺寸和密度。
????“默認”資源是指未標記配置限定符的資源。例如,drawable/?中的資源是默認可繪制資源。 系統假設默認資源設計用于基線屏幕尺寸和密度,即 正常屏幕尺寸和中密度。 因此,系統對于高密度屏幕向上擴展默認密度資源,對于低密度屏幕向下擴展。
????當系統查找密度特定的資源但在密度特定目錄中未找到時,不一定會使用默認資源。系統在縮放時可能改用其他密度特定資源提供更好的效果。例如,查找低密度資源但該資源不可用時, 系統會縮小資源的高密度版本,因為系統可輕松以 0.5 為系數將高密度資源縮小至低密度資源,與以 0.75 為系數 縮小中密度資源相比,偽影更少。
????如需有關 Android 如何通過使配置限定符與設備配置匹配來選擇備用資源的更多信息,請參閱Android 如何查找最佳匹配資源
4 如何支持多種屏幕
1.在 XML 布局文件中指定尺寸時使用?wrap_content、match_parent 或dp。
????為 XML 布局文件中的視圖定義 android:layout_width?和 android:layout_height 時,使用?"wrap_content"、"match_parent"?或?dp?單位可確保在當前設備屏幕上為 視圖提供適當的尺寸。
????例如,layout_width="100dp"?的視圖在 中密度屏幕上測出寬度為 100 像素,在高密度屏幕上系統會將其擴展至 150 像素寬, 因此視圖在屏幕上占用的物理空間大約相同。
????類似地,您應選擇?sp(縮放獨立的像素)來定義文本大小。sp?縮放系數取決于用戶設置,系統會像處理?dp?一樣縮放大小。
2.不要在應用代碼中使用硬編碼的像素值(px)
? ??由于性能的原因和簡化代碼的需要,Android 系統使用像素作為表示尺寸或坐標值的標準單位。這意味著, 視圖的尺寸在代碼中始終以像素表示,但始終基于當前的屏幕密度。 例如,如果?myView.getWidth()?返回 10,則表示視圖在 當前屏幕上為 10 像素寬,但在更高密度的屏幕上,返回的值可能是 15。如果在應用代碼中使用像素值來處理預先未針對當前屏幕密度縮放的位圖,您可能需要縮放代碼中使用的像素值,以與未縮放的位圖來源匹配。
3.在 XML 布局文件中指定時使用weight。
? ?weight是權重屬性,通過設置它可以改變控件在布局中的權重。 Google官方推薦,當使用weight屬性時,將android:layout_width設為0dp,這樣就可以理解為自動占比。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
? ? android:layout_width="match_parent"
? ? android:layout_height="match_parent"
? ? android:orientation="horizontal">
<TextView
? ? ? ? android:id="@+id/textView"
? ? ? ? android:layout_width="0dp"
? ? ? ? android:layout_height="wrap_content"
? ? ? ? android:layout_weight="1"
? ? ? ? android:text="TextView" />
</TextView>
<TextView
? ? ? ? android:id="@+id/textView2"
? ? ? ? android:layout_width="0dp"
? ? ? ? android:layout_height="wrap_content"
? ? ? ? android:layout_weight="2"
? ? ? ? android:text="TextView" />
</TextView>
</LinearLayout>
? ? ? ? 在ConstraintLayout中,spread?和?spread inside?Chain 鏈可以設置每個組件的 weight 權重,這跟?LinearLayout?的?weight?權重設置很像,它可以設置為小數。
4.為不同屏幕密度提供替代位圖可繪制對象(備用資源)
???如果需要精確控制應用在不同 屏幕配置上的外觀,請在配置特定的資源目錄中調整您的布局和位圖可繪制對象。
????例如,考慮要顯示在中密度和高密度屏幕上的圖標。只需創建兩種不同大小的圖標 (例如中密度使用 100x100,高密度使用 150x150),然后使用適當的限定符以適當的方向放置兩個變體:
res/drawable-mdpi/icon.png???//for medium-density screens
res/drawable-hdpi/icon.png???//for high-density screens
? ? 也有的情況是會在根據sw最小寬度限定符去寫很多的dimens,例如一種非常好用的Android屏幕適配
????但是dimens多了會影響啟動速度,因此dimens的使用是根據實際需求來寫,并不是越詳細越好。這是我反編譯一個軟件得到的,僅供參考:
????常見的限定符有尺寸限定符,最小寬度限定符,屏幕方向限定符。例如,以下應用資源目錄為不同屏幕尺寸對象提供不同的布局設計。
res/layout/my_layout.xml? ? ? ? ? ? ? // layout for normal screen size ("default")
res/layout-large/my_layout.xml? ? ? ? // layout for large screen size
res/layout-xlarge/my_layout.xml? ? ? // layout for extra-large screen size
res/layout-xlarge-land/my_layout.xml? // layout for extra-large in landscape orientation
? ? 如需了解如何使用備用資源的更多信息以及配置限定符的完整列表(不只是屏幕配置),請參閱提供備用資源。
? ? 我們還可以根據需要使用配置限定符,常見的配置限定符如圖3-2所示:
? ? 總結為兩點:1.使用備用資源
? ? ? ? ? ? ? ? ? ? ? ? ? 2.使用配置限定符
? ??5. 使用.9圖
? ??如果您在可能改變尺寸的組件上使用簡單圖像,您很快會發現效果有些差強人意,因為運行組件會均勻地拉伸或縮小您的圖像。 解決方案是使用.9圖,這種特殊格式的 PNG 文件會指示哪些區域可以拉伸,哪些區域不可以拉伸。
5. 常用的工具類
5.1 單位轉換
public class DensityUtil {
? ? public static int dp2px(Context context, float dpValue) {
? ? ? ? final float scale = context.getResources().getDisplayMetrics().density;
? ? ? ? return (int) (dpValue * scale + 0.5f);
? ? }
? ? public static int px2dp(Context context, float pxValue) {
? ? ? ? final float scale = context.getResources().getDisplayMetrics().density;
? ? ? ? return (int) (pxValue / scale + 0.5f);
? ? }
? ? public static int px2sp(Context context, float pxValue) {
? ? ? ? final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
? ? ? ? return (int) (pxValue / fontScale + 0.5f);
? ? }
? ? public static int sp2px(Context context, float spValue) {
? ? ? ? final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
? ? ? ? return (int) (spValue * fontScale + 0.5f);
? ? }
}
5.2 獲取屏幕尺寸和密度
public class GetScreenParameter {
? ? //方法一:已過時,可使用,但不建議使用?
? ? public static void getResolution1(Context mContext) {?
? ? ? ? Display mDisplay = ((Activity) mContext).getWindowManager()?
? ? ? ? .getDefaultDisplay();?
? ? ? ? int W = mDisplay.getWidth();?
? ? ? ? int H = mDisplay.getHeight();?
? ? }?
? ? //方法二:通過getWindowManager來獲取屏幕尺寸的?
? ? public static void getResolution2(Context mContext) {?
? ? ? ? DisplayMetrics mDisplayMetrics = new DisplayMetrics();?
? ? ? ? ((Activity) mContext).getWindowManager().getDefaultDisplay() ?.getMetrics(mDisplayMetrics);?
? ? ? ? int W = mDisplayMetrics.widthPixels;?
? ? ? ? int H = mDisplayMetrics.heightPixels;?
? ? ? ? float density = mDisplayMetrics.density; //獲取屏幕密度
? ? ? ? int densityDpi = mDisplayMetrics.densityDpi;//獲取dpi
? ? }?
? ? //方法三:通過getResources來獲取屏幕尺寸的,大部分用這個
? ? public static void getResolution3(Context mContext) {?
? ? ? ? DisplayMetrics mDisplayMetrics = new DisplayMetrics();?
? ? ? ? mDisplayMetrics = mContext.getResources().getDisplayMetrics();?
? ? ? ? int W = mDisplayMetrics.widthPixels;?
? ? ? ? int H = mDisplayMetrics.heightPixels;?
? ? ? ? float density = mDisplayMetrics.density;?
? ? ? ? int densityDpi = mDisplayMetrics.densityDpi;
? ? }?
}