支援不同的像素密度

Android 裝置不僅有不同的螢幕大小,包括手機、平板電腦、電視等,也都有不同像素大小的螢幕。一部裝置每英寸可能為 160 像素,另一部裝置在相同空間能容納 480 像素。如果您未考慮像素密度的這些變化,系統可能會縮放圖片,導致圖片模糊不清,或者圖片顯示的大小可能不正確。

本頁將說明如何使用各自解析度獨立的測量單位,並針對每個像素密度提供替代的點陣圖資源,藉此設計應用程式來支援不同的像素密度。

請觀看以下影片,一覽這些技巧。

如要進一步瞭解如何設計圖示素材資源,請參閱 Material Design 圖示規範

使用密度獨立像素

請避免使用像素來定義距離或大小,不同螢幕的像素密度不同,因此相同數量的像素會對應不同裝置上的不同實體尺寸,因此使用像素定義維度是問題並不容易。

圖片顯示兩部裝置範例,螢幕密度不同
圖 1:兩個相同大小的螢幕可能會有不同的像素數量。

為了在不同密度的螢幕上保留 UI 的可見大小,請將 UI 設計為密度獨立像素 (dp) 做為測量單位。一個 dp 是大小在中密度螢幕 (160 dpi,或「baseline」密度) 上約一個像素的虛擬像素單位。Android 會將這個值轉譯為每一個密度對應的實際像素數量。

請看圖 1 中的兩部裝置。寬度為 100 像素的檢視畫面,在裝置左側顯示的比例會大上許多。凡是定義為 100 dp 的檢視畫面,在兩個螢幕上都會顯示相同大小。

定義文字大小時,您可以改用可縮放像素 (sp) 做為單位。根據預設,sp 單位的大小與 dp 相同,但會根據使用者偏好的文字大小調整大小。請勿在版面配置大小時使用 sp。

舉例來說,如要指定兩個檢視畫面之間的間距,請使用 dp:

<Button android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/clickme"
    android:layout_marginTop="20dp" />

指定文字大小時,請使用 sp:

<TextView android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:textSize="20sp" />

將 dp 單位轉換為像素單位

在某些情況下,您需要以 dp 表示尺寸,然後將其轉換為像素。dp 單位與螢幕像素的轉換如下:

px = dp * (dpi / 160)

注意:請不要將這個方程式硬式編碼來計算像素。請改用 TypedValue.applyDimension(),這項功能會將多種類型的尺寸 (dp、sp 等) 轉換為像素。

假設有一個應用程式在使用者手指移動至少 16 像素時,辨識出捲動或快速滑過手勢。在基準螢幕上,使用者的手指必須移動 16 pixels / 160 dpi (等於 1/10 英寸 (或 2.5 公釐) 的位置),系統才能辨識手勢。

在高密度螢幕 (240 dpi) 的裝置上,使用者的手指必須移動 16 pixels / 240 dpi,這等於 1/15 英寸 (或 1.7 公釐)。距離越短,應用程式對使用者的靈敏度也就越高。

如要修正這個問題,請以 dp 為單位在程式碼中表示手勢門檻,然後將其轉換為實際像素。例如:

Kotlin

// The gesture threshold expressed in dp
private const val GESTURE_THRESHOLD_DP = 16.0f

private var gestureThreshold: Int = 0

// Convert the dps to pixels, based on density scale
gestureThreshold = TypedValue.applyDimension(
  COMPLEX_UNIT_DIP,
  GESTURE_THRESHOLD_DP + 0.5f,
  resources.displayMetrics).toInt()

// Use gestureThreshold as a distance in pixels...

Java

// The gesture threshold expressed in dp
private final float GESTURE_THRESHOLD_DP = 16.0f;

// Convert the dps to pixels, based on density scale
int gestureThreshold = (int) TypedValue.applyDimension(
  COMPLEX_UNIT_DIP,
  GESTURE_THRESHOLD_DP + 0.5f,
  getResources().getDisplayMetrics());

