Mendukung Ukuran Layar Berbeda

Pelajaran ini menampilkan cara mendukung beragam ukuran layar dengan:

  • Memastikan layout Anda bisa diubah ukurannya dengan baik agar pas dengan layar
  • Menyediakan layout UI yang sesuai dengan konfigurasi layar
  • Memastikan layout yang benar diterapkan pada layar yang benar
  • Menyediakan bitmap diskalakan dengan benar

Menggunakan "wrap_content" dan "match_parent"

Untuk memastikan bahwa layout Anda fleksibel dan beradaptasi dengan ukuran layar berbeda, Anda harus menggunakan "wrap_content" dan "match_parent" untuk lebar dan tinggi beberapa komponen tampilan. Jika Anda menggunakan "wrap_content", lebar atau tinggi tampilan akan diatur ke ukuran minimum yang diperlukan agar pas dengan materi dalam tampilan itu, sementara "match_parent" akan membuat komponen membentang agar pas dengan ukuran tampilan induknya.

Dengan menggunakan nilai ukuran "wrap_content" dan "match_parent" sebagai ganti ukuran terprogram atau hardcoded, masing-masing tampilan Anda hanya akan menggunakan ruang yang diperlukan untuk tampilan itu maupun luaskan untuk mengisi ruang yang tersedia. Misalnya:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout android:layout_width="match_parent" 
                  android:id="@+id/linearLayout1"  
                  android:gravity="center"
                  android:layout_height="50dp">
        <ImageView android:id="@+id/imageView1" 
                   android:layout_height="wrap_content"
                   android:layout_width="wrap_content"
                   android:src="@drawable/logo"
                   android:paddingRight="30dp"
                   android:layout_gravity="left"
                   android:layout_weight="0" />
        <View android:layout_height="wrap_content" 
              android:id="@+id/view1"
              android:layout_width="wrap_content"
              android:layout_weight="1" />
        <Button android:id="@+id/categorybutton"
                android:background="@drawable/button_bg"
                android:layout_height="match_parent"
                android:layout_weight="0"
                android:layout_width="120dp"
                style="@style/CategoryButtonStyle"/>
    </LinearLayout>

    <fragment android:id="@+id/headlines" 
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="match_parent" />
</LinearLayout>

Perhatikan bagaimana sampel ini menggunakan "wrap_content" dan "match_parent" untuk ukuran komponen daripada dimensi spesifik. Ini memungkinkan layout untuk beradaptasi dengan benar ke ukuran layar dan orientasi yang berbeda.

Misalnya, seperti inilah layout itu akan terlihat dalam mode potret dan lanskap. Perhatikan, ukuran komponen beradaptasi secara otomatis dengan lebar dan tinggi.

Gambar 1. Aplikasi contoh News Reader dalam potret (kiri) dan lanskap (kanan).

Menggunakan RelativeLayout

Anda bisa membentuk layout yang cukup kompleks menggunakan instance tersarang LinearLayout serta kombinasi "wrap_content" dan ukuran "match_parent". Akan tetapi, LinearLayout tidak memungkinkan Anda mengontrol dengan tepat hubungan spasial dari tampilan anak; tampilan dalam LinearLayout hanya berjajar berdampingan. Jika Anda ingin tampilan anak diorientasikan bervariasi, bukannya garis lurus, solusi yang lebih baik sering kali adalah menggunakan RelativeLayout, yang memungkinkan Anda menetapkan layout dari segi hubungan spasial antar komponen. Misalnya, Anda bisa menyejajarkan satu tampilan anak di sisi kiri dan tampilan lainnya di sisi kanan layar.

Misalnya:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/label"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Type here:"/>
    <EditText
        android:id="@+id/entry"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/label"/>
    <Button
        android:id="@+id/ok"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/entry"
        android:layout_alignParentRight="true"
        android:layout_marginLeft="10dp"
        android:text="OK" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toLeftOf="@id/ok"
        android:layout_alignTop="@id/ok"
        android:text="Cancel" />
</RelativeLayout>

Gambar 2 menampilkan cara layout ini muncul pada layar QVGA.

Gambar 2. Tangkapan layar pada layar QVGA (layar kecil).

Gambar 3 menunjukkan cara munculnya pada layar lebih besar.

Gambar 3. Tangkapan layar pada layar WSVGA (layar besar).

Perhatikan, meskipun ukuran komponen berubah, hubungan spasialnya akan dipertahankan seperti yang ditetapkan oleh RelativeLayout.LayoutParams.

