Tata letak dan ekspresi binding

Bahasa ekspresi memungkinkan Anda menulis ekspresi yang menangani peristiwa yang dikirim oleh tampilan. Library Data Binding otomatis menghasilkan class yang diperlukan untuk mengikat tampilan di tata letak dengan objek data Anda.

File tata letak data binding sedikit berbeda dan dimulai dengan tag root layout, diikuti dengan elemen data dan elemen root view. Elemen tampilan ini adalah tempat root Anda berada dalam file tata letak non-binding. Kode berikut menunjukkan contoh file tata letak:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"/>
   </LinearLayout>
</layout>

Variabel user dalam data menjelaskan properti yang dapat digunakan dalam tata letak ini:

<variable name="user" type="com.example.User" />

Ekspresi dalam tata letak ditulis di properti atribut menggunakan sintaksis @{}. Pada contoh berikut, teks TextView ditetapkan ke properti firstName dari variabel user:

<TextView android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.firstName}" />

Objek data

Misalnya Anda memiliki objek biasa untuk mendeskripsikan entity User:

Kotlin

data class User(val firstName: String, val lastName: String)

Java


public class User {
  public final String firstName;
  public final String lastName;
  public User(String firstName, String lastName) {
      this.firstName = firstName;
      this.lastName = lastName;
  }
}

Jenis objek ini memiliki data yang tidak pernah berubah. Umumnya aplikasi memiliki data yang dibaca sekali dan tidak pernah berubah setelahnya. Anda juga dapat menggunakan objek yang mengikuti serangkaian konvensi, seperti menggunakan metode pengakses dalam bahasa pemrograman Java, seperti yang ditunjukkan dalam contoh berikut:

Kotlin

// Not applicable in Kotlin.
data class User(val firstName: String, val lastName: String)

Java

public class User {
  private final String firstName;
  private final String lastName;
  public User(String firstName, String lastName) {
      this.firstName = firstName;
      this.lastName = lastName;
  }
  public String getFirstName() {
      return this.firstName;
  }
  public String getLastName() {
      return this.lastName;
  }
}

Dari perspektif data binding, kedua class ini setara. Ekspresi @{user.firstName} yang digunakan untuk atribut android:text mengakses kolom firstName di class sebelumnya dan metode getFirstName() di class kedua. Class ini juga di-resolve ke firstName(), jika metode tersebut ada.

Mengikat data

Class binding dibuat untuk setiap file tata letak. Secara default, nama class didasarkan pada nama file tata letak, yang dikonversi ke Pascal case, dengan akhiran Binding yang ditambahkan ke dalamnya. Misalnya, nama file tata letak sebelumnya adalah activity_main.xml, sehingga class binding terkait yang dihasilkan adalah ActivityMainBinding.

Class ini menyimpan semua binding dari properti tata letak—misalnya, variabel user—ke tampilan tata letak dan mengetahui cara menetapkan nilai untuk ekspresi binding. Sebaiknya buat binding saat meng-inflate tata letak, seperti yang ditunjukkan dalam contoh berikut:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val binding: ActivityMainBinding = DataBindingUtil.setContentView(
            this, R.layout.activity_main)

    binding.user = User("Test", "User")
}

Java

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
   User user = new User("Test", "User");
   binding.setUser(user);
}

Saat runtime, aplikasi akan menampilkan pengguna Test di UI. Atau, Anda bisa mendapatkan tampilan menggunakan LayoutInflater, seperti ditunjukkan dalam contoh berikut:

Kotlin

val binding: ActivityMainBinding = ActivityMainBinding.inflate(getLayoutInflater())

Java

ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());

Jika menggunakan item data binding di dalam adaptor Fragment, ListView, atau RecyclerView, Anda mungkin lebih memilih menggunakan metode inflate() class binding atau class DataBindingUtil, seperti yang ditunjukkan dalam contoh kode berikut:

Kotlin

val listItemBinding = ListItemBinding.inflate(layoutInflater, viewGroup, false)
// or
val listItemBinding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false)

