阅读 9018

DataBinding最全使用说明

Google开源的数据绑定框架, 实现了MVVM架构, 增强了xml的功能, 大幅度精简了java代码量, 并且代码可读性更高, 对性能的影响基本为零.

DataBinding会自动在build目录下生成类. 因为被集成进AndroidStudio所以不需要你手动编译会实时编译, 并且支持大部分代码补全.

启用DataBinding

android{
      dataBinding {
        enabled = true;
    }
}
复制代码

因为怕你们没注意到我写在文章开头

  • 我想强调的是XML只做赋值或者简单的三元运算或者判空等不要做复杂运算;
  • 逻辑运算在Model中
  • 有时候可以偷懒将Activity当作ViewModel来使用

DataBinding的强大是毋庸置疑, 只会更方便(抛弃MVP吧);

鉴于文章篇幅, 后面我将会出一篇文章以及开源库告诉大家如何实现DataBinding是如何让RecyclerView一行代码写通用适配器(无需写实现类)

一行代码实现多类型/添加头布局脚布局/点击事件;

布局

布局文件

<layout>
  
    <data>
        <variable
            name="user"
            type="com.liangjingkanji.databinding.pojo.UserBean"/>
    </data>

    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.liangjingkanji.databinding.MainActivity">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.userName}"
            />
        
    </RelativeLayout>
  
</layout>
复制代码

layout

布局根节点必须是<layout> . 同时layout只能包含一个View标签. 不能直接包含<merge>

data

<data>标签的内容即DataBinding的数据. data标签只能存在一个.

variable

通过<variable>标签可以指定类, 然后在控件的属性值中就可以使用

<data>
	<variable name="user" type="com.liangfeizc.databindingsamples.basic.User" />
</data>
复制代码

通过DataBinding的setxx()方法可以给Variable设置数据. name值不能包含_下划线

import

第二种写法(导入), 默认导入了java/lang包下的类(String/Integer). 可以直接使用被导入的类的静态方法.

<data>
  <!--导入类-->
    <import type="com.liangfeizc.databindingsamples.basic.User" />
  <!--因为User已经导入, 所以可以简写类名-->
    <variable name="user" type="User" />
</data>
复制代码

使用类

<TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.userName}"
          />
<!--user就是在Variable标签中的name, 可以随意自定义, 然后就会使用type中的类-->
复制代码

Tip: user代表UserBean这个类, 可以使用UserBean中的方法以及成员变量. 如果是getxx()会自动识别为xx. 注意不能使用字符串android, 否则会报错无法绑定.

class

<data>标签有个属性<class>可以自定义DataBinding生成的类名以及路径

<!--自定义类名-->
<data class="CustomDataBinding"></data>

<!--自定义生成路径以及类型-->
<data class=".CustomDataBinding"></data> <!--自动在包名下生成包以及类-->
复制代码

Tip:注意没有代码自动补全. 自定义路径Module/build/generated/source/apt/debug/databinding/目录下, 基本上不需要自定义路径

默认:

public class MainActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // ActivityMainBinding这个类根据布局文件名生成(id+Binding)
    ActivityMainBinding viewDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);

    UserBean userBean = new UserBean();
    userBean.setUserName("姜涛");

    // setUser这个方法根据Variable标签的name属性自动生成
    viewDataBinding.setUser(userBean);
  }
}
复制代码

alias

<variable>标签如果需要导入(import)两个同名的类时可以使用alias属性(别名属性)

<import type="com.example.home.data.User" />
<import type="com.examle.detail.data.User" alias="DetailUser" />
<variable name="user" type="DetailUser" />
复制代码

include

在include其他布局的时候可能需要传递变量(variable)值过去

<variable
          name="userName"
          type="String"/>

....

<include
         layout="@layout/include_demo"
         bind:userName="@{userName}"/>
复制代码

include_demo

    <data>

        <variable
            name="userName"
            type="String"/>
    </data>

...

android:text="@{userName}"
复制代码

两个布局通过includebind:<变量名>值来传递. 而且两者必须有同一个变量

DataBinding不支持merge标签

自动布局属性