Menggunakan Qualifier Ukuran

Banyak hal bisa Anda peroleh dari layout fleksibel atau layout relatif seperti yang ada di bagian sebelumnya. Walaupun layout tersebut beradaptasi dengan layar yang berbeda dengan merentang ruang di dalam dan ke sekeliling komponen, layout tersebut mungkin tidak memberikan pengalaman pengguna yang terbaik bagi setiap ukuran layar. Karena itu, aplikasi Anda tidak hanya harus mengimplementasikan layout fleksibel, namun juga menyediakan beberapa layout alternatif untuk menargetkan konfigurasi layar yang berbeda. Anda melakukannya dengan menggunakan qualifier konfigurasi, yang memungkinkan waktu proses memilih sumber daya secara otomatis berdasarkan konfigurasi perangkat Anda saat ini (seperti desain layout berbeda untuk ukuran layar berbeda).

Misalnya, banyak aplikasi mengimplementasikan pola "dua panel" untuk layar besar (aplikasi mungkin menampilkan daftar item pada satu panel dan isinya pada panel yang lain). Tablet dan TV cukup besar bagi kedua panel agar pas pada layar secara bersamaan, namun layar ponsel harus menampilkannya secara terpisah. Jadi, untuk mengimplementasikan layout tersebut, Anda harus memiliki file-file berikut:

  • res/layout/main.xml, layout satu-panel (default):
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <fragment android:id="@+id/headlines"
                  android:layout_height="fill_parent"
                  android:name="com.example.android.newsreader.HeadlinesFragment"
                  android:layout_width="match_parent" />
    </LinearLayout>
  • res/layout-large/main.xml, layout dua-panel:
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="horizontal">
        <fragment android:id="@+id/headlines"
                  android:layout_height="fill_parent"
                  android:name="com.example.android.newsreader.HeadlinesFragment"
                  android:layout_width="400dp"
                  android:layout_marginRight="10dp"/>
        <fragment android:id="@+id/article"
                  android:layout_height="fill_parent"
                  android:name="com.example.android.newsreader.ArticleFragment"
                  android:layout_width="fill_parent" />
    </LinearLayout>

