Komponen Tampilan Kustom

Android menawarkan model canggih dan andal yang terbagi menjadi komponen terpisah untuk membuat UI, berdasarkan pada kelas tata letak dasar: View dan ViewGroup. Sebagai permulaan, platform ini menyertakan berbagai subclass View dan ViewGroup bawaan — yang disebut dengan widget dan tata letak — yang dapat Anda gunakan untuk membuat UI.

Daftar sebagian widget yang tersedia mencakup Button, TextView, EditText, ListView, CheckBox, RadioButton, Gallery, Spinner, dan yang memiliki kegunaan khusus AutoCompleteTextView, ImageSwitcher, dan TextSwitcher.

Di antara tata letak yang tersedia adalah LinearLayout, FrameLayout, RelativeLayout, dan lainnya. Untuk contoh lainnya, buka Objek Tata Letak Umum.

Jika tidak ada widget atau tata letak bawaan yang memenuhi kebutuhan, Anda dapat membuat subclass View Anda sendiri. Jika Anda hanya perlu melakukan sedikit penyesuaian pada widget atau tata letak yang sudah ada, cukup buat subclass widget atau tata letak dan ganti metodenya.

Pembuatan subclass View sendiri memberi Anda kontrol yang akurat terhadap tampilan dan fungsi elemen layar. Untuk memberi gambaran terkait kontrol yang Anda dapatkan dengan tampilan kustom, berikut beberapa contoh hal yang dapat Anda lakukan dengan tampilan kustom:

  • Anda dapat membuat jenis View yang sepenuhnya dirender secara kustom, misalnya tombol "kontrol volume" yang dirender menggunakan grafis 2D, dan yang menyerupai kontrol elektronik analog.
  • Anda dapat menggabungkan kumpulan komponen View menjadi satu komponen yang baru, mungkin untuk membuat semacam ComboBox (kombinasi daftar pop-up dan kolom teks entri gratis), kontrol pemilih panel ganda (panel kiri dan kanan dengan daftar di masing-masing panel, tempat Anda dapat menetapkan kembali letak setiap item dalam setiap daftar), dan lain-lain.
  • Anda dapat mengganti cara komponen EditText dirender di layar ( Tutorial Notepad menggunakan efek yang baik ini untuk membuat halaman notepad bergaris).
  • Anda dapat mencatat peristiwa lain seperti penekanan tombol dan menanganinya dengan suatu cara kustom (seperti untuk game).

Bagian di bawah menjelaskan cara membuat View kustom dan menggunakannya di aplikasi Anda. Untuk mengetahui informasi referensi yang mendetail, buka class View.

Pendekatan Dasar

Berikut ringkasan umum terkait hal yang perlu diketahui untuk memulai pembuatan komponen View Anda sendiri:

  1. Perluas class atau subclass View yang sudah ada dengan class Anda sendiri.
  2. Ganti beberapa metode dari superclass. Metode superclass yang perlu diganti dimulai dengan 'on', misalnya, onDraw(), onMeasure(), dan onKeyDown(). Ini mirip dengan peristiwa on... di Activity atau ListActivity yang Anda ganti untuk lifecycle dan hook fungsi lainnya.
  3. Gunakan class ekstensi Anda yang baru. Setelah selesai, class ekstensi Anda yang baru dapat digunakan sebagai pengganti tampilan yang menjadi dasarnya.

Tips: Class ekstensi dapat didefinisikan sebagai inner class di dalam aktivitas yang menggunakannya. Tindakan ini bermanfaat karena class ekstensi mengontrol akses ke class ekstensi, tetapi tidak diperlukan (mungkin Anda ingin membuat View publik baru untuk penggunaan yang lebih luas di aplikasi Anda).

Komponen yang Disesuaikan Sepenuhnya

Komponen yang disesuaikan sepenuhnya dapat digunakan untuk membuat komponen grafis yang muncul sesuai keinginan Anda. Mungkin VU meter grafis yang tampak seperti pengukur analog lama, atau tampilan teks untuk menyanyikan lagu, yang mana bola melambung bergerak sesuai dengan kata sehingga Anda dapat bernyanyi bersama mesin karaoke. Apa pun itu, Anda memerlukan sesuatu yang tidak dapat dilakukan oleh komponen bawaan, bagaimana pun Anda menggabungkannya.