DataBinding对于自定义属性支持非常好, 只要View中包含setter方法就可以直接在布局中使用该属性

public void setCustomName(@NonNull final String customName) {
    mLastName.setText("吴彦祖");
  }
复制代码

然后直接使用(但是IDE没有代码补全)

app:customName="@{@string/wuyanzu}"
复制代码

但是setter方法只支持单个参数. app:这个命名空间可以随意

数据双向绑定

视图跟随数据刷新

BaseObservable

如果需要数据变化是视图也跟着变化则需要使用到以下两种方法

有两种方式:

  1. 继承BaseObservable

    public class ObservableUser extends BaseObservable {
        private String firstName;
        private String lastName;
    
        @Bindable
        public String getFirstName() {
            return firstName;
        }
    
      // 注解才会自动在build目录BR类中生成entry, 要求方法名必须以get开头
        @Bindable
        public String getLastName() {
            return lastName;
        }
    
        public void setFirstName(String firstName) {
            this.firstName = firstName;
            notifyPropertyChanged(BR.firstName);
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
            notifyPropertyChanged(BR.lastName); // 需要手动刷新
        }
    }
    复制代码

还可以监听属性改变事件

ObservableUser.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
  @Override
  public void onPropertyChanged(Observable observable, int i) {

  }
});
复制代码

属性第一次改变时会回调两次, 之后都只回调一次. 如果使用notifyChange()不会得到id(即i等于0). 使用

notifyPropertyChanged(i)就可以在回调里面得到id.

BaseObservable和Observable的区别:

  1. BaseObservable是实现了Observable的类, 帮我们实现了监听器的线程安全问题.
  2. BaseObservable使用了PropertyChangeRegistry来执行OnPropertyChangedCallback
  3. 所以我不推荐你直接实现Observable.

ObservableField

databinding默认实现了一系列实现Observable接口的字段类型

BaseObservable,
ObservableBoolean,
ObservableByte,
ObservableChar,
ObservableDouble,
ObservableField<T>,
ObservableFloat,
ObservableInt,
ObservableLong,
ObservableParcelable<T extends Parcelable>,
ObservableShort,
ViewDataBinding
复制代码

示例

public class PlainUser {
  public final ObservableField<String> firstName = new ObservableField<>();
  public final ObservableField<String> lastName = new ObservableField<>();
  public final ObservableInt age = new ObservableInt();
}
复制代码

对于集合数据类型ObservableArrayMap/ObservableArrayLis/ObjservableMap等集合数据类型

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
复制代码

