Mendukung kepadatan piksel yang berbeda-beda

Perangkat Android tidak hanya hadir dalam ukuran layar yang berbeda (handset, tablet, TV, dan sebagainya), tetapi juga layarnya memiliki ukuran piksel yang berbeda. Maksudnya, perangkat yang satu memiliki 160 piksel per inci persegi, sedangkan perangkat lainnya sesuai dengan 480 piksel dalam ruang yang sama. Jika Anda tidak mempertimbangkan perbedaan ini dalam kepadatan piksel, sistem mungkin menskalakan gambar Anda (yang mengakibatkan gambar blur) atau gambar mungkin muncul dalam ukuran yang benar-benar salah.

Halaman ini menunjukkan cara mendesain aplikasi untuk mendukung berbagai kepadatan piksel menggunakan satuan pengukuran bebas resolusi dan memberikan resource bitmap alternatif untuk setiap kepadatan pikselnya.

Tonton video berikut untuk ringkasan tentang teknik ini.

Untuk informasi selengkapnya tentang mendesain aset ikon aktual, lihat panduan ikon desain material.

Menggunakan piksel kepadatan mandiri

Kesalahan pertama yang harus dihindari adalah menggunakan piksel untuk menentukan jarak atau ukuran. Menentukan dimensi layout dengan piksel menjadi masalah karena layar yang berbeda memiliki kepadatan piksel yang berbeda pula, jadi jumlah piksel yang sama bisa sesuai dengan ukuran fisik yang berbeda pada perangkat yang berbeda.

Gambar 1. Dua layar dengan ukuran yang sama bisa memiliki jumlah piksel yang berbeda

Untuk mempertahankan ukuran UI Anda yang terlihat pada layar dengan kepadatan berbeda, Anda harus mendesain UI menggunakan piksel kepadatan mandiri (dp) sebagai unit pengukuran Anda. Satu dp adalah satuan piksel virtual yang kira-kira setara dengan satu piksel pada layar kepadatan medium (160 dpi; kepadatan "dasar pengukuran"). Android akan menerjemahkan nilai ini dalam jumlah tepat piksel aktual untuk masing-masing kepadatan.

Misalnya, perhatikan dua perangkat dalam gambar 1. Jika Anda menentukan tampilan lebar "100px", tampilan akan muncul jauh lebih besar daripada perangkat di kiri. Jadi, Anda harus menggunakan "100dp" untuk memastikan ukurannya sama di kedua layar.

Namun, ketika menentukan ukuran teks, Anda harus menggunakan piksel skalabel (sp) sebagai satuan (tetapi jangan pernah menggunakan sp untuk ukuran tata letak). Satuan sp adalah ukuran yang sama dengan dp, secara default, tetapi mengubah ukuran berdasarkan ukuran teks pilihan pengguna.

Misalnya, ketika Anda menentukan jarak antara dua tampilan, gunakan dp:

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

Saat menentukan ukuran teks, selalu gunakan sp:

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

Mengonversikan satuan dp ke satuan piksel

Dalam beberapa kasus, Anda perlu menyatakan dimensi dalam dp, kemudian mengonversinya ke piksel. Konversi satuan dp ke piksel layar cukup sederhana:

px = dp * (dpi / 160)

Bayangkan aplikasi dengan gestur scroll atau mengayun yang akan dikenali setelah jari pengguna berpindah minimal 16 piksel. Pada layar dasar pengukuran, pengguna harus memindahkan jari sejauh 16 pixels / 160 dpi, yang setara dengan 1/10 inci (atau 2,5 mm) sebelum gestur dikenali. Pada perangkat dengan layar kepadatan tinggi (240 dpi), jari pengguna harus berpindah sejauh 16 pixels / 240 dpi, yang setara dengan 1/15 inci (atau 1,7 mm). Jarak tersebut jauh lebih pendek sehingga aplikasi tampak lebih sensitif bagi pengguna.

Untuk memperbaiki masalah ini, ambang batas gestur harus dinyatakan dengan kode dalam dp, kemudian dikonversikan ke piksel aktual. Contoh:

Kotlin

    // The gesture threshold expressed in dp
    private const val GESTURE_THRESHOLD_DP = 16.0f
    ...
    private var mGestureThreshold: Int = 0
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Get the screen's density scale
        val scale: Float = resources.displayMetrics.density
        // Convert the dps to pixels, based on density scale
        mGestureThreshold = (GESTURE_THRESHOLD_DP * scale + 0.5f).toInt()

        // Use mGestureThreshold as a distance in pixels...
    }
    

Java

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

    // Get the screen's density scale
    final float scale = getResources().getDisplayMetrics().density;
    // Convert the dps to pixels, based on density scale
    mGestureThreshold = (int) (GESTURE_THRESHOLD_DP * scale + 0.5f);

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

Kolom DisplayMetrics.density menentukan faktor skala yang harus Anda gunakan untuk mengonversi satuan dp ke piksel, sesuai dengan kepadatan piksel saat ini. Pada layar berkepadatan medium, DisplayMetrics.density setara dengan 1,0; pada layar berkepadatan tinggi setara dengan 1,5; pada layar berkepadatan sangat tinggi, setara dengan 2,0; dan pada layar berkepadatan rendah setara dengan 0,75. Angka ini adalah faktor yang harus Anda kalikan dengan satuan dp untuk mendapatkan jumlah piksel aktual pada layar saat ini.

Menggunakan nilai konfigurasi yang diskalakan sebelumnya

Anda bisa menggunakan class ViewConfiguration untuk mengakses jarak, kecepatan, dan waktu yang biasa digunakan sistem Android. Contohnya, jarak dalam piksel yang digunakan oleh framework sebagai ambang batas scroll bisa diperoleh dengan getScaledTouchSlop():

Kotlin

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

Java

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

Metode dalam ViewConfiguration yang dimulai dengan prefiks getScaled dipastikan mengembalikan nilai dalam piksel yang akan ditampilkan secara sesuai, berapa pun kepadatan piksel saat ini.

Menyediakan bitmap alternatif

Untuk memberikan kualitas grafis pada perangkat dengan kepadatan piksel berbeda, Anda harus menyediakan beberapa versi masing-masing bitmap dalam aplikasi, satu untuk setiap bucket kepadatan, pada resolusi yang sesuai. Jika tidak, Android harus menskalakan bitmap Anda agar menempati ruang terlihat yang sama di setiap layar, sehingga mengakibatkan artefak penskalaan seperti blur.

Gambar 2. Ukuran relatif untuk bitmap pada ukuran kepadatan yang berbeda

Ada beberapa bucket kepadatan yang tersedia untuk digunakan di aplikasi Anda. Tabel 1 menjelaskan berbagai kualifikasi konfigurasi yang tersedia dan jenis layar yang bisa diterapkan dengannya.

Tabel 1. Kualifikasi konfigurasi untuk berbagai kepadatan piksel.

Kualifikasi kepadatan Deskripsi
ldpi Resource untuk layar kepadatan rendah (ldpi) (~120 dpi).
mdpi Resource untuk layar kepadatan medium (mdpi) (~160 dpi). (Kepadatan ini adalah dasar pengukuran.)
hdpi Resource untuk layar kepadatan tinggi (hdpi) (~240 dpi).
xhdpi Resource untuk layar kepadatan ekstra tinggi (xhdpi) (~320 dpi).
xxhdpi Resource untuk layar kepadatan ekstra ekstra tinggi (xxhdpi) (~480 dpi).
xxxhdpi Resource untuk penggunaan kepadatan ekstra ekstra ekstra tinggi (xxxhdpi) (~640 dpi).
nodpi Resource untuk semua kepadatan. Ukuran ini adalah resource kepadatan mandiri. Sistem tidak menskalakan resource yang diberi tag dengan qualifier ini, berapa pun kepadatan layar saat ini.
tvdpi Resource untuk layar di antara mdpi dan hdpi; sekitar 213 dpi. Ukuran ini tidak dianggap sebagai kelompok kepadatan utama". Kepadatan ini utamanya ditujukan untuk televisi dan sebagian besar aplikasi tidak memerlukannya, menyediakan resource mdpi dan hdpi sudah cukup untuk sebagian besar aplikasi dan sistem yang akan menskalakan sebagaimana mestinya. Jika merasa perlu menyediakan resource tvdpi, Anda harus menyetel ukurannya pada faktor 1,33* mdpi. Misalnya, gambar beresolusi 100 px x 100 px untuk layar mdpi seharusnya 133 px x 133 px untuk tvdpi.

