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<
) 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<String>"/>
<variable name="sparse" type="SparseArray<String>"/>
<variable name="map" type="Map<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 pemrosesnull
. - 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<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.