使用

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap<String, Object>"/>
</data><TextView
   android:text='@{user["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text='@{String.valueOf(1 + (Integer)user["age"])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
复制代码

Tip:

  1. 还支持ObservableParcelable<Object>序列化数据类型
  2. 上面说的这两种只会视图跟随数据更新, 数据并不会跟随视图刷新.
  3. ObservableField同样支持addOnPropertyChangedCallback监听属性改变

数据跟随视图刷新

通过表达式使用@=表达式就可以视图刷新的时候自动更新数据, 但是要求数据实现以下两种方式修改才会触发刷新

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:inputType="textNoSuggestions"
    android:text="@={model.name}"/>
复制代码

这种双向绑定存在一个很大的问题就是会死循环. 数据变化(回调监听器)触发视图变化, 然后视图又会触发数据变化(再次回调监听器), 然后一直循环, 设置相同的数据也视为数据变化.

所以我们需要判断当前变化的数据是否等同于旧数据

public class CustomBindingAdapter {

  @BindingAdapter("android:text") public static void setText(TextView view, CharSequence text) {
    CharSequence oldText = view.getText();

    if (!haveContentsChanged(text, oldText)) {
      return; // 数据没有变化不进行刷新视图
    }
    view.setText(text);
  }


  // 本工具类截取自官方源码
  private static boolean haveContentsChanged(CharSequence str1, CharSequence str2) {
    if ((str1 == null) != (str2 == null)) {
      return true;
    } else if (str1 == null) {
      return false;
    }
    final int length = str1.length();
    if (length != str2.length()) {
      return true;
    }
    for (int i = 0; i < length; i++) {
      if (str1.charAt(i) != str2.charAt(i)) {
        return true;
      }
    }
    return false;
  }
}
复制代码

Tip:

  1. 根据我上面说的, 监听器至少回调两次(数据->视图, 视图-> 数据)

  2. 以下这种是无效的, 因为String参数传递属于引用类型变量并不是常量, 需要用equals()

    // 本段截取官方源码, 我也不知道这sb为什么这么写
    if (text == oldText || (text == null && oldText.length() == 0)) {
      return; 
    }
    
    /**/
    复制代码

    正确

    if (text == null || text.equals(oldText) || oldText.length() == 0) {
      return;
    }
    复制代码

总结就是如果没有默认实行的控件属性使用双向数据绑定 就需要你自己实现BindingAdapter注解

注解

@Bindable

用于数据更新自动刷新视图. 后面提.

@BindingAdapter

用于标记方法. 前面提到了DataBinding自定义属性自动识别setter.

如果我们需要自定义xml, 就需要修改View的源码 ,但是DataBinding还有第二种方法相当于可以将setter方法抽取出来, 并且同时支持多个属性.

图片加载框架可以方便使用此方法.

@BindingAdapter(value = { "imageUrl", "error" }, requireAll = false)
  public static void loadImage(ImageView view, String url, Drawable error) {
    Glide.with(view.getContext()).load(url).into(view);
  }
复制代码
  1. 修饰方法, 要求方法必须public static
  2. 方法参数第一个要求必须是View
  3. 方法名不作要求
  4. 最后这个boolean类型是可选参数. 可以要求是否所有参数都需要填写. 默认true.
  5. 如果requireAll为false, 你没有填写的属性值将为null. 所以需要做非空判断.

使用:

<ImageView
           android:layout_width="match_parent"
           android:layout_height="200dp"
           app:error="@{@drawable/error}"
           wuyanzu:imageUrl="@{imageUrl}"
           app:onClickListener="@{activity.avatarClickListener}"
           />
复制代码

可以看到命名空间可以随意, 但是如果在BindingAdapter的数组内你定义了命名空间就必须完全遵守

例如:

// 这里省略了一个注解参数.   
@BindingAdapter({ "android:imageUrl", "error" })
  public static void loadImage(ImageView view, String url, Drawable error) {
    if(url == null) return;
    Glide.with(view.getContext()).load(url).into(view);
  }
复制代码

Tip: 如果你的数据初始化是在异步的. 会回调方法但是数据为null(成员默认值). 所以我们必须要首先进行判空处理.

@BindingMethods

DataBinding默认可以在布局中使用setter方法作为自定义属性, 但是如果不是setter格式的方法就要使用BindingMethod注解了. 通过创建一个自定义属性来关联一个类中已有的方法.

该注解属于一个容器. 内部参数是一个@BindingMethod数组, 只能用于修饰类(任意类都可以, 类可以为空)

官方示例:

@BindingMethods({
        @BindingMethod(type = android.widget.ProgressBar.class, attribute = "android:indeterminateTint", method = "setIndeterminateTintList"),
        @BindingMethod(type = android.widget.ProgressBar.class, attribute = "android:progressTint", method = "setProgressTintList"),
        @BindingMethod(type = android.widget.ProgressBar.class, attribute = "android:secondaryProgressTint", method = "setSecondaryProgressTintList"),
})
public class ProgressBarBindingAdapter {
}
复制代码

@BindingMethod

该注解必须有三个属性

  1. type: 字节码
  2. attribute: 属性
  3. method: 方法

会在指定的字节码(type)中寻找方法(method), 然后通过你创建的布局属性(Attribute)来回调方法

如果属性名和@BindingAdapter冲突会报错

Tip: 可以注意到该注解只是单纯地关联已有的方法, 并不能新增方法. 所以全都是注解的空类.

@BindingConversion

属性值自动进行类型转换

  1. 只能修饰public static方法.
  2. 任意位置任意方法名都不限制
  3. DataBinding自动匹配被该注解修饰的方法和匹配参数类型
  4. 返回值类型必须和属性setter方法匹配, 且参数只能有一个
  5. 要求属性值必须是@{}DataBinding表达式

官方示例:

public class Converters {
    @BindingConversion
    public static ColorDrawable convertColorToDrawable(int color) {
        return new ColorDrawable(color);
    }
    @BindingConversion
    public static ColorStateList convertColorToColorStateList(int color) {
        return ColorStateList.valueOf(color);
    }
}
复制代码

设置布局中TextView的背景,

android:background="@{`吴彦祖`}"
复制代码

可以看到我给背景随意设置一个字符串. 这样就不会匹配Background的int参数类型. 然后DataBinding就会检索匹配该类型的@BindingConversion方法. 然后转换.

注意android:text如果想用int自动转String是不可以的, 因为int值会被识别为resource id. @BindingConversion无法工作.

@InverseMethod

在android studio3.0提供inverse系列的新注解, 全部都是针对数据双向绑定.

在数据和视图的数据不统一时可以使用该注解@InverseMethod解决数据转换的问题

例如数据模型存储用户的id但是视图不显示id而是显示用户名(数据和视图的类型不一致), 我们就需要在两者之间转换.

需要创建public static两个方法, 我们简称为"转换方法(convertion method)"和"反转方法(inverse method)"

  • 转换方法与反转方法的参数数量必须相同
  • 转换方法的最终参数的类型与反转方法的返回值必须相同

转换方法: 是刷新视图的时候使用 (决定视图显示数据) 会回调两次(文章后面详细解释双向绑定的时候可以知道为何)

反转方法: 是刷新数据的时候使用 (决定实体存储数据)

简单示例:

在用户id和用户名之间转换. 存储id但是显示的时候显示用户名

  @InverseMethod("toID") public static String toName(TextView view, int id) {
    if (id == 1) {
      return "吴彦祖";
    }
    return "";
  }

  public static int toID(TextView view, String name) {
    if (name.equals("吴彦祖")) {
      return 1;
    }
    return 0;
  }
复制代码

使用

    <TextView
        android:id="@+id/iv"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:text="@={MyInverseMethod.toName( iv, data.id)}"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        />
复制代码

注意和BindingAdapter不同, 参数有View表达式就必须加上View的id

Tip:

在这个注解之前其实都是通过修改实体的setter和getter方法达到类型的转换. 但是这样会侵入整个实体类

我使用的gson类都是自动生成的我并不想去手动修改任何方法.

@InverseBindingAdapter

参数:

  • String attribute 属性值(必填)
  • String event 非必填, 默认值 属性值 + AttrChanged后缀

介绍

  • 作用于方法,方法须为公共静态方法。
  • 方法的第一个参数必须为View类型
  • 必须与@BindingAdapter配合使用

event: 这个属性存在默认值(上面提过默认值的生成规则), 我们需要创建@BindingAdapter方法来实现event的属性. 这个方法我暂且称为数据变更方法.

在你绑定DataBinding时候回自动调用这个数据变更方法, (这个数据变更方法创建的属性并不能在xml中使用)

@InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
public static String getTextString(TextView view) {
  return view.getText().toString();
}
复制代码

android:text属性使用@={}双向绑定表达式. 数据变化触发视图刷新是回调setter方法

数据变更方法(官方源码简化版):

 @BindingAdapter(value = {
      "android:textAttr"
  }, requireAll = false)
  public static void setTextWatcher(TextView view, final InverseBindingListener textAttrChanged) {

    // 创建一个文字变化监听器
    final TextWatcher newValue;

    // 如果全部为null不要监听器
    if (textAttrChanged == null) {
      newValue = null;
    } else {
      newValue = new TextWatcher() {

        @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {

        }
        @Override public void onTextChanged(CharSequence s, int start, int before, int count) {
          if (textAttrChanged != null) {
            // 通知刷新
            textAttrChanged.onChange();
          }
        }

        @Override public void afterTextChanged(Editable s) {

        }
      };
    }
    // 如果视图已经有一个监听器就先删除
    final TextWatcher oldValue = ListenerUtil.trackListener(view, newValue, R.id.textWatcher);
    if (oldValue != null) {
      view.removeTextChangedListener(oldValue);
    }

    // 给视图添加监听器
    if (newValue != null) {
      view.addTextChangedListener(newValue);
    }
  }
复制代码

这里用到一个InverseBindingListener

public interface InverseBindingListener {
    /**
     * Notifies the data binding system that the attribute value has changed.
     */
    void onChange();
}
复制代码

总结就是你只要通知

@InverseBindingMethods

类似BindingMethods. 参数是@InverseBindingMethod

如果说BindingMethods是关联setter方法和自定义属性, 那么InverseBindingMethods就是关联getter方法和自定义属性.

setter是更新视图的时候使用, 而getter方法是更新数据时候使用的

必须与@BindingAdapter配合使用

  • 修饰类

示例:

@InverseBindingMethods({
        @InverseBindingMethod(type = RadioGroup.class, attribute = "android:checkedButton", method = "getCheckedRadioButtonId"),
})
public class RadioGroupBindingAdapter {
    @BindingAdapter("android:checkedButton")
    public static void setCheckedButton(RadioGroup view, int id) {
        if (id != view.getCheckedRadioButtonId()) {
            view.check(id);
        }
    }
复制代码

@InverseBindingMethod

参数:

  • Class type 控件的字节码

  • String attribute 属性

  • String event 默认值是属性加AttrChanged后缀作为默认值

  • String method Attribute值的getter形式作为默认值通过属性指定变化监听和返回方法

在自动生成DataBinding代码中可以看到

    private android.databinding.InverseBindingListener ivandroidTextAttr = new android.databinding.InverseBindingListener() {
        @Override
        public void onChange() {
            // Inverse of data.name
            //         is data.setName((java.lang.String) callbackArg_0)
            java.lang.String callbackArg_0 = com.liangjingkanji.databinding.MyInverseBindingAdapter.getTextString(iv);  // 拿到变化的属性
            // localize variables for thread safety
            // data != null
            boolean dataJavaLangObjectNull = false;
            // data.name
            java.lang.String dataName = null;
            // data
            com.liangjingkanji.databinding.Bean data = mData; // 拿到数据



            dataJavaLangObjectNull = (data) != (null);
            if (dataJavaLangObjectNull) {




                data.setName(((java.lang.String) (callbackArg_0))); // 存储到数据
            }
        }
    };
复制代码

所以如果你没用重写Inverse的数据变更方法将无法让视图通知数据刷新.

// 该方法会在绑定布局的时候回调
    @Override
    protected void executeBindings() {
        long dirtyFlags = 0;
        synchronized(this) {
            dirtyFlags = mDirtyFlags;
            mDirtyFlags = 0;
        }
        java.lang.String dataName = null;
        com.liangjingkanji.databinding.Bean data = mData;

        if ((dirtyFlags & 0x1aL) != 0) {



                if (data != null) {
                    // read data.name
                    dataName = data.getName();
                }
        }
        // batch finished
        if ((dirtyFlags & 0x1aL) != 0) {
            // api target 1

            com.liangjingkanji.databinding.MyInverseBindingAdapter.setText(this.iv, dataName);
        }
        if ((dirtyFlags & 0x10L) != 0) {
            // api target 1

          // 重点是这段代码, 将上面创建的监听器传入setTextWatcher方法
            com.liangjingkanji.databinding.MyInverseBindingAdapter.setTextWatcher(this.iv, (com.liangjingkanji.databinding.MyInverseBindingAdapter.BeforeTextChanged)null, (com.liangjingkanji.databinding.MyInverseBindingAdapter.OnTextChanged)null, (com.liangjingkanji.databinding.MyInverseBindingAdapter.AfterTextChanged)null, ivandroidTextAttr);
        }
    }
复制代码

总结

@BindingBuildInfo@Untaggable这两个注解是DataBinding自动生成Java类时使用的.

  • Bindable

    设置数据刷新视图. 自动生成BR的ID

  • BindingAdapter

    设置自定义属性. 可以覆盖系统原有属性

  • BindingMethod/BindingMethods

    关联自定义属性到控件原有的setter方法

  • BindingConversion

    如果属性不能匹配类型参数将自动根据类型参数匹配到该注解修饰的方法来转换

  • InverseMethod

    负责实现视图和数据之间的转换

  • InverseBindingAdapter

    视图通知数据刷新的

  • InverseBindingMethod/InverseBindingMethods

    视图通知数据刷新的(如果存在已有getter方法可用的情况下)

建议参考官方实现源码:

DataBindingAdapter

表达式

@{}里面除了可以执行方法以外还可以写表达式, 并且支持一些特有表达式

  • 算术 + - / * %
  • 字符串合并 +
  • 逻辑 && ||
  • 二元 & | ^
  • 一元 + - ! ~
  • 移位 >> >>> <<
  • 比较 == > < >= <=
  • Instanceof
  • Grouping ()
  • 文字 - character, String, numeric, null
  • Cast
  • 方法调用
  • Field 访问
  • Array 访问 []
  • 三元 ?:

避免空指针

variable的值即使设置null或者没有设置也不会出现空指针异常.

这是因为官方已经用DataBinding的@BindingAdapter注解重写了很多属性. 并且里面进行了判空处理.

<variable
	name="userName"
	type="String"/>

.....

android:text="@{userName}"
复制代码

不会出现空指针异常.

dataBinding.setUserName(null);
复制代码

并且还支持特有的非空多元表达式

android:text="@{user.displayName ?? user.lastName}"
复制代码

就等价于

android:text="@{user.displayName != null ? user.displayName : user.lastName}"
复制代码

还是需要注意数组越界的

集合

集合不属于java.lang*下, 需要导入全路径.

<variable
          name="list"
          type="java.util.List&lt;String&gt;"/>

<variable
          name="map"
          type="java.util.Map<String, String>"/>
复制代码

上面这种写法会报错

Error:与元素类型 "variable" 相关联的 "type" 属性值不能包含 '<' 字符。
复制代码

因为<符号需要转义.

常用转义字符

​ 空格 &nbsp; &#160;

< 小于号 &lt; &#60;

> 大于号 &gt; &#62;

& 与号 &amp; &#38; " 引号 &quot; &#34; ‘ 撇号 &apos; &#39; × 乘号 &times; &#215; ÷ 除号 &divide; &#247;

正确写法

<variable
          name="list"
          type="java.util.List&lt;String&gt;"/>

<variable
          name="map"
          type="java.util.Map&lt;String, String&gt;"/>
复制代码

集合和数组都可以用[]来得到元素

android:text="@{map["firstName"]}"
复制代码

字符串

如果想要在@{}中使用字符串, 可以使用三种方式

第一种:

android:text='@{"吴彦祖"}'
复制代码

第二种:

android:text="@{`吴彦祖`}"
复制代码

第三种:

android:text="@{@string/user_name}"
复制代码

同样支持@color或@drawable

格式化字符串

首先在strings中定义<string>

<string name="string_format">名字: %s  性别: %s</string>
复制代码

然后就可以使用DataBinding表达式

android:text="@{@string/string_format(`吴彦祖`, `男`)}"
复制代码

输出内容:

名字: 吴彦祖 性别: 男
复制代码

默认值

如果Variable还没有复制就会使用默认值显示.

android:text="@{user.integral, default=`30`}"
复制代码

上下文

DataBinding本身提供了一个名为context的Variable. 可以直接使用. 等同于View的getContext().

android:text="@{context.getApplicationInfo().toString()}"
复制代码

引用其他控件

          <TextView
              android:id="@+id/datingName"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_centerVertical="true"
              android:layout_marginLeft="8dp"
              android:layout_toRightOf="@id/iv_dating"
              android:text="活动"
              />

/...
<TextView
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_centerVertical="true"
              android:layout_marginLeft="8dp"
              android:layout_toRightOf="@id/iv_order"
              android:text="@{datingName.text}"
              />
复制代码

引用包含_的控件id是可以直接忽略该符号. 例如tv_name直接写tvName.

谢谢 lambda 指出错误

不论顺序都可以引用

使用Class

如果想用Class作为参数传递, 那么该Class不能直接通过静态导入来使用. 需要作为字段常量来使用

事件绑定

事件绑定分为两种:

  1. 方法引用
  2. 监听绑定

对于默认的事件需要书写同样的参数的方法才能接受到, 否则报错. 例如onClick()方法必须有View参数.

方法引用

public class MyHandlers {
  // 注意必须要传View参数
    public void onClickFriend(View view) { ... }
}
复制代码

直接通过View的属性来调用类方法

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
  <data>
    <variable
              name="activity"
              type="com.liangjingkanji.databinding.MainActivity"/>
  </data>
  
  <LinearLayout
                android:orientation="vertical"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
    
    <TextView android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:text="@{user.firstName}"
              android:onClick="@{activit::click}"/>
    
  </LinearLayout>
</layout>
复制代码

Tip: activity.clickactivity::click都属于方法调用, 但是如果是activity.click()就会报错. 因为对于默认事件需要统一参数. 必须加上activity.click(View v)

监听绑定

上面提到的都不能向回调里面传递自定义参数. 而如果使用

android:onClick="@{()->activity.click(text)}"
复制代码

就可以自定义回调参数了

ActivityMainBinding dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); 

dataBinding.setActivity(this);
dataBinding.setText("吴彦祖"); // 顺序无所谓
复制代码

然后在布局文件中使用Lambda

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
  <data>
    <variable
              name="text"
              type="String"/>

    <variable
              name="activity"
              type="com.liangjingkanji.databinding.MainActivity"/>
  </data>
  <LinearLayout 
                android:layout_width="match_parent" 						    
                android:layout_height="match_parent">
    <Button 
            android:layout_width="wrap_content" 									        android:layout_height="wrap_content"
            android:onClick="@{()->activity.click(text)}" />
  </LinearLayout>
</layout>
复制代码

注意: DataBinding会将控件所有的setter方法全部暴露为xml属性. 是不是很方便??

DataBinding组件

ViewDataBinding

自动生成的DataBinding类都继承自该类. 所以都拥有该类的方法

void	addOnRebindCallback(OnRebindCallback listener)
// 添加绑定监听器, 可以在Variable被设置的时候回调

void	removeOnRebindCallback(OnRebindCallback listener)
// 删除绑定监听器

View	getRoot()
// 返回被绑定的视图对象
  
abstract void	invalidateAll()
// 使所有的表达式无效并且立刻重新设置表达式. 会重新触发OnRebindCallback回调(可以看做重置)


abstract boolean	setVariable(int variableId, Object value)
// 可以根据字段id来设置变量

void	unbind()
// 解绑布局, ui不会根据数据来变化, 但是监听器还是会触发的
复制代码

这里有三个方法需要重点讲解:

abstract boolean	hasPendingBindings()
// 当ui需要根据当前数据变化时就会返回true(数据变化后有一瞬间)

void	executePendingBindings()
// 强制ui立刻刷新数据, 
复制代码

当你改变了数据以后(在你设置了Observable观察器的情况下)会马上刷新ui, 但是会在下一帧才会刷新UI, 存在一定的延迟时间. 在这段时间内hasPendingBindings()会返回true. 如果想要同步(或者说立刻)刷新UI可以马上调用executePendingBindings().

OnRebindCallback:

该监听器可以监听到布局绑定的生命周期

    mDataBinding.addOnRebindCallback(new OnRebindCallback() {
      /**
       * 绑定之前
       * @param binding
       * @return 如果返回true就会绑定布局, 返回false则取消绑定
       */
      @Override public boolean onPreBind(ViewDataBinding binding) {
        return false;
      }

      /**
       * 如果取消绑定则回调该方法(取决于onPreBind的返回值)
       * @param binding
       */
      @Override public void onCanceled(ViewDataBinding binding) {
        super.onCanceled(binding);
      }

      /**
       * 绑定完成
       * @param binding
       */
      @Override public void onBound(ViewDataBinding binding) {
        super.onBound(binding);
      }
    });
复制代码

DataBinding也有个数据变更监听器, 可以监听Variable的设置事件

mDataBinding.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {

  /**
       * 会在DataBinding设置数据的时候回调
       * @param sender DataBinding生成的类
       * @param propertyId Variable的id
       */
  @Override public void onPropertyChanged(Observable sender, int propertyId) {
    ActivityMainBinding databinding = (ActivityMainBinding) sender;
    switch (propertyId) {
      case BR.data:
        Log.d("日志", "(MainActivity.java:54) ___ Result = " + databinding.getData().getName());
        break;
      case BR.dataSecond:

        break;
    }
  }
});
复制代码