Untungnya, Anda dapat dengan mudah membuat komponen yang terlihat dan berperilaku sesuai keinginan, yang mungkin hanya terbatasi oleh imajinasi Anda, ukuran layar, dan daya pemrosesan yang tersedia (perlu diingat bahwa pada akhirnya aplikasi mungkin harus dijalankan pada sesuatu dengan daya yang jauh lebih sedikit dibanding komputer desktop Anda).

Untuk membuat komponen yang disesuaikan sepenuhnya:

  1. Tentu saja, tampilan paling umum yang dapat Anda perluas adalah View, jadi biasanya Anda akan memulai dengan memperluas tampilan ini untuk membuat komponen super yang baru.
  2. Anda dapat menyediakan constructor yang dapat mengambil atribut dan parameter dari XML, dan Anda juga dapat menggunakan atribut dan parameter Anda sendiri (mungkin warna dan rentang VU meter, atau lebar dan redaman jarum, dll.)
  3. Anda mungkin ingin membuat pemroses peristiwa, pengakses properti, dan pengubah sendiri, dan mungkin juga perilaku yang lebih rumit di class komponen.
  4. Kemungkinan besar Anda ingin mengganti onMeasure() dan juga perlu mengganti onDraw() jika ingin agar komponen menampilkan sesuatu. Meskipun keduanya memiliki perilaku default, onDraw() default tidak akan melakukan apa pun, dan onMeasure() default akan menetapkan ukuran 100x100 — yang mungkin bukan merupakan ukuran yang Anda inginkan.
  5. Metode on... lain juga dapat diganti sesuai kebutuhan.

Memperluas onDraw() dan onMeasure()

Metode onDraw() memberi Anda Canvas, tempat Anda dapat mengimplementasikan apa pun yang diinginkan: grafis 2D, komponen standar atau kustom lain, teks bergaya, atau apa pun yang Anda inginkan.

Catatan: Ini tidak berlaku untuk grafis 3D. Jika ingin menggunakan grafis 3D, Anda harus memperluas SurfaceView dan bukan View, dan mengambil dari thread terpisah. Buka sampel GLSurfaceViewActivity untuk detailnya.

onMeasure() sedikit lebih terlibat. onMeasure() merupakan bagian yang penting dari perenderan kontrak antara komponen Anda dan container-nya. onMeasure() harus diganti agar dapat melaporkan pengukuran bagian-bagian yang terdapat di dalamnya dengan efisien dan akurat. Ini dibuat sedikit lebih kompleks dengan persyaratan batas dari induk (yang diteruskan ke metode onMeasure()) dan dengan persyaratan untuk memanggil metode setMeasuredDimension() dengan lebar dan tinggi yang diukur setelah selesai dihitung. Jika gagal memanggil metode ini dari metode onMeasure() yang telah diganti, hasilnya akan menjadi pengecualian pada waktu pengukuran.

Pada level yang tinggi, implementasi onMeasure() akan terlihat seperti ini:

  1. Metode onMeasure() yang telah diganti dipanggil dengan spesifikasi ukuran lebar dan tinggi (parameter widthMeasureSpec dan heightMeasureSpec, keduanya merupakan kode bilangan bulat yang mewakili dimensi) yang harus diperlakukan sebagai persyaratan untuk batasan terhadap pengukuran lebar dan tinggi yang harus Anda hasilkan. Referensi lengkap terkait jenis batasan yang dapat dijadikan persyaratan oleh spesifikasi ini dapat ditemukan di dokumentasi referensi pada View.onMeasure(int, int) (dokumentasi referensi ini juga menjelaskan keseluruhan operasi pengukuran dengan baik).
  2. Metode onMeasure() komponen Anda harus menghitung lebar dan tinggi pengukuran yang diperlukan untuk merender komponen. Metode ini harus mencoba berada dalam spesifikasi yang diteruskan, meskipun metode ini dapat memilih untuk melebihi spesifikasi tersebut, (dalam kasus ini, induk dapat memilih apa yang harus dilakukan, termasuk menyesuaikan, men-scroll, atau memunculkan pengecualian, atau meminta onMeasure() untuk mencoba lagi, mungkin dengan spesifikasi pengukuran yang berbeda).
  3. Setelah lebar dan tinggi dihitung, metode setMeasuredDimension(int width, int height) harus dipanggil dengan pengukuran yang telah dihitung. Jika gagal melakukannya, pengecualian akan muncul.

