深刻源码学习 android data binding 之:data binding 注解

虽然没有开通专栏以前已经在挖金投稿过了这篇文章,可是我打算写一个关于android data binding库的一系列的文章,为了完整性,我仍是在这里从新发布一遍。若是以前已经看过这篇android data binding 实践之:data binding 注解,那么能够忽略下面的内容,若是你喜欢的话能够收藏也能够点赞哦!html


其实在android data binding这个库里面核心的内容我以为就是下面几个:java

  • 定义的一系列方便使用的注解
  • 注解处理器的逻辑(这部分代码量很是庞大其实)
  • 解决监听和回调的机制

上面这几个核心内容我都会在接下来的文章中讨论,几天先把几个关键的注解的使用进行简单的分析。喜欢的能够点赞能够收藏哦!android

Bindable

使用场景

data binding的意义主要数据的变更能够自动触发UI界面的刷新。可是若是咱们使用的是传统的java bean对象的时候,是没有办法实现“数据变动触发ui界面”的目的的。而 Bindable 注解就是帮助咱们完成这个任务的。express

若是咱们要实现“数据变动触发ui界面”的话,途径主要有两个:数组

  1. 继承 BaseObservable ,使用 Bindable 注解field的getter而且在调用setter的使用使用 OnPropertyChangedCallback#onPropertyChanged
  2. 使用data-binding library当中提供的诸如 ObservableField<> , ObservableInt做为属性值

代码定义

/** * The Bindable annotation should be applied to any getter accessor method of an * {@link Observable} class. Bindable will generate a field in the BR class to identify * the field that has changed. * * @see OnPropertyChangedCallback#onPropertyChanged(Observable, int) */
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME) // this is necessary for java analyzer to work
public @interface Bindable {

}复制代码

使用解析

根据上面代码中的注释咱们能够知道,Bindable 是对继承了 Observable 的类当中的 getter 方法进行注解。而且当咱们使用了这个注解的时候,databinding library 会在 BR 这个类当中生成一个属性,用以标识发生了变化的属性fieldapp

使用示例

public class PrivateUser extends BaseObservable{

    private String fistName;
    private String lastName;

    public PrivateUser(String fistName, String lastName) {
        this.age = age;
        this.fistName = fistName;
        this.lastName = lastName;
    }

    @Bindable
    public String getFistName() {
        return fistName;
    }

    public void setFistName(String fistName) {
        this.fistName = fistName;
        notifyPropertyChanged(BR.fistName);
    }

    @Bindable
    public String getLastName() {
        return lastName;
    }

    public void setLasetName(String lasetName) {
        this.lastName = lastName;
        notifyPropertyChanged(BR.lastName);

    }
  }复制代码

使用心得

  1. 根据 Bindable 的定义能够发现,Bindable 是支持对属性进行注解的,因此当咱们的属性是public的(不须要经过getter进行访问)的时候,是能够在属性上面使用该注解的。可是改变属性的值的时候是必定要调用 onPropertyChanged() 这个方法的,不然没法实现通知刷新UI的功能;
  2. 上面频繁出现的 BR 这个类是在编译的时候生成的,使用的时候可能会发现ide没办法找到咱们的属性,好比BR.lastName ,只要rebuild一下就能够了。

BindingAdapter

使用场景

当咱们使用data binding的时候,data-binding library会尽量的找到给view对应的属性(Attribute)进行赋值的方法,一般这个方法的名称就是set${Attribute}。这时候属性前面的命名空间会被忽略,而只关注属性的名称。好比咱们数据绑定了TextView的 android:text 属性,那么data-binding library会去寻找 setText(String) 这样的一个方法。框架

BindingAdapter 注解的方法可以控制给view赋值的操做过程,也就是说能够实现自定义setter的实现。对于那些不具有对应setter方法的属性(好比咱们要绑定一个 android:paddingLeft 的属性的时候,咱们却只有 setPadding(left, top, right, bottom) 这样的一个setter方法),那么咱们能够经过BindingAdapter 注解来实现一个自定义的setter;好比咱们但愿给ImageView设置了一个字符串URL的值的时候,ImageView可以根据这个URL进行自主的联网下载图片的操做。ide