Agar dapat membuat resource alternatif yang bisa digambar bitmap untuk kepadatan yang berbeda, Anda harus mengikuti skala rasio 3:4:6:8:12:16 antara enam kepadatan utama. Misalnya, jika Anda memiliki resource yang dapat digambar bitmap berukuran 48x48 piksel untuk layar berkepadatan medium, semua ukuran yang berbeda harus sebesar:

  • 36x36 (0,75x) untuk kepadatan rendah (ldpi)
  • 48x48 (1,0x dasar pengukuran) untuk kepadatan medium (mdpi)
  • 72x72 (1,5x) untuk kepadatan tinggi (hdpi)
  • 96x96 (2,0x) untuk kepadatan ekstra tinggi (xhdpi)
  • 144x144 (3,0x) untuk kepadatan ekstra ekstra tinggi (xxhdpi)
  • 192x192 (4,0x) untuk kepadatan ekstra ekstra ekstra tinggi (xxxhdpi)

Kemudian, masukkan file gambar yang dihasilkan ke dalam subdirektori yang sesuai di res/, dan sistem secara otomatis akan memilih ukuran yang tepat berdasarkan kepadatan piksel perangkat tempat menjalankan aplikasi Anda:

    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
    

Kemudian, setiap kali Anda mereferensikan @drawable/awesomeimage, sistem akan memilih bitmap yang sesuai berdasarkan dpi layar. Jika Anda tidak memberikan resource spesifik kepadatan untuk kepadatan tersebut, sistem akan memilih pencocokan terbaik berikutnya dan menskalakannya agar sesuai dengan layar.

Tips: Jika Anda memiliki beberapa resource yang dapat digambar yang tidak boleh diskalakan sistem (mungkin karena Anda melakukan beberapa penyesuaian gambar pada waktu proses), Anda harus memasukkannya dalam direktori dengan qualifier konfigurasi nodpi. Resource dengan qualifier ini dianggap tidak tergantung pada kepadatan dan sistem tidak akan menskalakannya.

Untuk informasi selengkapnya tentang qualifier konfigurasi lain dan cara Android memilih resource yang sesuai untuk konfigurasi layar saat ini, lihat Menyediakan Resource.

Meletakkan ikon aplikasi di direktori mipmap

Seperti halnya aset bitmap lainnya, Anda perlu menyediakan versi spesifik kepadatan ikon aplikasi Anda. Namun, beberapa peluncur aplikasi menampilkan ikon aplikasi Anda 25% lebih besar dari yang disebutkan oleh bucket kepadatan perangkat.

Misalnya, jika bucket kepadatan perangkat adalah xxhdpi dan ikon terbesar yang Anda sediakan adalah dalam drawable-xxhdpi, aplikasi peluncur akan menambah skala ikon ini, dan hal itu membuatnya tampil lebih segar. Jadi, Anda harus menyediakan ikon peluncur kepadatan yang lebih tinggi dalam direktori mipmap-xxxhdpi. Kini peluncur bisa menggunakan aset xxxhdpi.

Karena ikon aplikasi mungkin ditambah skalanya seperti ini, Anda harus meletakkan semua ikon aplikasi dalam direktori mipmap, bukan direktori drawable. Tidak seperti direktori drawable, semua direktori mipmap dipertahankan di APK, meskipun Anda membuat APK spesifik kepadatan. Hal ini memungkinkan aplikasi peluncur untuk memilih ikon resolusi terbaik untuk ditampilkan di layar utama.

    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
    

