支援不同的像素密度

Android 裝置不僅有各種螢幕大小 (手機、平板電腦、電視等),且螢幕像素尺寸不同。一部裝置的每英寸像素數可能為 160 像素,而另一部裝置的像素在相同空間中則符合 480 像素。如果您未考慮像素密度的這些變化,系統可能會縮放圖片,導致圖片模糊不清,或者顯示的圖片尺寸不正確。

本頁將說明如何使用與解析度不同的測量單位,以及為每個像素密度提供替代點陣圖資源,設計應用程式來支援不同的像素密度。

如需這些技巧的簡介,請觀看以下影片。

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

使用密度獨立像素

避免使用像素來定義距離或大小。由於不同螢幕的像素密度不同,因此相同數量的像素會對應至不同裝置上的不同實體大小,因此使用像素定義尺寸會發生問題。

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

如要在不同密度的螢幕上保留 UI 的可見大小,請使用密度獨立像素 (dp) 做為測量單位來設計使用者介面。1 dp 是虛擬像素單位,約等於 160 dpi (160 dpi) 的虛擬像素密度。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 單位轉換為像素的縮放比例係數。在中密度螢幕中,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();

無論目前的像素密度為何,在 ViewConfiguration 中以 getScaled 前置字串開頭的方法都能保證傳回可正確顯示的值 (以像素為單位)。

偏好向量圖形

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

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

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

  1. 在「Project」視窗中的「res」目錄按一下滑鼠右鍵,然後依序選取「New」>「Vector Asset」
  2. 選取「Local file (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 適用於超高密度 (xhdpi) 螢幕的資源 (約 320 dpi)。
xxhdpi 適用於超超高密度 (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 縮放比例調整比例。舉例來說,如果您的點陣圖可繪項目是 48x48 像素,適用於中密度螢幕,則大小為:

  • 36x36 (0.75x),適用於低密度 (ldpi)
  • 48x48 (1.0x 基準),適用於中密度 (mdpi)
  • 72x72 (1.5 倍),適用於高密度 (hdpi)
  • 96x96 (2.0x),適用於 extra-high 像素密度 (xhdpi)
  • 144x144 (3.0x),適用於超高密度 (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 目錄中

與其他點陣圖素材資源一樣,您必須提供應用程式圖示的密度專屬版本。不過,部分應用程式啟動器會顯示應用程式圖示,遠多於裝置密度級別所呼叫的幅度。

舉例來說,如果裝置的密度級別為 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",以程式輔助方式將 inScaled 設為 "false",以程式輔助方式設定 Bitmap。在這種情況下,系統會在繪製時自動調整任何絕對像素座標和像素維度值。這樣做可確保以像素定義的螢幕元素仍能以基準像素密度 (mdpi) 正常顯示的方式顯示大小。系統會對應用程式以透明化的方式處理這項縮放作業,並向應用程式回報經過調整的像素尺寸,而非實體像素尺寸。

    舉例來說,假設裝置搭載 480x800 的 WVGA 高密度螢幕,大小與傳統 HVGA 螢幕相同,但是正在執行的應用程式已停用預先縮放功能。在此範例中,當系統查詢螢幕尺寸並回報 320x533 (亦即像素密度的近似 mdpi 轉譯) 時,會「符合」應用程式的需求。

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

    一般而言,您不會停用預先縮放功能。如要支援多螢幕,最佳做法是遵循本頁說明的基本技巧。

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

針對所有像素密度進行測試

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

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