DataBindingUtil

DataBinding不仅可以绑定Activity还可以绑定视图内容(View)


// 视图
static <T extends ViewDataBinding> T	bind(View root)

static <T extends ViewDataBinding> T	bind(View root, 
                                             DataBindingComponent bindingComponent)

  
// 布局
static <T extends ViewDataBinding> T	inflate(LayoutInflater inflater, 
                                                int layoutId, 
                                                ViewGroup parent, 
                                                boolean attachToParent, DataBindingComponent bindingComponent) // 组件

static <T extends ViewDataBinding> T	inflate(LayoutInflater inflater,
                                                int layoutId, 
                                                ViewGroup parent, 
                                                boolean attachToParent)

// activity
static <T extends ViewDataBinding> T	setContentView(Activity activity, 
                                                       int layoutId)

static <T extends ViewDataBinding> T	setContentView(Activity activity,
                                                       int layoutId, DataBindingComponent bindingComponent)
复制代码

还有两个不常用的方法, 检索视图是否被绑定, 如果没有绑定返回nul

static <T extends ViewDataBinding> T	getBinding(View view)

// 和getBinding不同的是如果视图没有绑定会去检查父容器是否被绑定
static <T extends ViewDataBinding> T	findBinding(View view)
复制代码

其他的方法

// 根据传的BR的id来返回字符串类型. 可能用于日志输出
static String	convertBrIdToString(int id)
复制代码

