Adaptor binding bertanggung jawab untuk membuat panggilan framework yang sesuai ke tetapkan nilai. Salah satu contohnya adalah menetapkan nilai properti, seperti memanggil Metode setText()
. Lainnya misalnya adalah menyetel pemroses peristiwa, seperti memanggil setOnClickListener()
.
Library Data Binding memungkinkan Anda menentukan metode yang dipanggil untuk menetapkan nilai, menyediakan logika binding Anda sendiri, dan menentukan jenis objek yang ditampilkan dengan menggunakan adaptor.
Menetapkan nilai atribut
Setiap kali nilai terikat berubah, class binding yang dihasilkan harus memanggil penyetel pada tampilan dengan ekspresi binding. Anda dapat membiarkan Data Binding Library secara otomatis menentukan metode, atau Anda dapat secara eksplisit mendeklarasikan atau menyediakan logika khusus untuk memilih metode.
Pemilihan metode otomatis
Untuk atribut bernama example
, library otomatis menemukan metode tersebut setExample(arg)
yang menerima jenis yang kompatibel sebagai argumen. Namespace atribut ini tidak dipertimbangkan. Hanya nama dan jenis atribut yang digunakan saat mencari metode.
Misalnya, dengan ekspresi android:text="@{user.name}"
, library mencari metode setText(arg)
yang menerima jenis yang ditampilkan oleh user.getName()
. Jika jenis nilai yang ditampilkan user.getName()
adalah String
, library mencari metode setText()
yang menerima argumen String
. Jika menampilkan int
, library akan menelusuri metode setText()
yang menerima argumen int
. Ekspresi harus menampilkan jenis yang benar. Anda dapat gunakan nilai yang ditampilkan jika perlu.
Data binding berfungsi meskipun tidak ada atribut dengan nama yang ditentukan. Anda dapat membuat atribut untuk penyetel apa pun dengan menggunakan pengikatan data. Misalnya, dukungan kelas DrawerLayout
tidak memiliki atribut, tetapi memiliki banyak penyetel. Tata letak berikut secara otomatis menggunakan setScrimColor(int)
dan addDrawerListener(DrawerListener)
metode sebagai penyetel untuk app:scrimColor
dan app:drawerListener
masing-masing:
<androidx.drawerlayout.widget.DrawerLayout android:layout_width="wrap_content" android:layout_height="wrap_content" app:scrimColor="@{@color/scrim}" app:drawerListener="@{fragment.drawerListener}">
Menentukan nama metode kustom
Beberapa atribut memiliki penyetel yang tidak melakukan pencocokan berdasarkan nama. Dalam situasi ini, dapat dikaitkan dengan penyetel menggunakan BindingMethods
anotasi. Anotasi digunakan dengan class dan dapat berisi beberapa BindingMethod
anotasi, satu untuk setiap metode yang diganti namanya. Metode binding adalah anotasi yang yang dapat Anda tambahkan ke kelas mana pun di aplikasi Anda.
Pada contoh berikut, atribut android:tint
dikaitkan dengan setImageTintList(ColorStateList)
—bukan dengan metode setTint()
:
Kotlin
@BindingMethods(value = [ BindingMethod( type = android.widget.ImageView::class, attribute = "android:tint", method = "setImageTintList")])
Java
@BindingMethods({ @BindingMethod(type = "android.widget.ImageView", attribute = "android:tint", method = "setImageTintList"), })
Biasanya, Anda tidak perlu mengganti nama penyetel di class framework Android. Tujuan sudah diimplementasikan menggunakan konvensi nama untuk menemukan metode yang cocok.
Menyediakan logika kustom
Beberapa atribut memerlukan logika binding kustom. Misalnya, tidak ada data terkait penyetel untuk atribut android:paddingLeft
. Sebagai gantinya, metode setPadding(left, top, right, bottom)
disediakan. Metode adaptor binding statis dengan BindingAdapter
memungkinkan Anda menyesuaikan cara penyetel dipanggil.
Atribut class framework Android sudah memiliki BindingAdapter
anotasi. Contoh berikut menunjukkan adaptor binding untuk Atribut paddingLeft
:
Kotlin
@BindingAdapter("android:paddingLeft") fun setPaddingLeft(view: View, padding: Int) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()) }
Java
@BindingAdapter("android:paddingLeft") public static void setPaddingLeft(View view, int padding) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); }
Jenis parameter itu penting. Parameter pertama menentukan jenis tampilan yang terkait dengan atribut. Parameter kedua menentukan jenis yang diterima dalam ekspresi binding untuk atribut yang diberikan.
Adaptor binding juga berguna untuk jenis penyesuaian lainnya. Misalnya, loader kustom bisa dipanggil dari thread pekerja untuk memuat gambar.
Anda juga bisa memiliki adaptor yang menerima beberapa atribut, seperti yang ditunjukkan dalam contoh berikut:
Kotlin
@BindingAdapter("imageUrl", "error") fun loadImage(view: ImageView, url: String, error: Drawable) { Picasso.get().load(url).error(error).into(view) }
Java
@BindingAdapter({"imageUrl", "error"}) public static void loadImage(ImageView view, String url, Drawable error) { Picasso.get().load(url).error(error).into(view); }
Anda dapat menggunakan adaptor di tata letak, seperti ditunjukkan dalam contoh berikut. Catatan bahwa @drawable/venueError
merujuk ke resource dalam aplikasi Anda. Mengelilingi resource dengan @{}
akan menjadikannya ekspresi binding yang valid.
<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />
Adaptor dipanggil jika imageUrl
dan error
digunakan untuk Objek ImageView
, imageUrl
adalah dan error
adalah Drawable
Jika Anda ingin adaptor yang akan dipanggil saat ada atribut yang diatur, atur atribut requireAll
flag adaptor ke false
, seperti yang ditunjukkan pada contoh berikut:
Kotlin
@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = false) fun setImageUrl(imageView: ImageView, url: String?, placeHolder: Drawable?) { if (url == null) { imageView.setImageDrawable(placeholder); } else { MyImageLoader.loadInto(imageView, url, placeholder); } }
Java
@BindingAdapter(value={"imageUrl", "placeholder"}, requireAll=false) public static void setImageUrl(ImageView imageView, String url, Drawable placeHolder) { if (url == null) { imageView.setImageDrawable(placeholder); } else { MyImageLoader.loadInto(imageView, url, placeholder); } }
Metode adaptor binding dapat mengambil nilai lama di pengendalinya. Sebuah metode mengambil nilai lama dan baru harus mendeklarasikan semua nilai lama untuk atribut terlebih dahulu, diikuti dengan nilai baru, seperti yang ditunjukkan dalam contoh berikut:
Kotlin
@BindingAdapter("android:paddingLeft") fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) { if (oldPadding != newPadding) { view.setPadding(newPadding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()) } }
Java
@BindingAdapter("android:paddingLeft") public static void setPaddingLeft(View view, int oldPadding, int newPadding) { if (oldPadding != newPadding) { view.setPadding(newPadding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); } }
Pengendali peristiwa hanya dapat digunakan dengan antarmuka atau class abstrak dengan satu abstrak, seperti yang ditunjukkan pada contoh berikut:
Kotlin
@BindingAdapter("android:onLayoutChange") fun setOnLayoutChangeListener( view: View, oldValue: View.OnLayoutChangeListener?, newValue: View.OnLayoutChangeListener? ) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (oldValue != null) { view.removeOnLayoutChangeListener(oldValue) } if (newValue != null) { view.addOnLayoutChangeListener(newValue) } } }
Java
@BindingAdapter("android:onLayoutChange") public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue, View.OnLayoutChangeListener newValue) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (oldValue != null) { view.removeOnLayoutChangeListener(oldValue); } if (newValue != null) { view.addOnLayoutChangeListener(newValue); } } }
Gunakan pengendali peristiwa ini dalam tata letak Anda sebagai berikut:
<View android:onLayoutChange="@{() -> handler.layoutChanged()}"/>
Jika memiliki beberapa metode, suatu pemroses harus dipecah menjadi beberapa pemroses. Misalnya, View.OnAttachStateChangeListener
memiliki dua metode: onViewAttachedToWindow(View)
dan onViewDetachedFromWindow(View)
. Library ini menyediakan dua antarmuka untuk membedakan atribut dan pengendali untuk mereka:
Kotlin
// Translation from provided interfaces in Java: @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) interface OnViewDetachedFromWindow { fun onViewDetachedFromWindow(v: View) } @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) interface OnViewAttachedToWindow { fun onViewAttachedToWindow(v: View) }
Java
@TargetApi(VERSION_CODES.HONEYCOMB_MR1) public interface OnViewDetachedFromWindow { void onViewDetachedFromWindow(View v); } @TargetApi(VERSION_CODES.HONEYCOMB_MR1) public interface OnViewAttachedToWindow { void onViewAttachedToWindow(View v); }
Karena mengubah satu pemroses bisa memengaruhi yang lain, Anda memerlukan adaptor yang berfungsi untuk salah satu atribut atau keduanya. Anda dapat menetapkan requireAll
ke false
di anotasi untuk menentukan bahwa tidak setiap atribut harus diberi binding seperti yang ditunjukkan dalam contoh berikut:
Kotlin
@BindingAdapter( "android:onViewDetachedFromWindow", "android:onViewAttachedToWindow", requireAll = false ) fun setListener(view: View, detach: OnViewDetachedFromWindow?, attach: OnViewAttachedToWindow?) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { val newListener: View.OnAttachStateChangeListener? newListener = if (detach == null && attach == null) { null } else { object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) { attach.onViewAttachedToWindow(v) } override fun onViewDetachedFromWindow(v: View) { detach.onViewDetachedFromWindow(v) } } } val oldListener: View.OnAttachStateChangeListener? = ListenerUtil.trackListener(view, newListener, R.id.onAttachStateChangeListener) if (oldListener != null) { view.removeOnAttachStateChangeListener(oldListener) } if (newListener != null) { view.addOnAttachStateChangeListener(newListener) } } }
Java
@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"}, requireAll=false) public static void setListener(View view, OnViewDetachedFromWindow detach, OnViewAttachedToWindow attach) { if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) { OnAttachStateChangeListener newListener; if (detach == null && attach == null) { newListener = null; } else { newListener = new OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { if (attach != null) { attach.onViewAttachedToWindow(v); } } @Override public void onViewDetachedFromWindow(View v) { if (detach != null) { detach.onViewDetachedFromWindow(v); } } }; } OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view, newListener, R.id.onAttachStateChangeListener); if (oldListener != null) { view.removeOnAttachStateChangeListener(oldListener); } if (newListener != null) { view.addOnAttachStateChangeListener(newListener); } } }
Contoh di atas sedikit rumit karena Class View
menggunakan addOnAttachStateChangeListener()
dan removeOnAttachStateChangeListener()
metode alih-alih metode penyetel untuk OnAttachStateChangeListener
. Class android.databinding.adapters.ListenerUtil
membantu memantau pemroses sehingga dapat dihapus di adaptor binding.
Konversi objek
Konversi objek otomatis
Saat Object
ditampilkan dari binding ekspresi tersebut, pustaka akan memilih metode yang digunakan untuk menyetel nilai saat ini. Object
ditransmisikan ke jenis parameter metode yang dipilih. Ini lebih praktis dalam aplikasi yang menggunakan Class ObservableMap
ke menyimpan data, seperti yang ditunjukkan pada contoh berikut:
<TextView android:text='@{userMap["lastName"]}' android:layout_width="wrap_content" android:layout_height="wrap_content" />
Objek userMap
dalam ekspresi menampilkan nilai, yang secara otomatis transmisi ke jenis parameter yang ditemukan dalam metode setText(CharSequence)
yang digunakan untuk tetapkan nilai atribut android:text
. Jika jenis parameternya adalah ambigu, mentransmisikan jenis nilai yang ditampilkan dalam ekspresi.
Konversi kustom
Dalam beberapa situasi, konversi kustom diperlukan di antara jenis-jenis tertentu. Sebagai contoh, atribut android:background
tampilan mengharapkan Drawable
, tetapi nilai color
yang ditentukan adalah bilangan bulat. Contoh berikut menunjukkan yang mengharapkan Drawable
, tetapi yang diberikan adalah bilangan bulat:
<View android:background="@{isError ? @color/red : @color/white}" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
Setiap kali Drawable
diharapkan dan bilangan bulat ditampilkan, konversikan int
ke ColorDrawable
. Untuk melakukan konversi, gunakan metode statis dengan BindingConversion
anotasi, sebagai berikut:
Kotlin
@BindingConversion fun convertColorToDrawable(color: Int) = ColorDrawable(color)
Java
@BindingConversion public static ColorDrawable convertColorToDrawable(int color) { return new ColorDrawable(color); }
Namun, jenis nilai yang diberikan dalam ekspresi binding harus konsisten. Anda tidak dapat menggunakan jenis yang berbeda dalam ekspresi yang sama, seperti yang ditunjukkan di bawah contoh:
// The @drawable and @color represent different value types in the same // expression, which causes a build error. <View android:background="@{isError ? @drawable/error : @color/white}" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
Referensi lainnya
Untuk mempelajari data binding lebih lanjut, lihat referensi berikut.
Contoh
Codelab
Postingan blog
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Library Data Binding
- Tata letak dan ekspresi binding
- View binding