Perhatikan qualifier large dalam nama direktori layout kedua. Layout ini akan dipilih pada perangkat dengan layar yang tergolong besar (misalnya, tablet 7" ke atas). Layout lainnya (tanpa qualifier) akan dipilih untuk perangkat yang lebih kecil.

Menggunakan Qualifier Lebar-terkecil

Salah satu kesulitan yang dialami developer di perangkat Android sebelum 3.2 adalah keranjang ukuran layar "besar", yang mencakup Dell Streak, Galaxy Tab orisinal, dan tablet 7" secara umum. Akan tetapi, banyak aplikasi yang mungkin ingin menunjukkan layout berbeda bagi perangkat berbeda dalam kategori ini (seperti untuk perangkat 5" dan 7"), meskipun semua dianggap sebagai layar "besar". Karena itulah Android memperkenalkan qualifier "Lebar terkecil" (di antaranya) dalam Android 3.2.

Qualifier Lebar-terkecil memungkinkan Anda menargetkan layar yang memiliki lebar minimum tertentu yang diberikan dalam dp. Misalnya, tablet 7" pada umumnya memiliki lebar minimum 600 dp, sehingga jika ingin UI Anda memiliki dua panel pada layar tersebut (namun berupa satu daftar pada layar yang lebih kecil), Anda bisa menggunakan dua layout yang sama dari bagian sebelumnya untuk layout satu dan dua-panel, namun sebagai ganti qualifier ukuran large, gunakan sw600dp untuk menunjukkan layout dua-panel adalah untuk layar yang lebar terkecilnya 600 dp:

  • res/layout/main.xml, layout satu-panel (default):
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <fragment android:id="@+id/headlines"
                  android:layout_height="fill_parent"
                  android:name="com.example.android.newsreader.HeadlinesFragment"
                  android:layout_width="match_parent" />
    </LinearLayout>
  • res/layout-sw600dp/main.xml, layout dua-panel:
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="horizontal">
        <fragment android:id="@+id/headlines"
                  android:layout_height="fill_parent"
                  android:name="com.example.android.newsreader.HeadlinesFragment"
                  android:layout_width="400dp"
                  android:layout_marginRight="10dp"/>
        <fragment android:id="@+id/article"
                  android:layout_height="fill_parent"
                  android:name="com.example.android.newsreader.ArticleFragment"
                  android:layout_width="fill_parent" />
    </LinearLayout>

Ini berarti perangkat yang lebar terkecilnya lebih besar daripada atau sama dengan 600 dp akan memilih layout layout-sw600dp/main.xml (dua-panel), sedangkan layar lebih kecil akan memilih layout layout/main.xml satu-panel.

Akan tetapi, ini tidak akan bekerja dengan baik pada perangkat sebelum versi 3.2, karena perangkat tersebut tidak mengenali sw600dp sebagai qualifier ukuran, jadi Anda tetap harus menggunakan qualifier large juga. Jadi Anda harus memiliki file bernama res/layout-large/main.xml yang identik dengan res/layout-sw600dp/main.xml. Di bagian berikutnya, Anda akan melihat teknik yang memungkinkan Anda menghindari duplikasi file layout dengan cara ini.

Menggunakan Alias Layout

Qualifier lebar-terkecil hanya tersedia pada Android 3.2 ke atas. Karena itu, Anda juga tetap harus menggunakan keranjang ukuran abstrak (kecil, normal, besar dan ekstra besar) agar kompatibel dengan versi sebelumnya. Misalnya, jika ingin mendesain UI Anda agar menampilkan UI satu-panel pada ponsel, namun UI multipanel pada tablet 7", TV dan perangkat besar lainnya, Anda harus menyediakan file-file ini:

  • res/layout/main.xml: layout satu-panel
  • res/layout-large: layout multipanel
  • res/layout-sw600dp: layout multipanel

Dua file terakhir identik, karena salah satu akan dicocokkan oleh perangkat Android 3.2, dan lainnya adalah untuk digunakan tablet dan TV dengan versi Android sebelumnya.

Untuk mencegah duplikasi file yang sama untuk tablet dan TV (dan kerumitan pemeliharaan yang diakibatkannya), Anda bisa menggunakan file alias. Misalnya, Anda bisa mendefinisikan layout berikut:

  • res/layout/main.xml, layout satu-panel
  • res/layout/main_twopanes.xml, layout dua-panel

Dan tambahkan kedua file ini:

  • res/values-large/layout.xml:
    <resources>
        <item name="main" type="layout">@layout/main_twopanes</item>
    </resources>
    
  • res/values-sw600dp/layout.xml:
    <resources>
        <item name="main" type="layout">@layout/main_twopanes</item>
    </resources>
    

Dua file terakhir memiliki materi identik, namun sebenarnya tidak mendefinisikan layout. File tersebut hanya menyetel main menjadi alias bagi main_twopanes. Karena file-file ini memiliki pemilih large dan sw600dp, keduanya akan diterapkan pada tablet dan TV terlepas dari versi Android-nya (tablet dan TV sebelum 3.2 cocok dengan large dan setelah 3.2 akan cocok dengan sw600dp).

Menggunakan Qualifier Orientasi

Beberapa layout berfungsi dengan baik pada orientasi lanskap dan potret, namun sebagian besar bisa dibantu dengan penyesuaian. Dalam aplikasi contoh News Reader, beginilah perilaku layout di setiap ukuran layar dan orientasi:

  • layar kecil, potret: satu-panel, dengan logo
  • layar kecil, lanskap: satu-panel, dengan logo
  • tablet 7", potret: satu-panel, dengan bilah aksi
  • tablet 7", lanskap: dua-panel, lebar, dengan bilah aksi
  • tablet 10", potret: dua-panel, sempit, dengan bilah aksi
  • tablet 10", lanskap: dua-panel, lebar, dengan bilah aksi
  • TV, lanskap: dua-panel, lebar, dengan bilah aksi

Jadi masing-masing layout ini didefinisikan dalam file XML dalam direktori res/layout/. Kemudian, tetapkan setiap layout ke berbagai konfigurasi layar, aplikasi menggunakan alias layout untuk mencocokkannya ke setiap konfigurasi:

res/layout/onepane.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment android:id="@+id/headlines"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="match_parent" />
</LinearLayout>

res/layout/onepane_with_bar.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout android:layout_width="match_parent" 
                  android:id="@+id/linearLayout1"  
                  android:gravity="center"
                  android:layout_height="50dp">
        <ImageView android:id="@+id/imageView1" 
                   android:layout_height="wrap_content"
                   android:layout_width="wrap_content"
                   android:src="@drawable/logo"
                   android:paddingRight="30dp"
                   android:layout_gravity="left"
                   android:layout_weight="0" />
        <View android:layout_height="wrap_content" 
              android:id="@+id/view1"
              android:layout_width="wrap_content"
              android:layout_weight="1" />
        <Button android:id="@+id/categorybutton"
                android:background="@drawable/button_bg"
                android:layout_height="match_parent"
                android:layout_weight="0"
                android:layout_width="120dp"
                style="@style/CategoryButtonStyle"/>
    </LinearLayout>

    <fragment android:id="@+id/headlines" 
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="match_parent" />
</LinearLayout>

res/layout/twopanes.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal">
    <fragment android:id="@+id/headlines"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="400dp"
              android:layout_marginRight="10dp"/>
    <fragment android:id="@+id/article"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.ArticleFragment"
              android:layout_width="fill_parent" />
</LinearLayout>

res/layout/twopanes_narrow.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal">
    <fragment android:id="@+id/headlines"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="200dp"
              android:layout_marginRight="10dp"/>
    <fragment android:id="@+id/article"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.ArticleFragment"
              android:layout_width="fill_parent" />
</LinearLayout>

Karena sekarang semua layout yang memungkinkan telah didefinisikan, maka tinggal memetakan layout yang tepat ke setiap konfigurasi menggunakan qualifier konfigurasi. Sekarang Anda bisa melakukannya dengan teknik alias layout:

res/values/layouts.xml:

<resources>
    <item name="main_layout" type="layout">@layout/onepane_with_bar</item>
    <bool name="has_two_panes">false</bool>
</resources>

res/values-sw600dp-land/layouts.xml:

<resources>
    <item name="main_layout" type="layout">@layout/twopanes</item>
    <bool name="has_two_panes">true</bool>
</resources>

res/values-sw600dp-port/layouts.xml:

<resources>
    <item name="main_layout" type="layout">@layout/onepane</item>
    <bool name="has_two_panes">false</bool>
</resources>

res/values-large-land/layouts.xml:

<resources>
    <item name="main_layout" type="layout">@layout/twopanes</item>
    <bool name="has_two_panes">true</bool>
</resources>

res/values-large-port/layouts.xml:

<resources>
    <item name="main_layout" type="layout">@layout/twopanes_narrow</item>
    <bool name="has_two_panes">true</bool>
</resources>

Menggunakan Bitmap Nine-Patches

Mendukung ukuran layar yang berbeda biasanya berarti sumber daya gambar Anda juga harus dapat beradaptasi dengan ukuran yang berbeda. Misalnya, latar belakang tombol harus pas dengan bentuk tombol apa pun yang diterapkan padanya.

Jika Anda menggunakan gambar sederhana pada komponen yang bisa mengubah ukuran, Anda akan segera melihat bahwa hasilnya kurang mengesankan, karena waktu proses akan merentang atau menciutkan gambar Anda secara seragam. Solusinya adalah menggunakan bitmap nine-patch, yaitu file PNG yang diformat secara khusus yang menunjukkan area mana yang bisa dan tidak bisa direntang.

Karena itu, saat mendesain bitmap yang akan digunakan pada komponen dengan ukuran bervariasi, gunakan selalu nine-patches. Untuk mengonversi bitmap menjadi nine-patch, Anda bisa mulai dengan gambar biasa (gambar 4, yang dengan zoom 4x agar jelas).

Gambar 4. button.png

Kemudian jalankan melalui utilitas draw9patch dari SDK (yang berada dalam direktori tools/), di mana Anda bisa menandai area yang harus direntang dengan menggambar piksel di sepanjang batas kiri dan atas. Anda juga bisa menandai area yang harus menyimpan materi dengan menggambar piksel di sepanjang batas kanan dan bawah, yang hasilnya seperti dalam gambar 5.

Gambar 5.button.9.png

Perhatikan piksel hitam di sepanjang batas. Piksel di batas atas dan kiri menunjukkan tempat gambar bisa direntang, dan piksel di batas kanan dan bawah menunjukkan lokasi menempatkan materi.

Selain itu, perhatikan ekstensi .9.png. Anda harus menggunakan ekstensi ini, karena dengan cara inilah kerangka kerja mendeteksi bahwa ini adalah gambar nine-patch, dan bukan gambar PNG biasa.

Saat Anda menerapkan latar belakang ini ke komponen (dengan menetapkan android:background="@drawable/button", kerangka kerja merentang gambar dengan benar untuk mengakomodasi ukuran tombol, seperti yang ditampilkan dengan berbagai ukuran dalam gambar 6.

Gambar 6. Tombol menggunakan nine-patch button.9.png dalam berbagai ukuran.