Hỗ trợ nhiều mật độ pixel

Thiết bị Android không chỉ có nhiều kích thước màn hình như điện thoại di động, máy tính bảng, TV, v.v. mà còn có màn hình với nhiều kích thước pixel. Một thiết bị có thể có 160 pixel/inch, trong khi một thiết bị khác lại đáp ứng 480 pixel trong cùng không gian đó. Nếu bạn không xem xét những biến thể này trong mật độ pixel, thì hệ thống có thể điều chỉnh tỷ lệ hình ảnh của bạn, khiến hình ảnh bị mờ hoặc hình ảnh có thể xuất hiện sai kích thước.

Trang này cho bạn biết cách thiết kế ứng dụng để hỗ trợ nhiều mật độ pixel bằng cách sử dụng các đơn vị đo lường độc lập về độ phân giải và cung cấp tài nguyên bitmap thay thế cho từng mật độ pixel.

Hãy xem video sau đây để biết thông tin tổng quan về các kỹ thuật này.

Để biết thêm thông tin về cách thiết kế thành phần biểu tượng, hãy xem Nguyên tắc về biểu tượng trong Material Design.

Sử dụng pixel không phụ thuộc vào mật độ

Tránh sử dụng pixel để xác định khoảng cách hoặc kích thước. Việc xác định kích thước bằng pixel là một vấn đề vì các màn hình khác nhau có mật độ pixel khác nhau, vì vậy, cùng một số lượng pixel tương ứng với các kích thước vật lý khác nhau trên các thiết bị khác nhau.

Hình ảnh cho thấy 2 màn hình thiết bị mẫu có mật độ khác nhau
Hình 1: Hai màn hình cùng kích thước có thể có số lượng pixel khác nhau.

Để duy trì kích thước hiển thị của giao diện người dùng trên các màn hình có mật độ khác nhau, hãy thiết kế giao diện người dùng bằng cách sử dụng pixel không phụ thuộc vào mật độ (dp) làm đơn vị đo lường. Một dp là đơn vị pixel ảo gần bằng với một pixel trên màn hình có độ phân giải trung bình (160 dpi hay mật độ "cơ sở"). Android chuyển giá trị này sang số lượng pixel thực thích hợp cho mỗi mật độ.

Xem xét hai thiết bị trong hình 1. Khung hiển thị rộng 100 pixel sẽ xuất hiện lớn hơn nhiều trên thiết bị ở bên trái. Một khung hiển thị được xác định là rộng 100 dp sẽ xuất hiện cùng kích thước trên cả hai màn hình.

Khi xác định kích thước văn bản, bạn có thể sử dụng đơn vị pixel có thể mở rộng (sp). Theo mặc định, đơn vị sp có cùng kích thước với dp nhưng sẽ thay đổi kích thước dựa trên kích thước văn bản mà người dùng ưu tiên. Không bao giờ sử dụng sp cho kích thước bố cục.

Ví dụ: để chỉ định khoảng cách giữa hai khung hiển thị, hãy sử dụng dp:

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

Khi chỉ định kích thước văn bản, hãy sử dụng sp:

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

Chuyển đổi đơn vị dp sang đơn vị pixel

Trong một số trường hợp, bạn cần biểu thị kích thước ở dp, sau đó chuyển đổi chúng thành pixel. Cách chuyển đổi đơn vị dp thành pixel màn hình như sau:

px = dp * (dpi / 160)

Lưu ý: Đừng mã hoá cứng phương trình này để tính pixel. Thay vào đó, hãy sử dụng TypedValue.applyDimension() để chuyển đổi nhiều loại kích thước (dp, sp, v.v.) thành pixel cho bạn.

Hãy tưởng tượng một ứng dụng trong đó cử chỉ cuộn hoặc hất được nhận dạng sau khi ngón tay của người dùng di chuyển ít nhất 16 pixel. Trên màn hình cơ sở, ngón tay của người dùng phải di chuyển 16 pixels / 160 dpi, tương đương với 1/10 inch (hoặc 2,5 mm), trước khi nhận dạng cử chỉ.

Trên thiết bị có màn hình với độ phân giải cao (240 dpi), ngón tay của người dùng phải di chuyển 16 pixels / 240 dpi, tương đương với 1/15 inch (1,7 mm). Khoảng cách ngắn hơn nhiều nên ứng dụng có vẻ nhạy cảm hơn với người dùng.