此外咱们还能够覆盖原有的setter方法的逻辑,好比咱们使用 BindingAdapter 的时候参数传入的是 android:text ,那么咱们方法的实现逻辑就会复写原来的setText(String)的方法逻辑了布局

代码定义

@Target(ElementType.METHOD)
public @interface BindingAdapter {

    /** * @return The attributes associated with this binding adapter. */
    String[] value();

    /** * Whether every attribute must be assigned a binding expression or if some * can be absent. When this is false, the BindingAdapter will be called * when at least one associated attribute has a binding expression. The attributes * for which there was no binding expression (even a normal XML value) will * cause the associated parameter receive the Java default value. Care must be * taken to ensure that a default value is not confused with a valid XML value. * * @return whether or not every attribute must be assigned a binding expression. The default * value is true. */
    boolean requireAll() default true;
}复制代码

使用解析

  • 使用了 BindingAdapter 注解的方法可以控制给view赋值的操做过程。注解当中的参数就是咱们须要关联绑定的属性址。关于这一点,官方代码注释里给了一个至关简单的实例:
@BindingAdapter("android:bufferType")
public static void setBufferType(TextView view, TextView.BufferType bufferType) {
    view.setText(view.getText(), bufferType);
}复制代码

在上面的例子中,只要咱们的TextView中设置了 android:bufferType 这个属性,那么 setBufferType 这个方法就会被回调学习

  • 在使用了 BindingAdapter 注解的方法中咱们还能够拿到该属性设置的以前的value值,关于这一点,官方的注释中一样给出了一个简单的实例:
@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);
        }
    }
}复制代码
  • BindingAdapter 还能够设置多个属性参数。当咱们设置了多个属性参数以后,只有当全部的属性参数都被设置使用的时候才会回调咱们的方法。例如:
@BindingAdapter({"android:onClick", "android:clickable"})
public static void setOnClick(View view, View.OnClickListener clickListener, boolean clickable) {
    view.setOnClickListener(clickListener);
    view.setClickable(clickable);
}复制代码

当咱们同时设置了多个属性的时候,要注意参数的顺序问题,方法当中的属性值参数顺序必须跟注解当中的属性参数顺序一致

  • 在上面的例子中,BindingAdapter 注解的都是类的方法,实际上实例方法咱们也是可使用这个注解的。当咱们注解实例方法的时候,生成的 DataBindingComponent 会拥有一个getter方法得到注解方法所在的类的对象的实例

使用示例

官方代码注释中使用示例比较多,并且也很全面,这里主要展现一下使用 BindingAdapter 注解的实例方法的使用。

首先是咱们的XML布局文件:

<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">

    <data >

        <variable name="privateUser" type="net.uni_unity.databindingdemo.model.PrivateUser" />
    </data>

    <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">

        <TextView app:text="@{privateUser}" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="5dp" />
    </LinearLayout>
</layout>复制代码

接下来是咱们要建立一个对象,里面包含了咱们的 BindingAdapter 注解的方法:

public class MyBindindAdapter {

    @BindingAdapter("app:text")
    public void setText(View textView, PrivateUser user) {
        Log.d("setText", "isCalled");
    }
}复制代码

而后是咱们的activity:

public class MainActivity extends AppCompatActivity {

    private PrivateUser user;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //经过DataBindingUtil加载咱们的布局文件
        //在这里咱们实例化了一个咱们定义的DataBindingComponent对象MyComponent
        //这个是很是关键的一个地方,不然系统会使用DataBindingUtil#getDefaultComponent()拿到的默认实例做为参数
        //若是咱们在此以前没有调用DataBindingUtil.setDefaultComponent()方法,上面的方法就会返回null
        ActivityMainBinding activityBinding = DataBindingUtil.setContentView(this, R.layout.activity_main,new MyComponent());
        //将view与数据进行绑定
        user = new PrivateUser("privateFistName", "privateLastName", "private_user", 10);
        activityBinding.setPrivateUser(user);
    }复制代码

接下来就是咱们实现了 DataBindingComponent 的对象

public class MyComponent implements android.databinding.DataBindingComponent {

    @Override
    public MyBindindAdapter getMyBindindAdapter() {
        return new MyBindindAdapter();
    }
}复制代码

