Bộ chuyển đổi liên kết

Bộ chuyển đổi liên kết chịu trách nhiệm thực hiện các lệnh gọi khung thích hợp đến đặt giá trị. Một ví dụ là đặt giá trị thuộc tính, như gọi thuộc tính Phương thức setText(). Khác Ví dụ: thiết lập trình nghe sự kiện, chẳng hạn như gọi setOnClickListener() .

Thư viện liên kết dữ liệu cho phép bạn chỉ định phương thức được gọi để đặt giá trị, cung cấp logic liên kết của riêng bạn và chỉ định loại đối tượng được trả về bằng cách bằng cách sử dụng bộ chuyển đổi.

Đặt giá trị thuộc tính

Bất cứ khi nào một giá trị ràng buộc thay đổi, lớp liên kết được tạo phải gọi một phương thức setter trên khung hiển thị có biểu thức liên kết. Bạn có thể cho phép Liên kết dữ liệu Thư viện sẽ tự động xác định phương thức hoặc bạn có thể khai báo rõ ràng hoặc cung cấp logic tuỳ chỉnh để chọn một phương thức.

Lựa chọn phương thức tự động

Đối với thuộc tính có tên example, thư viện sẽ tự động tìm phương thức setExample(arg) chấp nhận các loại tương thích làm đối số. Không gian tên thuộc tính đó sẽ không được xem xét. Chỉ sử dụng tên và loại thuộc tính khi tìm kiếm một phương thức.

Ví dụ: với biểu thức android:text="@{user.name}", thư viện tìm phương thức setText(arg) chấp nhận loại được trả về bởi user.getName(). Nếu loại dữ liệu trả về của user.getName()String, thì thư viện sẽ tìm phương thức setText() chấp nhận đối số String. Nếu biểu thức trả về int, thư viện sẽ tìm kiếm phương thức setText() chấp nhận đối số int. Biểu thức phải trả về đúng loại. Bạn có thể truyền giá trị trả về nếu cần.

Liên kết dữ liệu hoạt động ngay cả khi không có thuộc tính nào có tên đã cho. Bạn có thể tạo thuộc tính cho bất kỳ phương thức setter nào bằng cách sử dụng liên kết dữ liệu. Ví dụ: hỗ trợ lớp DrawerLayout không có thuộc tính nào nhưng có nhiều phương thức setter. Bố cục sau đây tự động sử dụng setScrimColor(int)addDrawerListener(DrawerListener) làm phương thức setter cho app:scrimColorapp:drawerListener các thuộc tính tương ứng:

<androidx.drawerlayout.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor="@{@color/scrim}"
    app:drawerListener="@{fragment.drawerListener}">

Chỉ định tên phương thức tuỳ chỉnh

Một số thuộc tính có phương thức setter không khớp theo tên. Trong những trường hợp này, việc có thể được liên kết với phương thức setter bằng cách sử dụng BindingMethods của bạn. Chú giải này được dùng với một lớp và có thể chứa nhiều BindingMethod các chú giải, một chú giải cho mỗi phương thức đã đổi tên. Phương thức liên kết là các chú giải bạn có thể thêm vào bất kỳ lớp học nào trong ứng dụng của mình.

Trong ví dụ sau, thuộc tính android:tint được liên kết với setImageTintList(ColorStateList) chứ không phải bằng phương thức 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"),
})

Thông thường, bạn không cần đổi tên phương thức setter trong các lớp khung Android. Chiến lược phát hành đĩa đơn các thuộc tính đã được triển khai bằng cách sử dụng quy ước tên để tự động tìm phương thức so khớp.

Cung cấp logic tuỳ chỉnh

Một số thuộc tính cần logic liên kết tuỳ chỉnh. Ví dụ: không có phương thức setter cho thuộc tính android:paddingLeft. Thay vào đó, phương thức setPadding(left, top, right, bottom) được cung cấp. Phương thức bộ chuyển đổi liên kết tĩnh với BindingAdapter chú giải này cho phép bạn tuỳ chỉnh cách gọi phương thức setter cho một thuộc tính.

Các thuộc tính của các lớp trong khung Android đã có BindingAdapter các chú thích. Ví dụ sau đây minh hoạ phương thức điều hợp liên kết (binding adapter) cho Thuộc tính 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());
}

Các loại thông số rất quan trọng. Tham số đầu tiên xác định loại thành phần hiển thị đã liên kết với thuộc tính đó. Tham số thứ hai xác định loại được chấp nhận trong biểu thức liên kết của thuộc tính đã cho.

Bộ chuyển đổi liên kết cũng hữu ích cho các loại tuỳ chỉnh khác. Ví dụ: bạn có thể gọi một trình tải tuỳ chỉnh từ một luồng worker để tải hình ảnh.