例如BR.name这个字段对应的是4, 就可以使用该方法将4转成"name"

DataBindingComponent

每个DataBinding都可以拥有一个组件或者说设置一个默认的全局组件

创建一个Component的步骤:

  1. 创建一个MyDataBindingComponent实现接口DataBindingComponent
  2. 创建MyBindingAdapter类, 用@BindingAdapter修饰其成员方法(不需要静态)
  3. 在MyDataBindingComponent写入一个get**方法()来返回该MyBindingAdapter
public class MyDefaultComponent implements DataBindingComponent {

  public MyBindingAdapter mAdapter = new MyBindingAdapter();

  public MyBindingAdapter getMyBindingAdapter() {
    return mAdapter;
  }

  class MyBindingAdapter {
    @BindingAdapter("android:text") public void setText(TextView textView, String text) {
	/*省略*/
      textView.setText(text);
    }
  }
}
复制代码

设置默认组件都是由DataBindingUtils设置, 但是方法也有所不同

static DataBindingComponent	getDefaultComponent()

static void	setDefaultComponent(DataBindingComponent bindingComponent)
复制代码

以上这种设置必须在绑定视图之前设置, 并且是默认全局的, 只需要设置一次.

static <T extends ViewDataBinding> T	setContentView(Activity activity,
                                                       int layoutId, DataBindingComponent bindingComponent)
复制代码

类似于上面这种在绑定视图的同时来设置组件需要每次绑定视图都设置, 否则就会报错.

或者你可以将@BindingAdapter注解的方法变为Static修饰.

另外不仅仅是@BindingAdapter可以设置成组件, @InverseBindingAdapter同样可以

注意

  1. 可以使用include不过不能作为root布局. merge不能使用
  2. 如果没有自动生成DataBinding类可以先写个variable(或者make module下)

推荐插件

关于DataBinding我推荐使用插件生成, 方便快捷很多;

DataBindingModelFormatter

快捷生成实现Observable的数据模型

DataBindingSupport

自动生成DataBinding所需的XML格式

关注下面的标签,发现更多相似文章
评论

查看更多 >