// Use gestureThreshold as a distance in pixels...

DisplayMetrics.density 欄位會指定用於根據目前像素密度將 dp 單位轉換為像素的縮放比例係數。在 medium 像素密度的螢幕上,DisplayMetrics.density 等於 1.0,而在高密度螢幕中則等於 1.5。在超高密度螢幕中則等於 2.0,在低密度螢幕中則等於 0.75。TypedValue.applyDimension() 會使用這個數字取得目前畫面的實際像素數量。

使用預先縮放的設定值

您可以使用 ViewConfiguration 類別存取 Android 系統的常用距離、速度和時間。舉例來說,您可以使用 getScaledTouchSlop() 取得架構做為捲動門檻使用的距離 (以像素為單位):

Kotlin

private val GESTURE_THRESHOLD_DP = ViewConfiguration.get(myContext).scaledTouchSlop

Java

private final int GESTURE_THRESHOLD_DP = ViewConfiguration.get(myContext).getScaledTouchSlop();

無論目前像素密度為何,只要以 getScaled 前置字元開頭的 ViewConfiguration 方法,系統就能傳回能夠正確顯示的像素值。

偏好向量圖形

如果不想建立多個特定密度的圖片版本,另一個方法是建立單一向量圖形。向量圖形使用 XML 建立圖片以定義路徑和顏色,而不是使用像素點陣圖。因此,向量圖形可以縮放到任何大小,而無需縮放構件,但通常最適合用於圖示等插圖,而非相片。

向量圖形通常會以 SVG (可擴充向量圖形) 檔案提供,但 Android 不支援此格式,因此您必須將 SVG 檔案轉換為 Android 的向量可繪項目格式。

您可以使用 Android Studio 的 Vector Asset Studio 將 SVG 轉換為向量可繪項目,如下所示:

  1. 在「Project」視窗中的「res」目錄上按一下滑鼠右鍵,然後依序選取「New」>「Vector Asset」
  2. 選取「本機檔案 (SVG、PSD)」
  3. 找到想要匯入的檔案,並進行調整。

    圖片:如何在 Android Studio 中匯入 SVG
    圖 2:使用 Android Studio 匯入 SVG。

    您可能會在「Asset Studio」視窗中發現一些錯誤,指出向量可繪項目不支援檔案的某些屬性。這不會阻止您匯入檔案,並忽略不支援的屬性。

  4. 按一下「Next」

  5. 在下一個畫面中,確認您要在專案中存放檔案的來源集,然後按一下「Finish」

    由於一個向量可繪項目可用於所有像素密度,因此這個檔案會位於預設的可繪項目目錄,如以下階層所示。您不需要使用密度專屬的目錄。

    res/
      drawable/
        ic_android_launcher.xml
    

如要進一步瞭解如何建立向量圖形,請參閱向量可繪項目說明文件。

提供替代點陣圖

為了針對具有不同像素密度的裝置提供良好的圖形品質,請在應用程式中提供每個點陣圖版本 (每個像素密度級別一個),且該解析度各不相同。否則,Android 必須縮放點陣圖,使其在每個畫面上佔用相同的可見空間,進而導致縮放失真 (例如模糊)。

顯示不同密度大小點陣圖相對大小的圖片
圖 3:不同密度級別中點陣圖的相對大小。

有幾種密度級別可供您的應用程式使用。表 1 說明可用的各種設定限定詞,以及這些限定詞適用的螢幕類型。

表 1. 適用於不同像素密度的設定限定詞