Java

ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
// or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);

Bahasa ekspresi

Fitur umum

Bahasa ekspresi sangat mirip dengan ekspresi yang ditemukan dalam kode terkelola. Anda dapat menggunakan operator dan kata kunci berikut dalam bahasa ekspresi:

  • Matematika: + - / * %
  • Penyambungan string: +
  • Logika: && ||
  • Biner: & | ^
  • Unary: + - ! ~
  • Shift: >> >>> <<
  • Perbandingan: == > < >= <= (< harus di-escape sebagai &lt;)
  • instanceof
  • Pengelompokan: ()
  • Literal, seperti karakter, String, numerik, null
  • Cast
  • Panggilan metode
  • Akses kolom
  • Akses array: []
  • Operator ternary: ?:

Berikut beberapa contohnya:

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'

Operasi yang tidak ada

Operasi berikut tidak ada di sintaksis ekspresi yang dapat Anda gunakan dalam kode terkelola:

  • this
  • super
  • new
  • Panggilan umum eksplisit

Operator penggabungan null

Operator penggabungan null (??) akan memilih operand kiri jika nilainya bukan null atau kanan jika yang pertama adalah null:

android:text="@{user.displayName ?? user.lastName}"

Secara fungsional, tindakan ini sama seperti berikut:

android:text="@{user.displayName != null ? user.displayName : user.lastName}"

Referensi properti

Ekspresi dapat mereferensikan properti di sebuah class menggunakan format berikut, yang sama untuk kolom, pengambil, dan objek ObservableField:

android:text="@{user.lastName}"

Menghindari pengecualian pointer null

Kode data binding yang dihasilkan akan otomatis memeriksa nilai null dan menghindari pengecualian pointer null. Misalnya, dalam ekspresi @{user.name}, jika user bernilai null, user.name diberi nilai defaultnya yaitu null. Jika Anda mereferensikan user.age, di mana usia berjenis int, data binding akan menggunakan nilai default 0.

Referensi tampilan

Ekspresi dapat mereferensikan tampilan lain dalam tata letak menurut ID, menggunakan sintaksis berikut:

android:text="@{exampleText.text}"

Pada contoh berikut, tampilan TextView mereferensikan tampilan EditText dalam tata letak yang sama:

<EditText
    android:id="@+id/example_text"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"/>
<TextView
    android:id="@+id/example_output"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@{exampleText.text}"/>

Koleksi

Anda dapat mengakses koleksi umum, seperti array, daftar, daftar sparse, dan peta, menggunakan operator [] untuk memudahkan.

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String>"/>
    <variable name="sparse" type="SparseArray&lt;String>"/>
    <variable name="map" type="Map&lt;String, String>"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
...
android:text="@{list[index]}"
...
android:text="@{sparse[index]}"
...
android:text="@{map[key]}"

Anda juga dapat merujuk ke sebuah nilai di peta menggunakan notasi object.key. Misalnya, Anda dapat mengganti @{map[key]} pada contoh sebelumnya dengan @{map.key}.

Literal string

Anda dapat menggunakan tanda kutip tunggal untuk mengapit nilai atribut, yang memungkinkan Anda menggunakan tanda kutip ganda dalam ekspresi, seperti yang ditunjukkan dalam contoh berikut:

android:text='@{map["firstName"]}'

