Adaptor binding

Adaptor binding bertanggung jawab membuat panggilan framework yang sesuai untuk menetapkan nilai. Salah satu contohnya adalah menetapkan nilai properti seperti memanggil metode setText(). Contoh lainnya adalah menetapkan pemroses peristiwa seperti memanggil metode setOnClickListener().

Library Data Binding memungkinkan Anda menentukan metode yang dipanggil untuk menetapkan nilai, menyediakan logika binding Anda sendiri, dan menentukan jenis objek yang ditampilkan menggunakan adaptor.

Menetapkan nilai atribut

Setiap kali nilai terikat berubah, class binding yang dihasilkan harus memanggil metode setter di tampilan yang memiliki ekspresi binding. Anda dapat mengizinkan Library Data Binding untuk menentukan metode secara otomatis, mendeklarasikan metode tersebut secara eksplisit, atau menyediakan logika kustom untuk memilih metode.

Pemilihan metode otomatis

Untuk atribut dengan nama example, library otomatis mencoba menemukan metode setExample(arg) yang menerima jenis yang kompatibel sebagai argumen. Namespace atribut tidak dipertimbangkan, hanya nama dan jenis atribut yang digunakan saat menelusuri metode.

Misalnya, jika terdapat ekspresi android:text="@{user.name}", library akan mencari metode setText(arg) yang menerima jenis nilai yang ditampilkan oleh user.getName(). Jika jenis nilai yang ditampilkan user.getName() adalah String, library akan mencari metode setText() yang menerima argumen String. Jika ekspresi menampilkan int, library akan menelusuri metode setText() yang menerima argumen int. Ekspresi ini harus menampilkan jenis yang benar agar Anda dapat mentransmisikan nilai yang ditampilkan jika diperlukan.

Data binding berfungsi meskipun tidak ada atribut dengan nama yang ditentukan. Karena itu, Anda dapat membuat atribut untuk semua penyetel menggunakan data binding. Misalnya, class dukungan DrawerLayout tidak memiliki atribut apa pun, tetapi memiliki banyak penyetel. Tata letak berikut otomatis menggunakan metode setScrimColor(int) dan setDrawerListener(DrawerListener) sebagai penyetel untuk atribut app:scrimColor dan app:drawerListener:

<android.support.v4.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, atribut mungkin dikaitkan dengan penyetel menggunakan anotasi BindingMethods. Anotasi ini digunakan dengan sebuah class dan dapat berisi beberapa anotasi BindingMethod, satu anotasi untuk setiap metode yang diganti namanya. Metode binding adalah anotasi yang dapat ditambahkan ke class apa pun di aplikasi Anda. Pada contoh berikut, atribut android:tint dikaitkan dengan metode 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 setter di class framework Android. Atribut tersebut sudah diimplementasikan menggunakan konvensi nama untuk otomatis menemukan metode yang cocok.

Menyediakan logika kustom

Beberapa atribut memerlukan logika binding kustom. Misalnya, tidak ada penyetel yang dikaitkan untuk atribut android:paddingLeft. Sebagai gantinya, metode setPadding(left, top, right, bottom) disediakan. Metode adaptor binding statis dengan anotasi BindingAdapter memungkinkan Anda menyesuaikan cara pemanggilan penyetel untuk sebuah atribut.

Atribut dalam class framework Android sudah memiliki anotasi BindingAdapter yang dibuat. Misalnya, 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 ditentukan.

Adaptor binding berguna untuk jenis penyesuaian lainnya. Misalnya, loader kustom dapat dipanggil dari thread pekerja untuk memuat gambar.

Jika terjadi bentrok, adaptor binding yang Anda tetapkan akan menggantikan adaptor default yang disediakan oleh framework Android.

Anda juga dapat memiliki adaptor yang menerima beberapa atribut, seperti 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 yang ditunjukkan dalam contoh berikut. Perlu diperhatikan bahwa @drawable/venueError merujuk ke resource dalam aplikasi Anda. Mengapit resource dengan @{} membuatnya menjadi ekspresi binding yang valid.