Untuk panduan desain ikon, lihat panduan penting tentang ikon.

Untuk membantu membuat ikon aplikasi, lihat Membuat Ikon Aplikasi dengan Image Asset Studio.

Menggunakan grafis vektor

Alternatif untuk membuat beberapa versi spesifik kepadatan sebuah gambar adalah membuat hanya satu grafis vektor. Grafis vektor membuat gambar menggunakan XML untuk menentukan jalur dan warna, bukan menggunakan bitmap piksel. Dengan demikian, grafis vektor dapat menskalakan hingga berbagai ukuran tanpa artefak penskalaan, meskipun vektor paling baik digunakan dalam hal ilustrasi seperti ikon, bukan fotografi.

Grafik vektor sering disediakan sebagai file SVG (Scalable Vector Graphics), tetapi Android tidak mendukung format ini sehingga Anda harus mengonversi file SVG ke format dapat digambar vektor milik Android.

Anda bisa mengonversi SVG ke resource dapat digambar vektor dari Android Studio menggunakan Vector Asset Studio seperti berikut:

  1. Di jendela Project, klik kanan di direktori res, lalu pilih Baru > Aset Vektor.
  2. Pilih File lokal (SVG, PSD).
  3. Temukan file yang ingin Anda impor, kemudian lakukan penyesuaian.

    Gambar 3. Mengimpor SVG dengan Android Studio

    Anda mungkin melihat beberapa error muncul di jendela Asset Studio, yang menunjukkan beberapa properti file yang tidak didukung resource yang dapat digambar vektor. Namun, hal ini tidak akan mencegah Anda dalam mengimpor, properti yang tidak didukung akan diabaikan begitu saja.

  4. Klik Berikutnya.

  5. Pada layar berikutnya, konfirmasikan kumpulan sumber tempat file yang diinginkan ada di project Anda, lalu klik Selesai.

    Karena satu resource yang dapat digambar vektor bisa digunakan pada semua kepadatan piksel, file ini akan masuk ke direktori drawable default (Anda tidak perlu menggunakan direktori spesifik kepadatan):

        res/
          drawable/
            ic_android_launcher.xml
        

Untuk informasi tentang pembuatan grafis vektor, baca dokumentasi Resource yang Dapat Digambar Vektor.

Saran untuk masalah kepadatan yang tidak biasa

Bagian ini menjelaskan lebih lanjut tentang cara Android menjalankan penskalaan untuk bitmap pada kepadatan piksel yang berbeda dan cara Anda bisa mengontrol bagaimana bitmap digambar pada kepadatan yang berbeda. Kecuali aplikasi memanipulasi grafis atau Anda mengalami masalah saat berjalan di kepadatan piksel yang berbeda, Anda dapat mengabaikan bagian ini.