Anda juga dapat menggunakan tanda kutip ganda untuk mengapit nilai atribut. Saat melakukannya, literal string harus diapit dengan tanda kutip terbalik `, seperti yang ditunjukkan di sini:

android:text="@{map[`firstName`]}"

Referensi

Sebuah ekspresi dapat mereferensikan resource aplikasi dengan sintaks berikut:

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

Anda dapat mengevaluasi string format dan bentuk jamak dengan memberikan parameter:

android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"

Anda dapat meneruskan referensi properti dan referensi tampilan sebagai parameter resource:

android:text="@{@string/example_resource(user.lastName, exampleText.text)}"

Jika bentuk jamak mengambil beberapa parameter, teruskan semua parameter:


  Have an orange
  Have %d oranges

android:text="@{@plurals/orange(orangeCount, orangeCount)}"

Beberapa resource memerlukan evaluasi jenis eksplisit, seperti ditunjukkan dalam tabel berikut:

Jenis Referensi normal Referensi ekspresi
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

Pengendalian peristiwa

Data binding memungkinkan Anda menulis ekspresi yang menangani peristiwa yang dikirim dari tampilan—misalnya, metode onClick(). Nama atribut peristiwa ditentukan berdasarkan nama metode pemroses, dengan beberapa pengecualian. Misalnya, View.OnClickListener memiliki metode onClick(), sehingga atribut untuk peristiwa ini adalah android:onClick.

Ada beberapa pengendali peristiwa khusus untuk peristiwa klik yang memerlukan atribut selain android:onClick untuk menghindari konflik. Anda dapat menggunakan atribut berikut untuk menghindari jenis konflik ini:

Class Setter pemroses Atribut
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

Anda dapat menggunakan dua mekanisme ini, yang dijelaskan secara mendetail di bagian berikut, untuk menangani peristiwa:

  • Referensi metode: dalam ekspresi, Anda dapat mereferensikan metode yang sesuai dengan tanda tangan metode pemroses. Saat ekspresi dievaluasi ke referensi metode, data binding menggabungkan referensi metode dan objek pemilik dalam pemroses dan menetapkan pemroses tersebut pada tampilan target. Jika ekspresi bernilai null, data binding tidak membuat pemroses dan menetapkan pemroses null.
  • Binding pemroses: ini adalah ekspresi lambda yang dievaluasi saat peristiwa terjadi. Data binding selalu membuat pemroses, yang ditetapkan di tampilan. Saat peristiwa dikirim, pemroses akan mengevaluasi ekspresi lambda.

Referensi metode

Anda dapat mengikat peristiwa ke metode pengendali secara langsung, mirip dengan cara menetapkan android:onClick ke metode dalam aktivitas. Salah satu keunggulan dibandingkan dengan atribut onClick View adalah ekspresi diproses pada waktu kompilasi. Jadi, jika metode tersebut tidak ada atau tanda tangannya salah, Anda akan menerima error waktu kompilasi.

Perbedaan utama antara referensi metode dan binding pemroses adalah penerapan pemroses yang sebenarnya dibuat saat data terikat, bukan saat peristiwa dipicu. Jika Anda memilih untuk mengevaluasi ekspresi saat peristiwa terjadi, gunakan binding pemroses.

Untuk menetapkan peristiwa ke pengendalinya, gunakan ekspresi binding normal, dengan nilainya berupa nama metode yang akan dipanggil. Misalnya, perhatikan contoh objek data tata letak berikut:

Kotlin

class MyHandlers {
    fun onClickFriend(view: View) { ... }
}

Java

public class MyHandlers {
    public void onClickFriend(View view) { ... }
}

Ekspresi binding dapat menetapkan pemroses klik untuk tampilan ke metode onClickFriend(), sebagai berikut:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="handlers" type="com.example.MyHandlers"/>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.firstName}"
           android:onClick="@{handlers::onClickFriend}"/>
   </LinearLayout>
</layout>

Binding pemroses

Binding pemroses adalah ekspresi binding yang berjalan saat peristiwa terjadi. Class ini mirip dengan referensi metode, tetapi memungkinkan Anda menjalankan ekspresi data binding arbitrer. Fitur ini tersedia di Plugin Android Gradle untuk Gradle versi 2.0 dan yang lebih baru.

Dalam referensi metode, parameter metode harus cocok dengan parameter pemroses peristiwa. Dalam binding pemroses, hanya nilai return yang harus cocok dengan nilai return yang diharapkan dari pemroses, kecuali jika mengharapkan void. Misalnya, pertimbangkan class presenter berikut yang memiliki metode onSaveClick():

Kotlin

class Presenter {
    fun onSaveClick(task: Task){}
}

Java

public class Presenter {
    public void onSaveClick(Task task){}
}

Anda dapat mengikat peristiwa klik ke metode onSaveClick() seperti berikut:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="task" type="com.android.example.Task" />
        <variable name="presenter" type="com.android.example.Presenter" />
    </data>
    <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
        <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:onClick="@{() -> presenter.onSaveClick(task)}" />
    </LinearLayout>
</layout>

Saat callback digunakan dalam ekspresi, data binding secara otomatis membuat pemroses yang diperlukan dan mendaftarkannya untuk peristiwa tersebut. Saat tampilan mengaktifkan peristiwa, data binding akan mengevaluasi ekspresi yang diberikan. Seperti ekspresi binding reguler, Anda akan mendapatkan keamanan data binding null dan thread saat ekspresi pemroses ini dievaluasi.

Pada contoh sebelumnya, parameter view yang diteruskan ke onClick(View) tidak ditentukan. Binding pemroses menyediakan dua pilihan untuk parameter pemroses: Anda dapat mengabaikan semua parameter ke metode atau menamai semuanya. Jika memilih untuk memberi nama parameter, Anda dapat menggunakannya dalam ekspresi. Misalnya, Anda dapat menulis ekspresi sebelumnya sebagai berikut:

android:onClick="@{(view) -> presenter.onSaveClick(task)}"

Jika ingin menggunakan parameter dalam ekspresi, Anda dapat melakukannya seperti berikut:

Kotlin

class Presenter {
    fun onSaveClick(view: View, task: Task){}
}

Java

public class Presenter {
    public void onSaveClick(View view, Task task){}
}

android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"

Anda juga dapat menggunakan ekspresi lambda dengan lebih dari satu parameter:

Kotlin

class Presenter {
    fun onCompletedChanged(task: Task, completed: Boolean){}
}

Java

public class Presenter {
    public void onCompletedChanged(Task task, boolean completed){}
}

<CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
      android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />

Jika peristiwa yang Anda proses menampilkan nilai yang jenisnya bukan void, ekspresi Anda juga harus menampilkan jenis nilai yang sama. Misalnya, jika Anda ingin memproses peristiwa sentuh lama (klik lama), ekspresi Anda harus menampilkan boolean.

Kotlin

class Presenter {
    fun onLongClick(view: View, task: Task): Boolean { }
}

Java

public class Presenter {
    public boolean onLongClick(View view, Task task) { }
}

android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"

Jika ekspresi tidak dapat dievaluasi karena objek null, data binding akan menampilkan nilai default untuk jenis tersebut, seperti null untuk jenis referensi, 0 untuk int, atau false untuk boolean.

Jika Anda perlu menggunakan ekspresi dengan predikat—misalnya, ternary—Anda dapat menggunakan void sebagai simbol:

android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"

Menghindari pemroses yang rumit

Ekspresi pemroses sangat canggih dan dapat membuat kode Anda lebih mudah dibaca. Di sisi lain, pemroses yang berisi ekspresi kompleks membuat tata letak Anda lebih sulit dibaca dan dikelola. Buat ekspresi Anda tetap sederhana seperti meneruskan data yang tersedia dari UI ke metode callback Anda. Terapkan logika bisnis apa pun dalam metode callback yang Anda panggil dari ekspresi pemroses.

Import, variable, dan include

Library Data Binding menyediakan fitur seperti impor, variabel, dan sertakan. Impor membuat class yang mudah dirujuk di dalam file tata letak Anda. Variabel memungkinkan Anda mendeskripsikan properti yang dapat digunakan dalam ekspresi binding. Include memungkinkan Anda menggunakan kembali tata letak kompleks di berbagai aplikasi.

Import

Impor memungkinkan Anda mereferensikan class di dalam file tata letak, seperti dalam kode terkelola. Anda dapat menggunakan nol atau beberapa elemen import di dalam elemen data. Contoh kode berikut mengimpor class View ke file tata letak:

<data>
    <import type="android.view.View"/>
</data>

Mengimpor class View memungkinkan Anda mereferensikannya dari ekspresi binding. Contoh berikut menunjukkan cara mereferensikan konstanta VISIBLE dan GONE class View:

<TextView
   android:text="@{user.lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>

Mengetik alias

Jika ada konflik nama class, Anda dapat mengganti nama salah satu class menjadi alias. Contoh berikut mengganti nama class View dalam paket com.example.real.estate menjadi Vista:

<import type="android.view.View"/>
<import type="com.example.real.estate.View"
        alias="Vista"/>

Anda kemudian dapat menggunakan Vista untuk mereferensikan com.example.real.estate.View dan View untuk mereferensikan android.view.View dalam file tata letak.

Mengimpor class lain

Anda dapat menggunakan jenis yang diimpor sebagai referensi jenis dalam variabel dan ekspresi. Contoh berikut menunjukkan User dan List yang digunakan sebagai jenis variabel:

<data>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List&lt;User>"/>
</data>

Anda dapat menggunakan jenis yang diimpor untuk mentransmisikan bagian ekspresi. Contoh berikut mentransmisikan properti connection ke jenis User:

<TextView
   android:text="@{((User)(user.connection)).lastName}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Anda juga dapat menggunakan jenis yang diimpor saat mereferensikan kolom dan metode statis dalam ekspresi. Kode berikut mengimpor class MyStringUtils dan mereferensikan metode capitalize-nya:

<data>
    <import type="com.example.MyStringUtils"/>
    <variable name="user" type="com.example.User"/>
</data>
…
<TextView
   android:text="@{MyStringUtils.capitalize(user.lastName)}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Sama seperti dalam kode terkelola, java.lang.* otomatis diimpor.

Variabel

Anda dapat menggunakan beberapa elemen variable di dalam elemen data. Setiap elemen variable menjelaskan properti yang dapat ditetapkan pada tata letak yang akan digunakan dalam ekspresi binding dalam file tata letak. Contoh berikut mendeklarasikan variabel user, image, dan note:

<data>
    <import type="android.graphics.drawable.Drawable"/>
    <variable name="user" type="com.example.User"/>
    <variable name="image" type="Drawable"/>
    <variable name="note" type="String"/>
</data>

Jenis variabel diperiksa pada waktu kompilasi, jadi jika sebuah variabel mengimplementasikan Observable atau merupakan koleksi yang dapat diamati, hal itu harus tercermin dalam jenisnya. Jika variabel adalah class dasar atau antarmuka yang tidak menerapkan antarmuka Observable, variabel tidak akan diamati.

Jika ada file tata letak berbeda untuk berbagai konfigurasi (misalnya, lanskap atau potret), variabel akan digabungkan. Tidak boleh ada definisi variabel yang bertentangan di antara file tata letak ini.

Class binding yang dihasilkan memiliki penyetel dan pengambil untuk setiap variabel yang dijelaskan. Variabel ini mengambil nilai kode terkelola default hingga penyetel dipanggil—null untuk jenis referensi, 0 untuk int, false untuk boolean, dll.

Variabel khusus bernama context dibuat untuk digunakan dalam ekspresi binding sesuai kebutuhan. Nilai untuk context adalah objek Context dari metode getContext() tampilan root. Variabel context diganti oleh deklarasi variabel eksplisit dengan nama tersebut.

Include

Anda dapat meneruskan variabel ke dalam binding tata letak yang disertakan dari tata letak yang memuatnya menggunakan namespace aplikasi dan nama variabel dalam atribut. Contoh berikut menunjukkan variabel user yang disertakan dari file tata letak name.xml dan contact.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </LinearLayout>
</layout>

Data binding tidak mendukung penyertaan sebagai turunan langsung dari elemen penggabungan. Misalnya, tata letak berikut tidak didukung:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <merge><!-- Doesn't work -->
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </merge>
</layout>

Referensi lainnya

Untuk mempelajari data binding lebih lanjut, lihat referensi tambahan berikut.

Contoh

Codelab

Postingan blog