密度限定詞 說明
ldpi 適用於低密度 (ldpi) 螢幕的資源 (約 120 dpi)。
mdpi 適用於中密度 (mdpi) 螢幕的資源 (約 160 dpi)。此為基準密度。
hdpi 適用於高密度 (hdpi) 螢幕的資源 (約 240 dpi)。
xhdpi 適用於 extra-high 像素密度 (xhdpi) 螢幕的資源 (約為 320 dpi)。
xxhdpi 適用於 extra-extra-high 像素密度 (xxhdpi) 螢幕的資源 (約為 480 dpi)。
xxxhdpi 適用於 extra-extra-extra-high 像素密度 (xxxhdpi) 螢幕的資源 (約為 640 dpi)。
nodpi 適用於所有像素密度的資源,都屬於像素密度獨立資源。無論目前的螢幕密度為何,系統不會縮放以這個限定詞標記的資源。
tvdpi 適用於 mdpi 與 hdpi 之間的螢幕資源,約 213dpi。這並不是「主要的」像素密度分組,主要用於電視,且大多數應用程式均不需要使用此限定詞。若認為必須提供 tvdpi 資源,請以 1.33 * mdpi 為倍數將其調整大小。例如,mdpi 螢幕的 100x100 像素圖片在 tvdpi 螢幕上會是 133x133 像素。

如要為不同密度建立替代點陣圖可繪項目,請遵循 6 個主要密度之間的 3:4:6:8:12:16 縮放比例。舉例來說,如果您的點陣圖可繪項目為 medium 像素密度螢幕且為 48x48 像素,則大小為:

  • 36x36 (0.75x),適用於低密度 (ldpi)
  • 48x48 (1.0x 基準),適用於中密度 (mdpi)
  • 72x72 (1.5x),適用於高密度 (hdpi)
  • 96x96 (2.0x),適用於 extra-high 像素密度 (xhdpi)
  • 144x144 (3.0x),適用於 extra-extra-high 像素密度 (xxhdpi)
  • 192x192 (4.0x),適用於 extra-extra-extra-high 密度 (xxxhdpi)

將產生的圖片檔放在 res/ 底下的適當子目錄中:

res/
  drawable-xxxhdpi/
    awesome_image.png
  drawable-xxhdpi/
    awesome_image.png
  drawable-xhdpi/
    awesome_image.png
  drawable-hdpi/
    awesome_image.png
  drawable-mdpi/
    awesome_image.png

接著,當您參照 @drawable/awesomeimage 時,系統會根據螢幕的 dpi 選取適當的點陣圖。如未提供該密度的像素密度專屬資源,系統會找出下一個最相符的項目,並配合螢幕調整大小。

提示:如果您有不需要系統縮放的可繪製資源 (例如,當您在執行階段自行對圖片執行某些調整時),請將這些資源放在具有 nodpi 設定限定詞的目錄中。設有這項限定詞的資源表示與密度無關,系統不會縮放這些資源。

如要進一步瞭解其他設定限定詞,以及 Android 如何根據目前的螢幕設定選擇合適的資源,請參閱「應用程式資源總覽」。

將應用程式圖示放在 mipmap 目錄中

和其他點陣圖素材資源一樣,您需要提供特定密度的應用程式圖示。不過,部分應用程式啟動器顯示的應用程式圖示,遠大於裝置密度級別所稱的 25%。

舉例來說,如果裝置的密度級別為 xxhdpi,而您提供的應用程式圖示最大為 drawable-xxhdpi,應用程式啟動器會放大這個圖示,使圖示看起來較不清晰。

為避免這種情況,請將所有應用程式圖示放入 mipmap 目錄,而非 drawable 目錄。與 drawable 目錄不同,無論您是否已建構密度專屬 APK,所有 mipmap 目錄都會保留在 APK 中。如此一來,啟動器應用程式就能挑選要在主畫面上顯示的最佳解析度圖示。

res/
  mipmap-xxxhdpi/
    launcher_icon.png
  mipmap-xxhdpi/
    launcher_icon.png
  mipmap-xhdpi/
    launcher_icon.png
  mipmap-hdpi/
    launcher_icon.png
  mipmap-mdpi/
    launcher_icon.png

以上述 xxhdpi 裝置的範例來說,您可以在 mipmap-xxxhdpi 目錄中提供更高密度的啟動器圖示。

