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只能包含一个View标签. 不能直接包含

data

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

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" />
  
    <variable name="user" type="User" />
data>

使用类

<TextView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.userName}"
          />

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

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

标签如果需要导入(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方法就可以直接在布局中使用该属性(这是因为DataBinding的库中官方已经帮你写好了很多自定义属性)

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,
ObservableFloat,
ObservableInt,
ObservableLong,
ObservableParcelable,
ObservableShort,
ViewDataBinding

示例

public class PlainUser {
  public final ObservableField firstName = new ObservableField<>();
  public final ObservableField lastName = new ObservableField<>();
  public final ObservableInt age = new ObservableInt();
}

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

ObservableArrayMap 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"/>
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序列化数据类型
  2. 上面说的这两种只会视图跟随数据更新, 数据并不会跟随视图刷新.
  3. ObservableField同样支持addOnPropertyChangedCallback监听属性改变
  4. 数据跟随视图刷新

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

    <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

    如果你想自定义一个属性并且将他和这个View内部的函数关联就必须使用这个特性;

    该注解属于一个容器. 内部参数是一个@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)”

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

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

    反转函数: 是刷新数据的时候使用 (决定实体存储数据), 可以理解为setter函数;

    简单示例:

    在用户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类型

    创建一个函数使用@InverseBindingAdapter修饰; 该函数会在UI变化的时候回调, 返回的数据会

    @InverseBindingAdapter(attribute = "good", event = "goodAttrChanged")
    public static String getTextString(TextView view) {
        Log.i("日志", "(DataBinding.java:20) ___ 反转" + view.getText());
        return "吴彦祖" + view.getText();
    }

    event: 该属性需要你创建一个@BindingAdapter

    @BindingAdapter("goodAttrChanged")
    public static void setGoodChanged(TextView textView, InverseBindingListener inverseBindingListener) {
        Log.d("日志", "(DataBinding.java:24) ___ 属性监听事件 = ");
    }

    在你绑定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:textAttrChanged"}, 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 (例: getMethod

    在自动生成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<String>"/>
    
    <variable
              name="map"
              type="java.util.Map"/>

    上面这种写法会报错

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

    因为<符号需要转义.

    常用转义字符

    ​ 空格  ;  ;

    < 小于号 <; <;

    > 大于号 >; >;

    & 与号 &; &;
    ” 引号 "; ";
    ‘ 撇号 &apos; ';
    × 乘号 ×; ×;
    ÷ 除号 ÷; ÷;

    正确写法

    <variable
              name="list"
              type="java.util.List<String>"/>
    
    <variable
              name="map"
              type="java.util.Map<String, String>"/>

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

    android:text="@{map["firstName"]}"

    字符串

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

    第一种:

    android:text='@{"吴彦祖"}'

    第二种:

    android:text="@{`吴彦祖`}"

    第三种:

    android:text="@{@string/user_name}"

    同样支持@color或@drawable

    格式化字符串

    首先在strings中定义

    <string name="string_format">名字: %s  性别: %sstring>

    然后就可以使用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的属性来调用类方法

    
    <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

    
    <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组件

    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    bind(View root)
    
    static  T    bind(View root, 
                                                 DataBindingComponent bindingComponent)
    
    
    // 布局
    static  T    inflate(LayoutInflater inflater, 
                                                    int layoutId, 
                                                    ViewGroup parent, 
                                                    boolean attachToParent, DataBindingComponent bindingComponent) // 组件
    
    static  T    inflate(LayoutInflater inflater,
                                                    int layoutId, 
                                                    ViewGroup parent, 
                                                    boolean attachToParent)
    
    // activity
    static  T    setContentView(Activity activity, 
                                                           int layoutId)
    
    static  T    setContentView(Activity activity,
                                                           int layoutId, DataBindingComponent bindingComponent)

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

    static  T    getBinding(View view)
    
    // 和getBinding不同的是如果视图没有绑定会去检查父容器是否被绑定
    static  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    setContentView(Activity activity,
                                                           int layoutId, DataBindingComponent bindingComponent)

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

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

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

    注意

    1. 可以使用include不过不能作为root布局. merge不能使用
    2. 如果没有自动生成DataBinding类可以先写个variable(或者make module下)
    3. 即使你没有绑定数据(你可能会在网络请求成功里面绑定数据), 但是只要视图创建完成就会自定绑定数据. 这个时候数据是空对象. 空对象的字段也会有默认值(String的默认值是NULL, TextView就会显示NULL); 并且如果你用了三元表达式, 空对象的三元表达式都为false; 所以建议不要考虑空对象的情况;
    4. 如果你给一个要求值是布尔类型值的自定义属性(BindingAdapter)赋值一个函数, 空指针的情况会返回false;

    推荐插件

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

    DataBindingModelFormatter

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

    DataBindingSupport

    自动生成DataBinding所需的XML格式

    你可能感兴趣的:(Android)