咱们会发现 getMyBindindAdapter() 这个方法是带有 @Override 注解的,这是由于咱们使用 BindingAdapter 注解一个实例方法的时候,data-binding library都会为咱们在 DataBindingComponent 的实现中自动生成一个getter方法。

通过上面几步咱们就完成了使用 BindingAdapter 注解实例方法。其中的 关键的环节有两个:

  1. 实现 DataBindingComponent 这个接口;
  2. 经过 DataBindingUtil.setDefaultComponent(); 方法设置咱们的 DataBindingComponent 实例

使用心得

由于官方的代码注释中给出了很多的实例,这里主要说一下咱们使用 BindingAdapter 注解的时候要注意的问题

  1. BindingAdapter 注解的方法能够定义在任何位置。无论咱们的方法是实例方法仍是类方法,也无论这些方法定义在哪一个类里面,只要咱们加上了BindingAdapter 这个注解,只要参数匹配,被注解的方法就会被回调;
  2. BindingAdapter 注解的方法能够选择性的使用一个 DataBindingComponent 的实现类实例做为第一个参数,默认状况下DataBindingUtil里面的相关inflate方法会使用 DataBindingUtil#getDefaultComponent()拿到的对象;
  3. BindingAdapter 是如何起做用的呢?其实 关键的地方主要是两点

    • BindingAdapter 注解定义的参数,好比@BindingAdapter("android:onLayoutChange")

      BindingAdapter 当中的参数的命名空间能够随便定义,咱们可使用“android”,固然也可使用“app”,只要参数的值跟布局文件中定义的值是同样的就能够起做用;

    • 方法当中的定义的参数,好比setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,View.OnLayoutChangeListener newValue)

      方法当中第一个参数对应的就是咱们绑定的view对象,能够是具体的咱们绑定的view,好比TextView,也能够是对应的父类,好比说view。第二/三个参数对应的是绑定的属性对应的value值,并且这个value值的类型要跟属性里面使用的value值是一致的,不然会不起做用,甚至是报错。例如:

      我在布局文件中写了下面的一段代码:

      <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
      
      <data >
        <variable name="privateUser" type="net.uni_unity.databindingdemo.model.PrivateUser" />
      </data>
      
      <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
      
        <TextView app:text="@{privateUser}" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="5dp" />
        </LinearLayout>
      </layout>复制代码

      而后我在activity当中使用 BindingAdapter 注解了这样的一个方法:

      @BindingAdapter("app:text")
      public static void setText(View textView, String user) {
        Log.d("setText", "isCalled");
      }复制代码

      这个时候咱们编译的时候就会收到这样的error:

      java.lang.RuntimeException: Found data binding errors.
      / data binding error msg:Cannot find the setter for attribute 'app:text' with parameter type net.uni_unity.databindingdemo.model.PrivateUser on android.widget.TextView. file:/DataBindingDemo/app/src/main/res/layout/activity_main.xml loc:29:24 - 29:34 \ data binding error

      由于咱们在方法中定义的参数类型是String ,但是咱们在XML文件中定义的类型是 PrivateUser ,所以就提示咱们找不到参数匹配的setter方法了。

      若是咱们把方法中参数随便去掉一个,好比咱们定义以下:

      @BindingAdapter("app:text")
      public static void setText(View textView) {
        Log.d("setText", "isCalled");
      }复制代码

      这时候咱们就会获得另外一个编译错误:

      java.lang.RuntimeException: failure, see logs for details.@BindingAdapter setSimpleText(java.lang.String) has 1 attributes and 0 value parameters. There should be 1 or 2 value parameters.

      上面的两个错误示例大概可以让咱们明白 BindingAdapter 是怎样工做的了。

BindingConversion

使用场景

当咱们须要给view绑定的数据类型和view对应属性的目标类型不一致的时候,咱们一般的作法是先把数据类型转换以后再与view进行绑定。可是咱们使用了 BindingConversion 注解以后,就能够定义一个转换器实如今调用setter方法设置属性值的时候对数据进行转换。

代码定义

/** * Annotate methods that are used to automatically convert from the expression type to the value * used in the setter. The converter should take one parameter, the expression type, and the * return value should be the target value type used in the setter. Converters are used * whenever they can be applied and are not specific to any attribute. */
@Target({ElementType.METHOD})
public @interface BindingConversion {
}复制代码