Bạn cũng có thể có các bộ chuyển đổi nhận nhiều thuộc tính, như minh hoạ trong ví dụ sau:

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);
}

Bạn có thể sử dụng trình chuyển đổi trong bố cục, như trong ví dụ sau. Ghi chú @drawable/venueError tham chiếu đến một tài nguyên trong ứng dụng của bạn. Xung quanh có @{} sẽ làm cho nó trở thành một biểu thức liên kết hợp lệ.

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

Bộ chuyển đổi sẽ được gọi nếu imageUrlerror được dùng cho một Đối tượng ImageView, imageUrl là một và error là một Drawable. Nếu bạn muốn bộ chuyển đổi sẽ được gọi khi bất kỳ thuộc tính nào được thiết lập, hãy đặt thuộc tính requireAll cờ của bộ chuyển đổi sang false, như trong ví dụ sau:

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);
  }
}

Phương thức bộ chuyển đổi liên kết có thể lấy các giá trị cũ trong trình xử lý. Một phương thức việc lấy các giá trị cũ và giá trị mới trước tiên phải khai báo tất cả các giá trị cũ cho các thuộc tính. tiếp theo là các giá trị mới như trong ví dụ sau:

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());
   }
}

Bạn chỉ có thể dùng trình xử lý sự kiện với các giao diện hoặc lớp trừu tượng có một như trong ví dụ sau:

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);
    }
  }
}

Sử dụng trình xử lý sự kiện này trong bố cục của bạn như sau:

<View android:onLayoutChange="@{() -> handler.layoutChanged()}"/>

Khi một trình nghe có nhiều phương thức, bạn phải chia trình nghe đó thành nhiều trình nghe. Ví dụ: View.OnAttachStateChangeListener có hai phương thức: onViewAttachedToWindow(View)onViewDetachedFromWindow(View). Thư viện này cung cấp 2 giao diện để phân biệt các thuộc tính và trình xử lý cho họ:

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);
}

Vì việc thay đổi một trình nghe có thể ảnh hưởng đến trình nghe kia, nên bạn cần có một trình chuyển đổi phù hợp cho một trong hai thuộc tính hoặc cho cả hai. Bạn có thể đặt requireAll thành false trong chú thích để chỉ định rằng không phải mọi thuộc tính đều phải được gán một liên kết như được trình bày trong ví dụ sau:

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);
        }
    }
}

Ví dụ ở trên hơi phức tạp vì Lớp View sử dụng phương thức addOnAttachStateChangeListener()removeOnAttachStateChangeListener() thay vì phương thức setter cho OnAttachStateChangeListener. Lớp android.databinding.adapters.ListenerUtil giúp theo dõi các dữ liệu này trình nghe để có thể xoá chúng trong bộ chuyển đổi liên kết.

Lượt chuyển đổi đối tượng

Tự động chuyển đổi đối tượng

Khi một Object được trả về từ một liên kết thì thư viện sẽ chọn phương thức dùng để đặt giá trị của thuộc tính này. Object được truyền đến một loại tham số của phương thức đã chọn. Chiến dịch này trong các ứng dụng sử dụng Lớp ObservableMap đến lưu trữ dữ liệu, như được thể hiện trong ví dụ sau:

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

Đối tượng userMap trong biểu thức trả về một giá trị, giá trị này sẽ tự động truyền tới loại tham số có trong phương thức setText(CharSequence) dùng để đặt giá trị của thuộc tính android:text. Nếu loại thông số là không rõ ràng, hãy truyền loại dữ liệu trả về trong biểu thức.

Lượt chuyển đổi tuỳ chỉnh

Trong một số trường hợp, bạn phải chuyển đổi tuỳ chỉnh giữa các loại cụ thể. Cho Ví dụ: thuộc tính android:background của khung hiển thị mong đợi có Drawable, nhưng giá trị color được chỉ định là một số nguyên. Ví dụ sau đây cho thấy một thuộc tính dự kiến có Drawable, nhưng thay vào đó, một số nguyên lại được cung cấp:

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

Mỗi khi dự kiến có Drawable và kết quả trả về là một số nguyên, hãy chuyển đổi int thành ColorDrawable. Để thực hiện chuyển đổi, hãy sử dụng phương thức tĩnh với BindingConversion như sau:

Kotlin

@BindingConversion
fun convertColorToDrawable(color: Int) = ColorDrawable(color)

Java

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
    return new ColorDrawable(color);
}

Tuy nhiên, các loại giá trị được cung cấp trong biểu thức liên kết phải nhất quán. Bạn không thể sử dụng các loại khác nhau trong cùng một biểu thức, như được minh hoạ sau đây ví dụ:

// 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"/>

Tài nguyên khác

Để tìm hiểu thêm về liên kết dữ liệu, hãy xem các tài nguyên sau.

Mẫu

Lớp học lập trình

Bài đăng trên blog