Tata letak dan ekspresi binding

Bahasa ekspresi memungkinkan Anda menulis ekspresi yang menangani peristiwa yang dikirim menurut penayangan. 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 {i>root<i} layout, diikuti dengan elemen data dan elemen root view. Tampilan ini adalah akar Anda 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 dalam properti atribut menggunakan atribut Sintaksis @{}. Pada contoh berikut, Teks TextView disetel ke Properti firstName dari variabel user:

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

Objek data

Misalkan 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. Dimungkinkan juga untuk menggunakan objek yang mengikuti serangkaian konvensi, seperti menggunakan metode pengakses di 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. Tujuan ekspresi @{user.firstName} yang digunakan untuk android:text mengakses kolom firstName di class sebelumnya dan getFirstName() di class kedua. Hal ini juga diselesaikan untuk firstName(), jika metode tersebut ada.

Mengikat data

Class binding dibuat untuk setiap file tata letak. Secara {i>default<i}, nama didasarkan pada nama file tata letak, dikonversi ke Pascal case, dengan akhiran Binding 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 dapat dapatkan tampilannya menggunakan LayoutInflater, seperti yang ditunjukkan dalam contoh berikut:

Kotlin

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

Java

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

Jika Anda menggunakan item data binding di dalam Fragment, ListView, atau RecyclerView adaptor, Anda dapat memilih untuk menggunakan inflate() dari class binding atau DataBindingUtil, sebagai 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: +
  • Logis: && ||
  • Biner: & | ^
  • Uner: + - ! ~
  • Shift: >> >>> <<
  • Perbandingan: == > < >= <= (< harus di-escape sebagai &lt;)
  • instanceof
  • Pengelompokan: ()
  • Literal, seperti karakter, String, numerik, null
  • Transmisi
  • 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 (??) memilih operand kiri jika nilainya bukan null atau kanan jika yang pertama adalah null:

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

Secara fungsional, ini setara dengan hal berikut:

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

Referensi properti

Ekspresi dapat mereferensikan properti di class dengan menggunakan format berikut, yang sama untuk {i>field<i}, pengambil, dan ObservableField objek:

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 adalah null, user.name diberi nilai default null. Jika Anda referensi user.age, jika usia berjenis int, maka data binding menggunakan nilai default 0.

Referensi tampilan

Ekspresi dapat merujuk tampilan lain dalam tata letak menurut ID, menggunakan elemen sintaksis:

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

Dalam contoh berikut, tampilan TextView mereferensikan tampilan EditText di 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, list, list 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. Sebagai misalnya, Anda dapat mengganti @{map[key]} dalam contoh sebelumnya dengan @{map.key}.

Literal string

Anda dapat menggunakan tanda kutip tunggal untuk mengapit nilai atribut, sehingga Anda dapat menggunakan tanda kutip ganda dalam ekspresi, seperti yang ditunjukkan pada 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 tampilan referensi sebagai parameter resource:

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

Jika bentuk jamak memerlukan beberapa parameter, teruskan semua parameter:


  Have an orange
  Have %d oranges

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

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

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 penanganan peristiwa yang dikirim dari tampilan—misalnya, onClick() . Nama atribut peristiwa ditentukan oleh 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 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 kedua mekanisme ini, yang dijelaskan secara rinci di bagian yang ikuti, untuk menangani peristiwa:

  • Referensi metode: dalam ekspresi, Anda dapat metode referensi yang sesuai dengan tanda tangan metode pemroses. Kapan ekspresi dievaluasi ke referensi metode, data binding menggabungkan metode objek pemilik dan referensi dalam pemroses dan menyetel pemroses tersebut di tampilan target. Jika ekspresi bernilai null, data binding tidak buat pemroses dan tetapkan pemroses null sebagai gantinya.
  • Binding pemroses: ini adalah ekspresi lambda yang dievaluasi saat peristiwa terjadi. Data binding selalu membuat pemroses, yang ditetapkannya pada tampilan. Ketika peristiwa dikirim, metode akan mengevaluasi ekspresi lambda.