如需圖示設計指南,請參閱「系統圖示」。

如要瞭解如何建構應用程式圖示,請參閱「使用 Image Asset Studio 建立應用程式圖示」一文。

罕見密度問題的建議做法

本節說明 Android 如何在不同像素密度上縮放點陣圖,以及如何進一步控制點陣圖在不同密度的繪製方式。除非應用程式會操控圖像,或在執行不同像素密度時發生問題,否則您可以忽略這個部分。

如果想進一步瞭解在執行階段操控圖像時可以支援多種密度,您需要瞭解系統如何協助確保點陣圖正確縮放。方法如下:

  1. 預先縮放資源,例如點陣圖可繪項目

    系統會根據目前螢幕的密度使用應用程式中的任何密度專屬資源。如果正確的密度沒有提供資源,系統會載入預設資源,並視需要進行調整。系統會假設預設資源 (來自不含設定限定詞的目錄) 是專為基準像素密度 (mdpi) 而設計,並將這些點陣圖重新調整為適合目前像素密度的適當大小。

    如果您要求了預先縮放資源的維度,系統會在縮放「之後」傳回代表該維度的值。舉例來說,針對 mdpi 螢幕設計為 50x50 像素的點陣圖,會在 hdpi 螢幕上縮放為 75x75 像素 (如果沒有 hdpi 額外資源),且系統會回報大小。

    在某些情況下,您可能不希望 Android 預先調整資源。如要避免預先縮放,最簡單的方式就是將資源放在具有 nodpi 設定限定詞的資源目錄中。例如:

    res/drawable-nodpi/icon.png

    當系統使用這個資料夾中的 icon.png 點陣圖時,不會根據目前裝置的密度縮放該點陣圖。

  2. 自動調整像素尺寸和座標

    如要停用預先縮放的尺寸和圖片,請在資訊清單中將 android:anyDensity 設為 "false",或者以程式輔助方式將 Bitmap 設為 "false"inScaled在此情況下,系統會在繪圖時自動調整任何絕對像素座標和像素維度值。這樣做可確保像素定義的螢幕元素,與以基準像素密度 (mdpi) 顯示的實體大小大致相同。系統會以透明公開的方式對應用程式處理縮放作業,並將經過調整的像素尺寸回報給應用程式,而非實際像素尺寸。

    舉例來說,假設裝置搭載 480x800 的 WVGA 高密度螢幕,且大小與傳統 HVGA 螢幕相同,但它執行的應用程式已停用預先縮放功能。在本例中,當應用程式查詢螢幕尺寸時,系統會回報應用程式為 320x533,也就是像素密度的約略 mdpi 轉譯。

    接著,當應用程式執行繪圖作業時,例如將矩形從 (10,10) 失效到 (100, 100),系統就會以適當比例縮放座標,藉此轉換座標,並將區域 (15,15) 變成 (150, 150)。如果應用程式直接操控經過調整的點陣圖,這種差異可能會造成非預期行為,但這屬於合理的取捨,是確保最佳應用程式效能。如果您遇到這種情況,請參閱將 dp 單位轉換為像素單位

    一般情況下,您不會停用預先縮放功能。如要支援多螢幕,按照本頁說明的基本技巧操作。

如果應用程式會以其他方式操控點陣圖或直接與螢幕上的像素互動,您可能需要採取額外步驟來支援不同的像素密度。舉例來說,如果您透過計算手指交叉的像素數量來回應觸控手勢,就必須使用適當的密度獨立像素值 (而非實際像素),但可以在 dp 和 px 值之間轉換

測試所有像素密度

請使用不同像素密度的多種裝置測試應用程式,確保 UI 可正確縮放。請盡可能在實體裝置上測試;如果您無法存取適用於所有不同像素密度的實體裝置,請使用 Android Emulator

如果您想在實體裝置上測試,但不想購買裝置,可以使用 Firebase Test Lab 存取 Google 資料中心內的裝置。