12/08/2018, 14:26

Custom Setters trong Android Data Binding

Khi sử dụng databinding trong android, chúng ra thường bind data vào trong android:text với TextView, Edittext, android:checked với CheckBox. Những thuộc tính được định nghĩa sẵn này sử dụng khá tốt, và có cả tính năng để liên kết với các event. Tuy nhiên khi bạn tự custom view, thì chỉ riêng những ...

Khi sử dụng databinding trong android, chúng ra thường bind data vào trong android:text với TextView, Edittext, android:checked với CheckBox. Những thuộc tính được định nghĩa sẵn này sử dụng khá tốt, và có cả tính năng để liên kết với các event. Tuy nhiên khi bạn tự custom view, thì chỉ riêng những thuộc tính có sẵn đó sẽ không đủ để sử dụng, vậy làm sao để tự custom các setting logic của riêng mình.

Binding To Setters

Ứng dụng của tôi có một custom View, ColorPicker:

public class ColorPicker extends View {
    private int color;

    public void setColor(int color) {
        this.color = color;
        invalidate();
    }

    public int getColor() {
        return color;
    }
    public void addListener(OnColorChangeListener listener) {
        //...
    }
    public void removeListener(OnColorChangeListener listener) {
        //...
    }
    //...
}

Tôi muốn set color cho nó sử dụng data binding. Ở đây không cần phải code thêm gì cả, data binding sẽ tự động tìm kiếm setter với tên được cung cấp trước đó, như là một thuộc tính của customView

<com.example.myapp.ColorPicker
    android:layout_awidth="wrap_content"
    android:layout_height="wrap_content"
    app:color="@{color}"/>

Binding Adapters

Một số trường hợp tôi muốn làm một số thứ phức tạp hơn, không chỉ đơn giản là gọi một setter trong View. Một ví dụ ưa thích của tôi là loading image dưới background. Đầu tiên, tôi cần chọn thuộc tính cần custom cho ImageView:

  <ImageView
     android:layout_awidth="wrap_content"
     android:layout_height="wrap_content"
     app:imageUrl="@{product.imageUrl}"/>

Nếu tôi không làm gì cả, data binding sẽ tìm kiếm method "setImageUrl(String)" trong ImageView và không tìm được. Tôi phải tạo một thuộc tính để set "app:imageUrl". Binding adapter là những method với annotation @BindingAdapter trong bất kì class nào bạn muốn sử dụng. Thông thường sẽ tổ chức các adapters vào một class dựa theo từng viewType.

Tôi sẽ không đi vào cụ thể một thư viện load ảnh nào cả, giả sử ở một trường hợp tổng quát, binding adapter sẽ như thế này:

  @BindingAdapter("imageUrl")
 public static void setImageUrl(ImageView imageView, String url) {
     if (url == null) {
         imageView.setImageDrawable(null);
     } else {
         MyImageLoader.loadInto(imageView, url);
     }
 }

BindingAdapter annotation sẽ lấy tên của thuộc tính làm param của nó. Với bất kỳ namespace nào khác "android", bạn không cần phải khai báo nó ở trong phần param của BindingAdapter annotation, nhưng nếu sử dụng android namespace, bạn phải để tên đầy đủ của nó (bao gồm "android:")

Bên dưới annotation, trong phần method, param thứ nhất là View, param thứ 2 là giá trị sẽ được set vào trong thuộc tính

MyImageLoader sẽ load ảnh ở dưới background và set ảnh vào ImageView khi hoàn thành. Trong khoảng thời gian đó, ImageView vẫn sẽ trống vì vậy trường hợp này tôi muốn có một placeholder ở đó

    <ImageView
      android:layout_awidth="wrap_content"
      android:layout_height="wrap_content"
      app:imageUrl="@{product.imageUrl}"
      app:placeholder="@{@drawable/shadowAvatar}"/>

Bây giờ để làm được điều này chúng ta sẽ sử dụng custom adapter với 2 thuộc tính. Ở đây, nếu ảnh đã được load xong rồi, tôi không muốn hiển thị placeholder nữa. Khi ảnh đang load, tôi muốn có hiển thị một ảnh cross-fade placeholder. Vì vậy binding adapter của chúng ta cần phải xử lý được với nhiều thuộc tính

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

Khi xử lý với nhiều thuộc tính, các thuộc tính phải được đặt bên trong list. Các param của method ở phía dưới sẽ có cùng thứ tự với chúng. Ở đây tôi set requireAll là false. Nếu tôi đổi nó thành true, binding adapter sẽ yêu cầu cả URL và placeholder để có thể hoạt động. Với giá trị set bằng false, nếu một trong hai giá trị URL hay placeholder không được set, binding adapter vẫn có thể gọi được, khi đó giá trị default (trong trường hợp này là null) sẽ được set cho các thuộc tính bạn không set

Với binding adapter này, MyImageLoader có thể làm tất cả những gì có thể với 2 thuộc tính URL và placeholder được cung cấp.

Nguồn bài viết

https://medium.com/google-developers/android-data-binding-custom-setters-55a25a7aea47#.9nesberen

0