使用解析

根据官方代码中的注释,咱们能够理解到 BindingConversion 的用途:使用该注解的方法能够自动的将咱们声明的参数类型转化成咱们实际要使用的参数类型的值。注解的方法接受一个参数,这个参数的类型就是咱们绑定的view对象声明的类型,而方法的返回值则是咱们绑定的属性值的目标类型。要注意的一点是,这种转换器能够被使用在任什么时候候,而并不须要对应特定的属性。

使用示例

首先,咱们在布局文件中定义以下:

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data >
        <variable name="date" type="java.util.Date"/>
    </data>

    <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
        <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{date}" android:padding="5dp" />
    </LinearLayout>
</layout>复制代码

原本 TextViewandroid:text 属性是接受一个 String 类型的值的,可是咱们在XML当中绑定的倒是一个 Date 类型的值。若是这时候咱们没有在java文件中定义Converter的话,那么咱们将会在编译期获得这样的一个error:

java.lang.RuntimeException: Found data binding errors.
/ data binding error msg:Cannot find the setter for attribute 'android:text' with parameter type java.util.Date on android.widget.TextView. file:/DataBindingDemo/app/src/main/res/layout/activity_main.xml loc:35:28 - 35:31 \ data binding error

可是在咱们定义了下面的以后,一切就妥妥的了:

@BindingConversion
public static String convertDate(Date date){
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    return sdf.format(date);
}复制代码

使用心得

  1. BindingAdapter 不一样,BindingConversion 是不支持实例方法的,若是咱们试图将上面的方法定义成实例方法,咱们将会在编译期获得这样的一个error:

java.lang.RuntimeException: failure, see logs for details.
@BindingConversion is only allowed on public static methods convertDate(java.util.Date)

BindingMethod

使用场景

有的属性咱们的view属性的setter方法跟属性的名称并不相匹配(由于data-bing是经过setAttr的形式去寻找对应的setter方法的)。好比说“android:tint”这个属性对应的setter方法名称是 setImageTintList(ColorStateList) ,而不是 setTint() 方法。这时候使用 BindingMethod 注解能够帮助咱们从新命名view属性对应的setter方法名称。

代码定义

/** * Used within an {@link BindingMethods} annotation to describe a renaming of an attribute to * the setter used to set that attribute. By default, an attribute attr will be associated with * setter setAttr. */
@Target(ElementType.ANNOTATION_TYPE)
public @interface BindingMethod {

    /** * @return the View Class that the attribute is associated with. */
    Class type();

    /** * @return The attribute to rename. Use android: namespace for all android attributes or * no namespace for application attributes. */
    String attribute();

    /** * @return The method to call to set the attribute value. */
    String method();
}复制代码

使用解析

根据官方代码中的注释咱们能够发现,BindingMethod 是放在 BindingMethods 注解当中做为参数数组的一个元素进行使用的。
使用的时候,咱们须要在注解参数中指定三个关键值,分别是:

  • type:属性所关联的view的class对象
  • attribute: 属性的名称(若是是android框架的属性使用“android”做为命名空间,若是是应用自己定义的属性则不须要附带命名空间)
  • method:对应的setter方法名称

使用示例

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

BindingMethods

使用场景

BindingMethodsBindingMethod 结合使用,使用场景和示例能够参考上面的说明

代码定义

/** * Used to enumerate attribute-to-setter renaming. By default, an attribute is associated with * setAttribute setter. If there is a simple rename, enumerate them in an array of * {@link BindingMethod} annotations in the value. */
@Target({ElementType.TYPE})
public @interface BindingMethods {
    BindingMethod[] value();
}复制代码

以上就是关于android data-binding library中的定义的注解的重点学习,主要是结合注解的定义进行简单的实践,包括使用的示例,使用场景以及本身的踩坑心得。

固然相对于这些注解,其实更加核心的部分是data-binding library 针对这些注解的所定义的注解处理器的核心实现,关于这部分的解析,我会在后面的文章中继续分析。

参考文献:

官方指导文档:Data Binding Library

官方data-binding代码仓库

感谢你宝贵的时间阅读这篇文章,欢迎你们关注我,也欢迎你们评论一块儿学习,个人我的主页浅唱android

相关文章
相关标签/搜索