Untuk memahami bagaimana Anda bisa mendukung beberapa kepadatan ketika memanipulasi grafis pada waktu proses secara lebih baik, Anda harus memahami bahwa sistem membantu memastikan skala yang tepat untuk bitmap dengan cara berikut:

  1. Prapenskalaan resource (misalnya resource yang dapat digambar bitmap)

    Berdasarkan kepadatan layar saat ini, sistem menggunakan berbagai resource spesifik kepadatan dari aplikasi Anda. Jika resource tidak tersedia dalam kepadatan yang tepat, sistem akan memuat resource default dan menskalakannya naik atau turun sesuai kebutuhan. Sistem menganggap bahwa resource default (resource dari direktori tanpa qualifier konfigurasi) didesain untuk kepadatan piksel dasar pengukuran (mdpi) dan akan mengubah ukuran bitmap tersebut ke ukuran yang sesuai dengan kepadatan piksel saat ini.

    Jika Anda meminta dimensi resource yang telah diskalakan sebelumnya, sistem akan menampilkan nilai yang mewakili dimensi tersebut setelah penskalaan. Misalnya, bitmap yang didesain pada ukuran 50x50 piksel untuk layar mdpi akan diskalakan ke 75x75 piksel pada layar hdpi (jika tidak ada resource alternatif untuk hdpi) dan sistem akan melaporkan ukuran tersebut.

    Ada beberapa situasi yang mungkin membuat Anda tidak ingin Android melakukan prapenskalaan pada resource. Cara termudah untuk menghindari prapenskalaan adalah memasukkan resource dalam direktori resource dengan qualifier konfigurasi nodpi. Contoh:

    res/drawable-nodpi/icon.png

    Jika sistem menggunakan bitmap icon.png dari folder ini, sistem tidak akan menskalakannya berdasarkan kepadatan perangkat saat ini.

  2. Penskalaan otomatis dimensi dan koordinat piksel

    Anda dapat menonaktifkan dimensi dan gambar prapenskalaan dengan menyetel android:anyDensity ke "false" dalam manifes atau secara terprogram untuk Bitmap dengan menyetel inScaled ke "false". Dalam hal ini, sistem akan otomatis menskalakan semua nilai koordinat piksel absolut dan nilai dimensi piksel pada waktu menggambarnya. Sistem melakukannya untuk memastikan elemen layar yang ditentukan dengan piksel tetap ditampilkan pada ukuran fisik yang kurang lebih sama karena akan ditampilkan pada kepadatan layar dasar pengukuran (mdpi). Sistem menangani penskalaan ini secara transparan ke aplikasi dan melaporkan dimensi piksel yang diskalakan ke aplikasi, bukan dimensi piksel fisik.

    Contohnya, anggaplah sebuah perangkat memiliki layar berkepadatan tinggi WVGA 480x800 dan ukurannya kurang lebih sama dengan layar HVGA biasa, tetapi perangkat menjalankan aplikasi yang menonaktifkan prapenskalaan. Dalam hal ini, sistem akan "berbohong" kepada aplikasi ketika membuat kueri untuk dimensi layar dan melaporkan 320x533 (perkiraan terjemahan mdpi untuk kepadatan piksel). Kemudian, ketika aplikasi melakukan operasi menggambar, misalnya menjadikan persegi panjang tidak valid dari (10,10) hingga (100, 100), sistem akan mentransformasikan koordinat dengan menskalakannya ke jumlah yang sesuai, dan benar-benar membuat region (15,15) hingga (150, 150) menjadi tidak valid. Perbedaan tersebut dapat menyebabkan perilaku yang tidak terduga jika aplikasi Anda secara langsung memanipulasi bitmap yang telah diskalakan, tetapi hal ini dianggap sebagai konsekuensi wajar untuk mempertahankan performa aplikasi sebaik mungkin. Jika Anda mengalami kondisi tersebut, baca Mengonversi satuan dp ke satuan piksel.

    Normalnya, Anda tidak perlu menonaktifkan prapenskalaan. Cara terbaik untuk mendukung banyak layar adalah dengan mengikuti teknik dasar yang dijelaskan dalam dokumen ini.

Jika aplikasi Anda memanipulasi bitmap atau berinteraksi secara langsung dengan piksel di layar dalam cara yang lain, Anda mungkin perlu mengambil langkah tambahan untuk mendukung kepadatan layar yang berbeda. Misalnya, jika Anda merespons gestur sentuh dengan menghitung jumlah piksel yang dilalui oleh jari, Anda perlu menggunakan nilai piksel kepadatan mandiri, bukan piksel aktual, tetapi Anda bisa dengan mudah mengonversi antara nilai dp dan px.

Menguji di semua kepadatan piksel

Penting untuk menguji aplikasi Anda di beberapa perangkat dengan kepadatan piksel berbeda sehingga Anda bisa memastikan penskalaan UI dengan benar. Menguji pada perangkat fisik itu mudah, tetapi Android Emulator juga bisa digunakan jika Anda tidak memiliki akses ke perangkat fisik untuk semua kepadatan piksel yang berbeda.

Jika Anda lebih memilih untuk mengujinya di perangkat fisik tetapi tidak ingin membeli perangkat, Anda bisa menggunakan Firebase Test Lab untuk mengakses perangkat di pusat data Google.