DataBinding使用指南(四):BindingAdapter

DataBinding使用指南(一):布局和binding表达式

DataBinding使用指南(二):使用可观察的数据对象

DataBinding使用指南(三):生成binding类

DataBinding使用指南(四):BindingAdapter

DataBinding使用指南(五):绑定布局视图到架构组件


版权声明:本文为博主原创文章,欢迎大家转载!

转载请标明出处: http://blog.csdn.net/guiying712/article/details/80411597,本文出自:【张华洋的博客】


BindingAdapter 负责对适当的框架调用设置值。例如设置一个属性值,调用 setText() 方法,另一个例子是设置一个事件监听器,比如调用 setOnClickListener() 方法。

DataBinding库允许我们指定调用的方法来设置值,提供我们自己的绑定逻辑,并通过使用适配器指定返回的对象的类型。

扫描二维码关注公众号,回复: 2837314 查看本文章

设置属性值


每当绑定值发生变化时,生成的绑定类必须使用绑定表达式在视图上调用 setter 方法,我们可以允许DataBinding库自动确定方法,显式声明方法或提供自定义逻辑来选择方法。

自动选择方法

对于名为example的属性,DataBinding库会自动尝试查找接受兼容类型作为参数的setExample(arg)方法,不考虑属性的命名空间,只有在搜索方法时使用属性的名称和类型。

例如,给定android:text="@{user.name}"表达式,DataBinding库将查找接受user.getName()返回的类型的setText(arg)方法;如果user.getName()的返回类型是String,则库将查找接受String参数的setText()方法;如果该表达式返回一个int,则库将搜索接受int参数的setText()方法,表达式必须返回正确的类型,我们也可以根据需要强制返回值。

即使给定名称不存在任何属性,DataBinding也可以工作。我们可以使用DataBinding为任何setter创建属性。例如,DrawerLayout没有任何属性,但有很多setter,以下布局将自动使用setScrimColor(int)setDrawerListener(DrawerListener)方法作为app:scrimColorapp:drawerListener属性,分别为:

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

指定自定义方法名称

一些属性具有不匹配名称的setter,在这些情况下,可以使用BindingMethods注解将一个属性与setter相关联,注解可以被应用到类上,并且可以包含多个BindingMethods注解,每个重命名的方法一个注解,在我们应用中的任何类都可以添加BindingMethods注解。在下面示例中,android:tint属性与setImageTintList(ColorStateList)方法关联,而不与setTint()方法关联:

@BindingMethods({
       @BindingMethod(type = "android.widget.ImageView",
                      attribute = "android:tint",
                      method = "setImageTintList"),
})

大多数情况下,我们不需要重命名Android框架类中的setter,这些属性已经实现了使用名称约定来自动查找匹配方法。

提供自定义逻辑

一些属性需要自定义绑定逻辑,例如,android:paddingLeft属性没有关联的setter,但是提供了setPadding(left, top, right, bottom)方法。使用BindingAdapter注解的静态绑定适配器方法允许我们自定义如何调用属性的setter。

Android框架类的属性已经创建了BindingAdapter注解,例如,以下示例显示paddingLeft属性的绑定适配器:

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
  view.setPadding(padding,
                  view.getPaddingTop(),
                  view.getPaddingRight(),
                  view.getPaddingBottom());
}

绑定适配器方法的参数类型很重要,第一个参数确定与属性关联的View类型,第二个参数确定给定属性的绑定表达式中接受的参数类型。

绑定适配器对自定义其他类型很有用,例如,可以从工作线程调用自定义加载器来加载图像。

我们定义的绑定适配器会在发生冲突时覆盖Android框架提供的默认适配器。

我们还可以让适配器接收多个属性,如下所示:

@BindingAdapter({"imageUrl", "error"})
public static void loadImage(ImageView view, String url, Drawable error) {
  Picasso.with(view.getContext()).load(url).error(error).into(view);
}

我们还可以在布局中使用适配器,如下例所示:

注意,@drawable/venueError是指应用中的资源,用@{}括住资源使其成为有效的绑定表达式。

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

注意:DataBinding库会忽略用于匹配目的的自定义命名空间。

如果 imageUrlerror 都用于 ImageView 对象并且imageUrl是字符串并且 errorDrawable,则调用适配器。

如果我们希望在设置任何属性时调用适配器,则可以将适配器的可选标志 requireAll 设置为 false,如下例所示:

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

注意:当发生冲突时,绑定适配器会覆盖默认的数据绑定适配器。

绑定适配器方法可以在其处理程序中选择使用旧值。一个方法采用旧值和新值应首先声明属性的所有旧值,然后再声明新值,如下例所示:

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

事件处理程序只能用于一个抽象类和接口的抽象方法,如下例所示:

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

在布局中使用此事件处理程序,如下所示:

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

当一个监听器有多个方法时,它必须被拆分成多个监听器。例如,View.OnAttachStateChangeListener 有两个方法: onViewAttachedToWindow(View)onViewDetachedFromWindow(View),我们必须创建两个接口来区分它们的属性和处理程序,如下所示:

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
  void onViewDetachedFromWindow(View v);
}

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
  void onViewAttachedToWindow(View v);
}

因为更改一个监听器也会影响另一个监听器,所以我们需要一个适用于任一属性或两者都适用的适配器。我们可以在注解中将 requireAll 设置为 false,以指定不是每个属性都必须分配一个绑定表达式,如下例所示:

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

上面的例子比正常情况稍微复杂一点,因为 View 类使用 addOnAttachStateChangeListener( )removeOnAttachStateChangeListener( )方法,而不是 OnAttachStateChangeListener 的 setter 方法。android.databinding.adapters.ListenerUtil 类有助于跟踪以前的监听器,它们可能会在绑定适配器中被删除。

转换对象


自动转换对象

当从绑定表达式返回 Object 时,DataBinding库选择用于设置属性值的方法,该 Object 被转换为所选方法的参数类型,使用 ObservableMap 类存储数据的应用中,此行为很方便,如下例所示:

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

注意:我们也可以使用 object.key 表示法引用 map 中的值。例如,上述示例中的 @{userMap["lastName"]} 可以替换为@{userMap.lastName} 。

表达式中的 userMap 对象返回一个值,该值自动转换为用于设置 android:text 属性值的 setText(CharSequence) 方法中找到的参数类型,如果参数类型不明确,则必须在表达式中转换返回类型。

自定义转换

在某些情况下,特定类型之间需要自定义转换。例如,View 的 android:background 属性需要 Drawable,但指定的颜色值是一个整数。以下示例显示了一个需要 Drawable 的属性,但是提供了一个整数:

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

每当需要一个 Drawable 并返回一个整数时,该 int 应该转换为一个 ColorDrawable,转换可以使用带有 BindingConversion 注解的静态方法完成,如下所示:

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

但是,绑定表达式中提供的值类型必须一致,不能在同一个表达式中使用不同的类型,如以下示例所示:

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

本文最近更新日期: 2018年4月26日。

DataBinding使用指南(一):布局和binding表达式

DataBinding使用指南(二):使用可观察的数据对象

DataBinding使用指南(三):生成binding类

DataBinding使用指南(四):BindingAdapter

DataBinding使用指南(五):绑定布局视图到架构组件

猜你喜欢

转载自blog.csdn.net/guiying712/article/details/80411597
今日推荐