Ekspresi tata letak dan binding

Bahasa ekspresi memungkinkan Anda menulis ekspresi yang menangani peristiwa yang dikirim ke 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 dari layout diikuti dengan elemen data dan elemen root view. Di elemen tampilan inilah root Anda akan 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 sintaks "@{}". Di sini, 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

Mari kita asumsikan bahwa Anda memiliki objek lama 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. Sering kali aplikasi memiliki data yang hanya dibaca sekali, dan tidak pernah berubah setelahnya. Anda juga dapat menggunakan objek yang mengikuti sekumpulan konvensi, seperti penggunaan metode aksesor di 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 pertama dan metode getFirstName() di class kedua. Atau, ekspresi ini juga dapat ditetapkan 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, dengan mengonversinya menjadi Pascal case, dan menambahkan akhiran Binding ke nama tersebut. Nama file tata letak di atas adalah activity_main.xml sehingga class terkait yang dihasilkannya adalah ActivityMainBinding. Class ini menampung semua binding dari properti tata letak (misalnya, variabel user) hingga tampilan tata letak dan mengetahui cara menetapkan nilai untuk ekspresi binding. Metode yang direkomendasikan untuk membuat binding ini adalah melakukannya saat tata letak diperluas, 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 ini menggunakan LayoutInflater, seperti 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 adaptor Fragment, ListView, atau RecyclerView, Anda dapat memilih untuk 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:

  • Matematis + - / * %
  • Penyambungan string +
  • Logis && ||
  • Biner & | ^
  • Uner + - ! ~
  • Geser >> >>> <<
  • Perbandingan == > < >= <= (Perhatikan bahwa < harus di-escape sebagai &lt;)
  • instanceof
  • Pengelompokan ()
  • Literal - karakter, String, numerik, null
  • Transmisi
  • Panggilan metode
  • Akses kolom
  • Akses array []
  • Operator terner ?:

Contoh:

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 dalam sintaks 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 di sebelah kanan jika nilainya null.

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

Secara fungsional, ini setara dengan:

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

Referensi properti

Sebuah 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 ditetapkan nilai defaultnya yaitu null. Jika Anda mereferensikan user.age, di mana usia berjenis int, maka data binding menggunakan nilai default 0.

Referensi tampilan

Ekspresi dapat mereferensikan tampilan lain dalam tata letak berdasarkan ID dengan sintaks berikut:

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

Dalam 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

Koleksi umum, seperti array, daftar, daftar sparse, dan peta, dapat diakses 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, @{map[key]} dalam contoh di atas dapat diganti 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 ditunjukkan dalam contoh berikut:

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