Referensi metode

Anda bisa mengikat peristiwa ke metode pengendali secara langsung, mirip dengan cara tugaskan android:onClick ke dalam suatu aktivitas. Satu keuntungan dibandingkan dengan Atribut onClick View adalah ekspresi tersebut diproses pada waktu kompilasi. Jadi, jika metode itu tidak ada, atau tanda tangan salah, Anda akan menerima kesalahan waktu kompilasi.

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

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

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 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. Mereka mirip dengan referensi metode, tetapi memungkinkan Anda menjalankan ekspresi. Fitur ini tersedia dengan 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 yang ditampilkan yang harus cocok dengan nilai hasil yang diharapkan dari pemroses, kecuali jika nilai yang ditampilkan adalah void. Sebagai contoh, perhatikan class presenter berikut yang memiliki onSaveClick() berikut:

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 akan membuat pemroses yang diperlukan dan mendaftarkannya ke peristiwa tersebut. Saat tampilan mengaktifkan , data binding mengevaluasi ekspresi yang diberikan. Seperti halnya binding biasa ekspresi, Anda akan mendapatkan keamanan null dan thread dari data binding sementara ekspresi pemroses yang akan 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 pada metode atau memberi nama semuanya. Jika Anda lebih suka untuk memberi nama parameter, Anda dapat menggunakannya dalam ekspresi. Sebagai contoh, 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 akan mengembalikan jenis nilai yang sama. Misalnya, jika Anda ingin untuk mendengarkan sentuhan & tahan (klik lama), ekspresi Anda harus mengembalikan 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 ditampilkan 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}"

Hindari pemroses yang rumit

Ekspresi pemroses sangat canggih dan dapat membuat kode Anda lebih mudah dibaca. Pada sedangkan pemroses yang berisi ekspresi kompleks akan membuat tata letak Anda lebih sulit untuk dibaca dan dikelola. Membuat ekspresi Anda sesederhana meneruskan data yang tersedia dari UI ke metode callback Anda. Implementasikan logika bisnis apa pun di dalam yang Anda panggil dari ekspresi pemroses.

Import, variable, dan include

Pustaka Data Binding menyediakan fitur seperti impor, variabel, dan disertakan. Impor membuat class yang mudah dirujuk di dalam file tata letak Anda. Variabel memungkinkan Anda menjelaskan 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. Tujuan contoh kode berikut mengimpor class View ke file tata letak:

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

Dengan mengimpor class View, Anda dapat mereferensikannya dari ekspresi binding. Contoh berikut menunjukkan cara mereferensikan VISIBLE dan Konstanta GONE dari 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 kelas, Anda dapat mengganti nama salah satu class menjadi alias. Contoh berikut mengganti nama class View di Paket com.example.real.estate ke 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. Tujuan 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. Hal berikut contoh 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 statis dan metode di ekspresi. Kode berikut mengimpor class dan referensi MyStringUtils 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. Masing-masing Elemen variable menjelaskan properti yang dapat ditetapkan pada tata letak yang akan digunakan dalam ekspresi binding di 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 variabel mengimplementasikan Observable atau merupakan koleksi yang dapat diamati, yang harus tercermin dalam jenisnya. Jika variabelnya adalah antarmuka atau class dasar yang tidak mengimplementasikan antarmuka Observable, variabelnya tidak diamati.

Ketika ada file tata letak yang berbeda untuk berbagai konfigurasi (misalnya, lanskap atau potret), variabel tersebut 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. Variabel 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 secara permanen sesuai kebutuhan. Nilai untuk context adalah nilai Objek Context dari tampilan root Metode getContext(). Tujuan Variabel context diganti oleh deklarasi variabel eksplisit dengannya nama.

Include

Anda dapat meneruskan variabel ke binding tata letak yang disertakan dari elemen dengan menggunakan namespace aplikasi dan nama variabel dalam atribut. Tujuan contoh berikut menunjukkan variabel user yang disertakan dari name.xml dan File tata letak 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 include sebagai turunan langsung dari elemen gabungan. 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