Berikut ringkasan dari beberapa metode standar lain yang dipanggil framework pada tampilan:

Kategori Metode Deskripsi
Kreasi Constructor Ada bentuk constructor yang dipanggil saat tampilan dibuat dari kode dan bentuk constructor yang dipanggil saat tampilan diluaskan dari file tata letak. Bentuk yang kedua harus mengurai dan menerapkan atribut apa pun yang ditetapkan di file tata letak.
onFinishInflate() Dipanggil setelah tampilan dan semua turunannya telah diperluas dari XML.
Tata Letak onMeasure(int, int) Dipanggil untuk menentukan persyaratan ukuran untuk tampilan ini dan semua turunannya.
onLayout(boolean, int, int, int, int) Dipanggil saat tampilan ini harus menetapkan ukuran dan posisi ke semua turunannya.
onSizeChanged(int, int, int, int) Dipanggil saat ukuran tampilan ini telah berubah.
Gambar onDraw(Canvas) Dipanggil saat tampilan harus merender kontennya.
Pemrosesan peristiwa onKeyDown(int, KeyEvent) Dipanggil saat peristiwa tombol baru terjadi.
onKeyUp(int, KeyEvent) Dipanggil saat peristiwa tombol ke atas terjadi.
onTrackballEvent(MotionEvent) Dipanggil saat peristiwa gerakan trackball terjadi.
onTouchEvent(MotionEvent) Dipanggil saat peristiwa gerakan layar sentuh terjadi.
Fokus onFocusChanged(boolean, int, Rect) Dipanggil saat tampilan mendapatkan atau kehilangan fokus.
onWindowFocusChanged(boolean) Dipanggil saat jendela yang berisi tampilan mendapatkan atau kehilangan fokus.
Melampirkan onAttachedToWindow() Dipanggil saat tampilan dilampirkan ke jendela.
onDetachedFromWindow() Dipanggil saat tampilan dilepas dari jendelanya.
onWindowVisibilityChanged(int) Dipanggil saat visibilitas jendela yang berisi tampilan telah berubah.

Kontrol Compound

Jika Anda tidak ingin membuat komponen yang disesuaikan sepenuhnya, tetapi ingin menyatukan komponen yang dapat digunakan kembali yang terdiri dari sekumpulan kontrol yang sudah ada, maka membuat Komponen Compound (atau Kontrol Compound) mungkin sesuai untuk Anda. Singkatnya, tindakan ini akan menyatukan beberapa kontrol (atau tampilan) yang lebih atomik menjadi sekumpulan item yang dapat diperlakukan sebagai satu item. Misalnya, Kotak Kombinasi dapat dianggap sebagai kombinasi dari satu baris kolom EditText dan tombol yang berdekatan, dengan PopupList terlampir. Jika Anda menekan tombol tersebut dan memilih sesuatu dari daftar, pilihan Anda akan mengisi kolom EditText, tetapi pengguna juga dapat mengetik sesuatu secara langsung ke kolom EditText jika menginginkannya.

Di Android, sebenarnya ada dua View lain yang tersedia untuk melakukan ini: Spinner dan AutoCompleteTextView, tetapi terlepas dari itu, konsep Kotak Kombinasi merupakan contoh yang mudah dipahami.