Để khắc phục vấn đề này, hãy biểu thị ngưỡng cử chỉ trong mã ở dạng dp, sau đó chuyển đổi ngưỡng đó thành pixel thực tế. Ví dụ:

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...

Trường DisplayMetrics.density chỉ định hệ số tỷ lệ dùng để chuyển đổi đơn vị dp sang pixel theo mật độ pixel hiện tại. Trên màn hình có độ phân giải trung bình, DisplayMetrics.density bằng 1,0 và trên màn hình có độ phân giải cao, giá trị này bằng 1,5. Trên màn hình có độ phân giải cực cao, giá trị này bằng 2,0 và trên màn hình có độ phân giải thấp, giá trị này bằng 0,75. TypedValue.applyDimension() sử dụng con số này để lấy số lượng pixel thực tế cho màn hình hiện tại.

Sử dụng giá trị cấu hình được chia tỷ lệ trước

Bạn có thể sử dụng lớp ViewConfiguration để truy cập vào các khoảng cách, tốc độ và thời gian thông thường mà hệ thống Android sử dụng. Ví dụ: bạn có thể lấy khoảng cách tính bằng pixel mà khung sử dụng làm ngưỡng cuộn bằng getScaledTouchSlop():

Kotlin

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

Java

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

Các phương thức trong ViewConfiguration bắt đầu bằng tiền tố getScaled được đảm bảo trả về một giá trị tính bằng pixel hiển thị chính xác bất kể mật độ pixel hiện tại.

Ưu tiên đồ hoạ vectơ

Một cách thay thế để tạo nhiều phiên bản hình ảnh cụ thể theo mật độ là chỉ tạo một đồ hoạ vectơ. Đồ hoạ vectơ tạo hình ảnh bằng XML để xác định đường dẫn và màu sắc, thay vì sử dụng bitmap điểm ảnh. Do đó, đồ hoạ vectơ có thể điều chỉnh tỷ lệ theo bất kỳ kích thước nào mà không cần điều chỉnh tỷ lệ cấu phần phần mềm, mặc dù chúng thường phù hợp nhất với hình minh hoạ như biểu tượng, chứ không phải ảnh chụp.

Đồ hoạ vectơ thường được cung cấp dưới dạng tệp SVG (Đồ hoạ vectơ có thể mở rộng), nhưng Android không hỗ trợ định dạng này. Vì vậy, bạn phải chuyển đổi tệp SVG sang định dạng vectơ vẽ được của Android.

Bạn có thể chuyển đổi SVG thành một vectơ vẽ được bằng cách sử dụng Vector Asset Studio của Android Studio như sau:

  1. Trong cửa sổ Dự án, hãy nhấp chuột phải vào thư mục res rồi chọn Mới > Thành phần vectơ.
  2. Chọn Tệp cục bộ (SVG, PSD).
  3. Tìm tệp bạn muốn nhập và điều chỉnh.

    Hình ảnh minh hoạ cách nhập tệp SVG (Đồ hoạ vectơ có thể mở rộng) trong Android Studio
    Hình 2: Nhập SVG bằng Android Studio.

    Bạn có thể nhận thấy một số lỗi trong cửa sổ Asset Studio cho biết các vectơ vẽ được không hỗ trợ một số thuộc tính của tệp. Điều này không ngăn bạn nhập tệp. Các thuộc tính không được hỗ trợ sẽ bị bỏ qua.

  4. Nhấp vào Next (Tiếp theo).

  5. Trên màn hình tiếp theo, hãy xác nhận nhóm tài nguyên mà bạn muốn tệp trong dự án rồi nhấp vào Finish (Hoàn tất).

    Vì một vectơ vẽ được có thể được dùng trên tất cả mật độ pixel, nên tệp này sẽ nằm trong thư mục có thể vẽ mặc định của bạn, như được hiển thị trong hệ phân cấp sau. Bạn không cần phải sử dụng thư mục cụ thể theo mật độ.

    res/
      drawable/
        ic_android_launcher.xml
    

Để biết thêm thông tin về cách tạo đồ hoạ vectơ, hãy đọc tài liệu về vectơ vẽ được.

Cung cấp bitmap thay thế