<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />
    

Adaptor akan dipanggil jika imageUrl dan error digunakan untuk objek ImageView dan imageUrl adalah string dan error adalah Drawable. Jika ingin agar adaptor dipanggil saat salah satu atribut ini ditetapkan, Anda dapat menetapkan tanda requireAll opsional adaptor ke false, seperti yang ditunjukkan dalam 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 juga dapat mengambil nilai lama di pengendalinya. Metode yang mengambil nilai lama dan baru harus pertama-tama mendeklarasikan semua nilai lama untuk atribut, lalu diikuti dengan nilai baru, seperti yang ditunjukkan dalam contoh di bawah:

Kotlin

    @BindingAdapter("android:paddingLeft")
    fun setPaddingLeft(view: View, oldPadding: Int, newPadding: Int) {
        if (oldPadding != newPadding) {
            view.setPadding(padding,
                        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 yang memiliki satu metode abstrak, seperti ditunjukkan dalam 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 menyediakan dua antarmuka untuk membedakan atribut dan pengendali atribut:

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 juga dapat memengaruhi pemroses yang lain, diperlukan adaptor yang berfungsi dengan salah satu atau kedua atribut. Anda dapat menetapkan requireAll ke false di anotasi untuk menentukan bahwa tidak setiap atribut harus ditetapkan ke ekspresi 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 lebih rumit daripada biasanya karena class View menggunakan metode addOnAttachStateChangeListener() dan removeOnAttachStateChangeListener(), bukan metode penyetel untuk OnAttachStateChangeListener. Class android.databinding.adapters.ListenerUtil membantu melacak pemroses sebelumnya sehingga dapat dihapus di adaptor binding.

Dengan menganotasi antarmuka OnViewDetachedFromWindow dan OnViewAttachedToWindow dengan @TargetApi(VERSION_CODES.HONEYCOMB_MR1), generator kode data binding mengetahui bahwa pemroses hanya boleh dibuat saat berjalan di Android 3.1 (API level 12) dan yang lebih tinggi, sama dengan versi yang didukung oleh metode addOnAttachStateChangeListener().

Konversi objek

Konversi objek otomatis

Saat sebuah Object ditampilkan dari ekspresi binding, library akan memilih metode yang digunakan untuk menetapkan nilai properti. Object ditransmisikan ke jenis parameter metode yang dipilih. Perilaku ini praktis pada aplikasi yang menggunakan class ObservableMap untuk menyimpan data, seperti yang ditunjukkan dalam contoh berikut:

<TextView
       android:text='@{userMap["lastName"]}'
       android:layout_width="wrap_content"
       android:layout_height="wrap_content" />
    

Objek userMap dalam ekspresi ini menampilkan nilai, yang otomatis ditransmisikan ke jenis parameter yang ditemukan dalam metode setText(CharSequence) yang digunakan untuk menetapkan nilai atribut android:text. Jika jenis parameter ambigu, Anda harus mentransmisikan jenis nilai yang ditampilkan dalam ekspresi tersebut.

Konversi kustom

Dalam beberapa situasi, konversi kustom diperlukan di antara jenis-jenis tertentu. Misalnya, atribut android:background dari sebuah tampilan mengharapkan Drawable, tetapi nilai color yang ditetapkan berupa integer. Contoh berikut menunjukkan atribut yang mengharapkan Drawable, tetapi justru diberi integer:

<View
       android:background="@{isError ? @color/red : @color/white}"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
    

Setiap kali Drawable diharapkan dan integer ditampilkan, int harus dikonversi menjadi ColorDrawable. Konversi dapat dilakukan menggunakan metode statis dengan anotasi BindingConversion, 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 dalam contoh berikut:

<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 tambahan berikut.

Contoh

Codelab

Postingan blog