Untuk membuat komponen compound:

  1. Titik awal yang umum adalah Tata letak, jadi buat class yang memperluas Tata letak. Mungkin dalam kasus Kotak kombinasi, kita dapat menggunakan LinearLayout dengan orientasi horizontal. Perlu diingat bahwa tata letak lain dapat ditambahkan di dalamnya, sehingga komponen compound dapat menjadi kompleks dan terstruktur secara bebas. Perhatikan bahwa sama halnya dengan Aktivitas, Anda dapat menggunakan pendekatan deklaratif (berbasis XML) untuk membuat komponen yang terdapat di dalamnya, atau Anda dapat menambahkannya secara terprogram dari kode.
  2. Dalam constructor untuk class yang baru, ambil parameter apa pun yang diharapkan superclass, lalu teruskan parameter tersebut melalui constructor superclass terlebih dahulu. Kemudian Anda dapat menyiapkan tampilan lain untuk digunakan dalam komponen baru; ini adalah tempat Anda membuat kolom EditText dan PopupList. Perhatikan bahwa Anda juga dapat memperkenalkan atribut dan parameter Anda sendiri ke XML yang dapat diambil dan digunakan oleh constructor.
  3. Anda juga dapat membuat pemroses untuk peristiwa yang dapat dihasilkan tampilan yang ada di dalamnya, misalnya, metode pemroses untuk Pemroses Klik Item Daftar agar memperbarui konten EditText jika pilihan daftar dibuat.
  4. Anda juga dapat membuat properti Anda sendiri dengan pengakses dan pengubah, misalnya, izinkan agar nilai EditText ditetapkan di awal di komponen dan meminta kontennya saat diperlukan.
  5. Dalam kasus perluasan Tata letak, Anda tidak perlu mengganti metode onDraw() dan onMeasure() karena tata letak akan memiliki perilaku default yang kemungkinan akan berfungsi dengan baik. Namun, Anda masih dapat menggantikannya jika diperlukan.
  6. Anda dapat mengganti metode on... lain, seperti onKeyDown(), mungkin agar memilih nilai default tertentu dari daftar pop-up kotak kombinasi saat tombol tertentu ditekan.

Ringkasnya, penggunaan Tata letak sebagai dasar untuk Kontrol Kustom memiliki beberapa keuntungan, termasuk:

  • Seperti pada layar aktivitas, Anda dapat menentukan tata letak menggunakan file XML deklaratif, atau Anda dapat membuat tampilan secara terprogram dan menambahkannya ke tata letak dari kode.
  • Metode onDraw() dan onMeasure() (serta sebagian besar metode on... lainnya) kemungkinan akan memiliki perilaku yang sesuai, sehingga Anda tidak perlu menggantinya.
  • Pada akhirnya, Anda dapat dengan cepat membuat tampilan compound yang kompleks secara bebas dan menggunakannya kembali seolah-olah tampilan tersebut merupakan komponen tunggal.

Mengubah Jenis View yang Ada

Ada opsi yang lebih mudah lagi untuk membuat View kustom, yang bermanfaat dalam situasi tertentu. Jika ada komponen yang sudah sangat mirip dengan yang diinginkan, Anda hanya perlu memperluas komponen tersebut dan mengganti perilaku yang ingin diubah. Anda dapat melakukan semua hal yang biasanya akan dilakukan dengan komponen yang disesuaikan sepenuhnya, tetapi dengan mulai menggunakan class khusus di hierarki View, Anda juga bisa mendapatkan perilaku gratis yang kemungkinan melakukan hal persis seperti yang diinginkan.

Misalnya, aplikasi NotePad menunjukkan banyak aspek penggunaan platform Android. Di antaranya adalah memperluas EditText View agar membuat notepad bergaris. Ini bukan contoh yang ideal, dan API untuk melakukan ini mungkin berubah, tetapi contoh ini menunjukkan prinsipnya.

Jika belum, impor sampel NotePad ke Android Studio (atau cukup lihat sumber menggunakan link yang tersedia). Khususnya, perhatikan definisi LinedEditText di file NoteEditor.java.