Để cung cấp chất lượng đồ hoạ tốt trên các thiết bị có mật độ pixel khác nhau, hãy cung cấp nhiều phiên bản của mỗi bitmap trong ứng dụng của bạn (một phiên bản cho mỗi nhóm mật độ) ở độ phân giải tương ứng. Nếu không, Android phải điều chỉnh bitmap theo tỷ lệ để bitmap chiếm cùng không gian hiển thị trên mỗi màn hình, dẫn đến việc điều chỉnh tỷ lệ các cấu phần phần mềm, chẳng hạn như làm mờ.

Hình ảnh cho thấy kích thước tương đối cho các bitmap ở nhiều kích thước mật độ
Hình 3: Kích thước tương đối của bitmap trong các bộ chứa mật độ khác nhau.

Bạn có thể dùng nhiều nhóm mật độ trong ứng dụng của mình. Bảng 1 mô tả các bộ hạn định cấu hình khác nhau hiện có và những loại màn hình được áp dụng.

Bảng 1. Bộ hạn định cấu hình cho các mật độ pixel khác nhau.

Bộ định tính mật độ Nội dung mô tả
ldpi Tài nguyên dành cho màn hình có độ phân giải thấp (ldpi) (~120 dpi).
mdpi Tài nguyên dành cho màn hình có độ phân giải trung bình (mdpi) (~160 dpi). Đây là mật độ cơ sở.
hdpi Tài nguyên cho màn hình có độ phân giải cao (hdpi) (~240 dpi).
xhdpi Tài nguyên cho màn hình có độ phân giải cực cao (xhdpi) (~320 dpi).
xxhdpi Tài nguyên cho màn hình có độ phân giải cực cực cao (xxhdpi) (~480 dpi).
xxxhdpi Tài nguyên cho mật độ cực cực cực cao (xxxhdpi) sử dụng (~640 dpi).
nodpi Tài nguyên cho tất cả mật độ. Đây là những tài nguyên không phụ thuộc vào mật độ. Hệ thống không mở rộng tỷ lệ tài nguyên được gắn thẻ bằng bộ hạn định này, bất kể mật độ màn hình hiện tại.
tvdpi Tài nguyên cho màn hình ở đâu đó giữa mdpi và hdpi; khoảng khoảng 213 dpi. Đây không được coi là nhóm mật độ "chính". Màn hình này chủ yếu dành cho TV và hầu hết các ứng dụng không cần đến. Chỉ cần cung cấp tài nguyên mdpi và hdpi là đủ cho hầu hết các ứng dụng, đồng thời hệ thống sẽ điều chỉnh màn hình theo tỷ lệ cho phù hợp. Nếu bạn thấy cần cung cấp tài nguyên tvdpi, hãy định kích thước tài nguyên đó ở hệ số 1,33 * mdpi. Ví dụ: hình ảnh 100 x 100 pixel cho màn hình mdpi là 133 x 133 pixel cho tvdpi.

Để tạo các bitmap có thể vẽ thay thế cho nhiều mật độ, hãy tuân theo tỷ lệ điều chỉnh theo tỷ lệ 3:4:6:8:12:16 giữa 6 mật độ chính. Ví dụ: nếu bạn có đối tượng có thể vẽ bitmap có kích thước 48 x 48 pixel cho màn hình có mật độ điểm ảnh trung bình, thì các kích thước sẽ là:

  • 36x36 (0,75x) với mật độ thấp (ldpi)
  • 48x48 (1,0x đường cơ sở) cho mật độ trung bình (mdpi)
  • 72 x 72 (1,5x) cho mật độ cao (hdpi)
  • 96 x 96 (2,0x) cho mật độ cực cao (xhdpi)
  • 144 x 144 (3,0 x) cho mật độ cực cực cao (xxhdpi)
  • 192 x 192 (4 x 4) cho mật độ cực cực cực cao (xxxhdpi)

Đặt các tệp hình ảnh đã tạo vào thư mục con thích hợp trong 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

Sau đó, bất cứ khi nào bạn tham chiếu @drawable/awesomeimage, hệ thống sẽ chọn bitmap phù hợp dựa trên dpi của màn hình. Nếu bạn không cung cấp tài nguyên cụ thể theo mật độ cho mật độ đó, hệ thống sẽ xác định vị trí phù hợp nhất tiếp theo và điều chỉnh tỷ lệ cho vừa với màn hình.

