Adaptor binding

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