Berikut beberapa hal yang perlu diperhatikan di file ini:

  1. Definisi

    Class didefinisikan dengan baris berikut:
    public static class LinedEditText extends EditText

    • LinedEditText didefinisikan sebagai inner class di dalam aktivitas NoteEditor, tetapi bersifat publik sehingga dapat diakses sebagai NoteEditor.LinedEditText dari luar class NoteEditor jika diinginkan.
    • Class ini static, yang berarti bahwa class ini tidak menghasilkan "metode sintesis" yang memungkinkannya mengakses data dari class induk, yang berarti bahwa class ini benar-benar berperilaku sebagai class terpisah, dan bukan class yang sangat terkait dengan NoteEditor. Ini adalah cara yang lebih sederhana untuk membuat inner class jika class tersebut tidak memerlukan akses untuk menyatakan berasal dari outer class, mempertahankan agar class yang dihasilkan tetap kecil, dan memungkinkannya digunakan dengan mudah dari class lain.
    • Class ini memperluas EditText, View yang telah kita pilih untuk disesuaikan dalam kasus ini. Setelah selesai, class yang baru akan dapat menjadi pengganti tampilan EditText normal.
  2. Inisialisasi Class

    Seperti biasa, super dipanggil terlebih dahulu. Selain itu, ini bukan constructor default, tetapi constructor yang diparameterisasi. EditText dibuat dengan parameter ini saat diluaskan dari file tata letak XML, oleh karenanya, constructor juga harus mengambil dan meneruskannya ke constructor superclass.

  3. Metode yang Diganti

    Contoh ini hanya mengganti satu metode, onDraw(), tetapi Anda mungkin perlu mengganti metode lain saat membuat komponen kustom Anda sendiri.

    Untuk sampel ini, penggantian metode onDraw() memungkinkan kita menggambar garis biru di kanvas tampilan EditText (kanvas diteruskan ke metode yang diganti). Metode super.onDraw() dipanggil sebelum metode berakhir. Metode superclass harus dipanggil, dan dalam kasus ini, kita melakukannya di akhir setelah kita menggambar garis yang ingin disertakan.

  4. Menggunakan Komponen Kustom

    Kini kita memiliki komponen kustom, tetapi bagaimana cara menggunakannya? Pada contoh NotePad, komponen kustom digunakan secara langsung dari tata letak deklaratif, jadi lihat note_editor.xml di folder res/layout.

        <view xmlns:android="http://schemas.android.com/apk/res/android"
            class="com.example.android.notepad.NoteEditor$LinedEditText"
            android:id="@+id/note"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="@android:color/transparent"
            android:padding="5dp"
            android:scrollbars="vertical"
            android:fadingEdge="vertical"
            android:gravity="top"
            android:textSize="22sp"
            android:capitalize="sentences"
        />
        
    • Komponen kustom dibuat sebagai tampilan umum di XML, dan class ditetapkan menggunakan paket lengkap. Perhatikan juga bahwa inner class yang ditetapkan direferensikan menggunakan notasi NoteEditor$LinedEditText, yang merupakan cara standar untuk mereferensikan ke inner class dalam bahasa pemrograman Java.

      Jika komponen View kustom Anda tidak didefinisikan sebagai inner class, maka sebagai gantinya, Anda dapat mendeklarasikan komponen View dengan nama elemen XML, dan mengecualikan atribut class. Contoh:

          <com.example.android.notepad.LinedEditText
            id="@+id/note"
            ... />
          

      Perhatikan bahwa class LinedEditText kini merupakan file class terpisah. Jika class ditambahkan di class NoteEditor, teknik ini tidak akan berfungsi.

    • Atribut dan parameter lain di definisi tersebut adalah atribut dan parameter yang diteruskan ke constructor komponen kustom, kemudian diteruskan melalui constructor EditText, sehingga merupakan parameter yang sama yang Anda gunakan untuk tampilan EditText. Perlu diperhatikan bahwa mungkin saja untuk juga menambahkan parameter Anda sendiri. Ini akan dibahas lagi di bawah.

Dan itu saja. Ini memang kasus yang sederhana, dan memang itulah tujuan utamanya — proses pembuatan komponen kustom memang sederhana.

Komponen yang lebih rumit dapat mengganti lebih banyak metode on... dan memperkenalkan beberapa metode helper-nya sendiri, yang secara substansial menyesuaikan properti dan perilakunya. Batasannya hanyalah imajinasi dan apa yang perlu dilakukan komponen Anda.