Mẹo: Nếu bạn có các tài nguyên có thể vẽ mà bạn không muốn hệ thống mở rộng quy mô, chẳng hạn như khi bạn tự thực hiện một số điều chỉnh đối với hình ảnh trong thời gian chạy, hãy đặt các tài nguyên đó vào một thư mục có bộ hạn định cấu hình nodpi. Các tài nguyên có bộ hạn định này được coi là không phụ thuộc vào mật độ và hệ thống sẽ không chuyển tỷ lệ các tài nguyên đó.

Để biết thêm thông tin về các bộ hạn định cấu hình khác và cách Android chọn tài nguyên thích hợp cho cấu hình màn hình hiện tại, hãy xem phần Tổng quan về tài nguyên ứng dụng.

Đặt biểu tượng ứng dụng trong thư mục mipmap

Giống như các thành phần bitmap khác, bạn cần cung cấp các phiên bản biểu tượng ứng dụng có mật độ cụ thể. Tuy nhiên, một số trình chạy ứng dụng cho thấy biểu tượng ứng dụng của bạn lớn hơn 25% so với biểu tượng mà bộ chứa mật độ của thiết bị yêu cầu.

Ví dụ: nếu bộ chứa mật độ của thiết bị là xxhdpi và biểu tượng ứng dụng lớn nhất mà bạn cung cấp nằm trong drawable-xxhdpi, thì trình chạy ứng dụng sẽ tăng kích thước biểu tượng này để biểu tượng trông kém sắc nét hơn.

Để tránh tình trạng này, hãy đặt tất cả các biểu tượng ứng dụng của bạn vào thư mục mipmap thay vì thư mục drawable. Không giống như thư mục drawable, tất cả thư mục mipmap đều được giữ lại trong tệp APK, ngay cả khi bạn tạo các tệp APK có mật độ cụ thể. Điều này cho phép các ứng dụng trình chạy chọn biểu tượng có độ phân giải tốt nhất để hiển thị trên màn hình chính.

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

Trong ví dụ trước về thiết bị xxhdpi, bạn có thể cung cấp biểu tượng trình chạy có độ phân giải cao hơn trong thư mục mipmap-xxxhdpi.

Để biết nguyên tắc thiết kế biểu tượng, hãy xem phần Biểu tượng hệ thống.

Để được trợ giúp tạo biểu tượng ứng dụng, hãy xem phần Tạo biểu tượng ứng dụng bằng Image Asset Studio.

Lời khuyên về các vấn đề về mật độ không phổ biến

Phần này mô tả cách Android thực hiện việc điều chỉnh tỷ lệ cho bitmap trên các mật độ pixel khác nhau và cách bạn có thể kiểm soát thêm cách vẽ bitmap trên các mật độ khác nhau. Trừ phi ứng dụng của bạn thao tác với đồ hoạ hoặc bạn gặp sự cố khi chạy trên các mật độ pixel khác nhau, bạn có thể bỏ qua phần này.