Anda juga dapat menggunakan tanda kutip ganda untuk mengapit nilai atribut. Saat melakukannya, string literal harus diapit dengan back quote `:

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

Resource

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 memerlukan beberapa parameter, Anda harus meneruskan 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 pengendali 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(), jadi 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 mekanisme berikut untuk menangani peristiwa:

  • Referensi metode: Dalam ekspresi, Anda dapat mereferensikan metode yang sesuai dengan tanda tangan metode pemroses. Saat ekspresi dievaluasi ke sebuah referensi metode, Data binding menggabung referensi metode dan objek pemilik dalam pemroses, dan menetapkan pemroses tersebut di tampilan target. Jika ekspresi dievaluasi ke null, Data binding tidak membuat pemroses dan, sebagai gantinya, menetapkan pemroses null.
  • Binding pemroses: Ini adalah ekspresi lambda yang dievaluasi saat peristiwa terjadi. Data binding selalu menghasilkan pemroses, yang ditetapkan di tampilan. Saat peristiwa dikirim, pemroses akan mengevaluasi ekspresi lambda.

Referensi metode

Peristiwa dapat diikat ke metode pengendali secara langsung, mirip dengan penetapan android:onClick ke sebuah metode dalam suatu aktivitas. Kelebihannya dibandingkan atribut View onClick adalah bahwa ekspresi diproses pada waktu kompilasi, sehingga jika metode tidak ada atau tanda tangannya salah, Anda akan menerima error waktu kompilasi.

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

Untuk menetapkan sebuah peristiwa ke pengendalinya, gunakan ekspresi binding biasa, dengan nilai berupa nama metode yang akan dipanggil. Misalnya, pertimbangkan 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 sebuah 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. Binding ini mirip dengan referensi metode, tetapi memungkinkan Anda menjalankan ekspresi data binding arbitrer. Fitur ini disertakan dalam Android Gradle Plugin versi 2.0 dan yang lebih baru.

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

Kotlin

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

    

Java

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

    

Selanjutnya, 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 otomatis akan membuat pemroses yang diperlukan dan mendaftarkannya untuk peristiwa tersebut. Saat tampilan mengaktifkan peristiwa, data binding mengevaluasi ekspresi yang diberikan. Seperti dalam ekspresi binding biasa, Anda tetap mendapatkan null dan keamanan thread data binding selagi ekspresi pemroses ini dievaluasi.

Dalam contoh di atas, kita belum menetapkan parameter view yang diteruskan ke onClick(View). Binding pemroses menyediakan dua pilihan untuk parameter pemroses: Anda dapat mengabaikan semua parameter ke metode, atau menamai semua parameter. Jika memilih untuk menamai parameter, Anda dapat menggunakannya dalam ekspresi. Misalnya, ekspresi di atas dapat ditulis sebagai berikut:

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

Atau, jika ingin menggunakan parameter dalam ekspresi, Anda dapat melakukannya sebagai 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 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 klik yang panjang, ekspresi akan 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 ini tidak dapat dievaluasi karena adanya objek null, data binding akan menampilkan nilai default untuk jenis tersebut. Misalnya, null untuk jenis referensi, 0 untuk int, false untuk boolean, dll.

Jika Anda perlu menggunakan ekspresi dengan predikat (misalnya, terner), 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 sangat mudah dibaca. Sebaliknya, pemroses yang berisi ekspresi kompleks menjadikan tata letak Anda sulit dibaca dan dikelola. Ekspresi ini harus semudah meneruskan data yang tersedia dari UI ke metode callback Anda. Anda harus menerapkan logika bisnis apa pun di dalam metode callback yang Anda panggil dari ekspresi pemroses.

Import, variable, dan include

Library Data Binding menyediakan fitur seperti import, variable, dan include. Import memudahkan pereferensian class di dalam file tata letak. Variable memungkinkan Anda mendeskripsikan properti yang dapat digunakan dalam ekspresi binding. Include memungkinkan Anda menggunakan kembali tata letak kompleks di berbagai aplikasi.

Import

Import memungkinkan Anda mereferensikan class di dalam file tata letak dengan mudah, sama seperti dalam kode terkelola. Nol atau lebih elemen import dapat digunakan di dalam elemen data. Contoh kode berikut mengimpor class View ke file tata letak:

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

Dengan mengimpor class View, Anda akan dapat mereferensikannya dari ekspresi binding. Contoh berikut menunjukkan cara mereferensikan konstanta VISIBLE dan GONE dalam 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, salah satu class dapat diganti namanya 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 dapat menggunakan Vista untuk mereferensikan com.example.real.estate.View dan View dapat digunakan untuk mereferensikan android.view.View dalam file tata letak.

Mengimpor class lain

Jenis yang diimpor dapat digunakan 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 juga 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"/>
    

Jenis yang diimpor juga dapat digunakan 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 disetel 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 menerapkan Observable atau merupakan koleksi yang dapat diobservasi, 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 dideskripsikan. Variabel ini mengambil nilai kode terkelola default hingga penyetel dipanggil—null untuk jenis referensi, 0 untuk int, false untuk boolean, dll.

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

Include

Variabel dapat diteruskan ke binding tata letak yang disertakan dari tata letak penampungnya menggunakan namespace aplikasi dan nama variabel dalam sebuah 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 include sebagai turunan langsung dari elemen merge. 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