Để hiểu rõ hơn về cách bạn có thể hỗ trợ nhiều mật độ khi thao tác với đồ hoạ trong thời gian chạy, bạn cần biết cách hệ thống giúp đảm bảo tỷ lệ phù hợp cho bitmap. Việc này được thực hiện theo các cách sau:

  1. Điều chỉnh trước quy mô tài nguyên, chẳng hạn như đối tượng bitmap có thể vẽ

    Dựa trên mật độ của màn hình hiện tại, hệ thống sẽ sử dụng mọi tài nguyên theo mật độ cụ thể từ ứng dụng của bạn. Nếu không có sẵn tài nguyên theo đúng mật độ, hệ thống sẽ tải tài nguyên mặc định và tăng hoặc giảm tỷ lệ tài nguyên đó nếu cần. Hệ thống giả định rằng các tài nguyên mặc định (những tài nguyên từ một thư mục không có bộ hạn định cấu hình) được thiết kế cho mật độ pixel cơ sở (mdpi) và đổi kích thước các bitmap đó thành kích thước phù hợp với mật độ pixel hiện tại.

    Nếu bạn yêu cầu kích thước của một tài nguyên được điều chỉnh theo tỷ lệ trước, hệ thống sẽ trả về các giá trị đại diện cho các phương diện đó sau khi điều chỉnh theo tỷ lệ. Ví dụ: bitmap được thiết kế ở 50 x 50 pixel cho màn hình mdpi sẽ được điều chỉnh theo tỷ lệ thành 75 x 75 pixel trên màn hình hdpi (nếu không có tài nguyên thay thế cho hdpi) và hệ thống báo cáo kích thước đó.

    Có một số trường hợp mà bạn có thể không muốn Android mở rộng trước quy mô tài nguyên. Cách dễ nhất để tránh việc điều chỉnh tỷ lệ trước là đặt tài nguyên vào một thư mục tài nguyên có bộ hạn định cấu hình nodpi. Ví dụ:

    res/drawable-nodpi/icon.png

    Khi sử dụng bitmap icon.png từ thư mục này, hệ thống sẽ không điều chỉnh bitmap theo tỷ lệ dựa trên mật độ hiện tại của thiết bị.

  2. Tự động điều chỉnh tỷ lệ kích thước và toạ độ pixel

    Bạn có thể tắt việc điều chỉnh tỷ lệ trước các kích thước và hình ảnh bằng cách đặt android:anyDensity thành "false" trong tệp kê khai hoặc theo phương thức lập trình cho Bitmap bằng cách đặt inScaled thành "false". Trong trường hợp này, hệ thống sẽ tự động điều chỉnh tỷ lệ mọi toạ độ pixel tuyệt đối và giá trị kích thước pixel tại thời điểm vẽ. Chiến lược này thực hiện việc này nhằm đảm bảo các phần tử trên màn hình do pixel xác định vẫn hiển thị ở kích thước vật lý gần bằng với kích thước có thể hiển thị ở mật độ pixel cơ sở (mdpi). Hệ thống xử lý việc điều chỉnh tỷ lệ này một cách minh bạch với ứng dụng và báo cáo kích thước pixel được điều chỉnh theo tỷ lệ cho ứng dụng, thay vì kích thước pixel thực tế.

    Ví dụ: giả sử một thiết bị có màn hình WVGA với độ phân giải cao, 480 x 800 và có kích thước tương tự như màn hình HVGA truyền thống, nhưng thiết bị đang chạy một ứng dụng đã vô hiệu hoá việc điều chỉnh tỷ lệ trước. Trong trường hợp này, hệ thống "nằm" cho ứng dụng khi truy vấn kích thước màn hình và báo cáo 320x533, phép dịch mdpi gần đúng cho mật độ pixel.

    Sau đó, khi ứng dụng thực hiện các thao tác vẽ, chẳng hạn như vô hiệu hoá một hình chữ nhật từ (10,10) thành (100, 100), hệ thống sẽ chuyển đổi toạ độ bằng cách điều chỉnh theo tỷ lệ phù hợp và thực sự vô hiệu hoá khu vực (15,15) thành (150, 150). Sự chênh lệch này có thể gây ra hành vi không mong muốn nếu ứng dụng của bạn trực tiếp điều khiển bitmap được điều chỉnh theo tỷ lệ, nhưng đây được coi là sự đánh đổi hợp lý để đảm bảo hiệu suất ứng dụng tốt nhất có thể. Nếu bạn gặp trường hợp này, hãy đọc bài viết Chuyển đổi đơn vị dp thành đơn vị pixel.

    Thường thì bạn không tắt phương pháp điều chỉnh tỷ lệ trước. Cách tốt nhất để hỗ trợ nhiều màn hình là làm theo các kỹ thuật cơ bản được mô tả trên trang này.

Nếu ứng dụng của bạn điều khiển bitmap hoặc tương tác trực tiếp với các pixel trên màn hình theo một cách khác, thì bạn có thể cần phải thực hiện thêm các bước để hỗ trợ nhiều mật độ pixel. Ví dụ: nếu phản hồi các cử chỉ chạm bằng cách đếm số pixel mà một ngón tay đi qua, thì bạn cần sử dụng các giá trị pixel không phụ thuộc vào mật độ phù hợp, thay vì các pixel thực tế, nhưng bạn có thể chuyển đổi giữa giá trị dp và px.

Kiểm thử trên tất cả mật độ pixel

Hãy kiểm thử ứng dụng của bạn trên nhiều thiết bị có mật độ pixel khác nhau để đảm bảo giao diện người dùng điều chỉnh tỷ lệ một cách chính xác. Kiểm thử trên thiết bị thực khi có thể; sử dụng Trình mô phỏng Android nếu bạn không có quyền truy cập vào thiết bị thực cho tất cả các mật độ pixel khác nhau.

Nếu muốn kiểm thử trên các thiết bị thực nhưng không muốn mua thiết bị đó, bạn có thể sử dụng Phòng thử nghiệm Firebase để truy cập vào các thiết bị trong trung tâm dữ liệu của Google.