DataBinding最全使用说明

若是你要在Android实现MVVM架构, 那么DataBinding是你的不二选择. MVVM也是目前全部前端/iOS/Android领域主流发展方向前端

  1. 更少的代码
  2. 更强大的容错性
  3. 更快的迭代速度
  4. 更高的可读性

本文与2019基于Kotlin再编辑java

前言android

  1. 不要企图使用LiveData取代DataBinding, DataBinding自己就兼容LiveData属性
  2. 不管项目大小MVVM都优于MVP
  3. 这是主流也是将来

启用 DataBinding会自动在build目录下生成类. 由于被集成进AndroidStudio因此不须要你手动编译会实时编译, 而且支持大部分代码补全.git

apply plugin: "kotlin-kapt" // Kotlin 使用 Databinding必须添加

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

开头github

  • Databinding不是替代ButterKnife之类的 findById只是他的一个小小的辅助功能而已, 我推荐使用Kotlin来解决这个需求;
  • Databinding的大部分状况下错误提示很完善, 个别XML书写错误也易于排查
  • 我想强调的是Xml中的@{} 只作赋值或者简单的三元运算或者判空等不要作复杂运算, 不然违背解耦原则.
  • 业务逻辑应该尽可能在Model中
  • ViewModel属于DataBinding自动生成的类

MVP对比MVVM的劣势api

  1. MVP经过接口回调实现致使代码可读性差, 阅读顺序不连贯
  2. MVP没法实现双向数据绑定
  3. MVP的实现因人而异, 差别性致使阅读性差
  4. MVP的代码量比MVC还要多, 属于经过提高代码量来解耦, 代码量比MVVM几何倍增
  5. 前端任何平台都开始趋向于MVVM, Web领域MVVM属于最成熟的应用

我开源一个基于Kotlin和Databinding特性的RecyclerView库: BRV, 具有无与伦比的简洁和MVVM特性;数组

我平时项目开发必备框架安全

  1. Android上最强网络请求 Net
  2. Android上最强列表(包含StateLayout) BRV
  3. Android最强缺省页 StateLayout
  4. JSON和长文本日志打印工具 LogCat
  5. 支持异步和全局自定义的吐司工具 Tooltip
  6. 开发调试窗口工具 DebugKit
  7. 一行代码建立透明状态栏 StatusBar

布局

布局文件markdown

<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("drake");

    // 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方法就能够直接在布局中使用该属性(这是由于DataBinding的库中官方已经帮你写好了不少自定义属性)

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

而后直接使用(可是IDE没有代码补全)

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

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

数据双向绑定

数据刷新视图

BaseObservable

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

有两种方式:

继承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); // 须要手动刷新
    }
}
复制代码
  • 简化用法只须要数据模型继承BaseObservable便可, 而后每次变动数据后调用notify()函数既能够刷新视图. 不须要注解.

    observableUser.name
    observableUser.notifyChange()
    复制代码
  • 若是你没法继承能够经过实现接口方式也能够. 查看BaseObservable实现的接口本身实现便可, 也能够复制代码示例.

还能够监听属性改变事件

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监听属性改变

若是数据为LiveData一样支持, 而且ViewDataBinding能够设置生命周期.

视图刷新数据

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

<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注解

注解

DataBinding经过注解来控制ViewModel的类生成

@Bindable

用于数据更新自动刷新视图. 后面的数据绑定提到.

@BindingAdapter

建立一个XML属性和函数, 而后在属性中进行设置数据操做会进入该函数.

图片加载框架能够方便使用此方法.

@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. 第一个参数必须是控件或其父类
  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(成员默认值). 因此咱们必需要首先进行判空处理.

Kotlin实现有两种方法

单例类+@JvmStatic注解

object ProgressAdapter {

    @JvmStatic
    @BindingAdapter("android:bindName")
    fun setBindName(view: View, name:String){

    }
}
复制代码

顶级函数

@BindingAdapter("android:bindName")
fun setBindName(view: View, name:String){

}

// 因为顶级函数太多影响代码补全建议使用顶级扩展函数, 以后也能够在代码中方便使用

@BindingAdapter("android:bindName")
fun View.setBindName( name:String){
   
}
复制代码

@BindingMethods

若是你想建立一个XML属性而且和View中的函数关联(即会自动使用属性值做为参数调用该函数). 就应该使用@BindingMethods注解一个类(该类无限制甚至能够是一个接口).

若是说@BindingAdapter是建立一个新的函数功能给控件使用, 那么BindingMethod就是引导DataBinding使用控件自身的函数.

该注解属于一个容器. 内部参数是一个@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: XML属性
  3. method: 函数名 即控件中的函数名称

注意

  • 若是属性名和@BindingAdapter定义的XML属性相同会冲突报错
  • 若是控件类中已经存在一个和你定义的属性相关联的函数(例setName函数和android:name属性就相关联)则会优先执行该函数

@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);
    }
}
复制代码

我写的Kotlin示例

@BindingConversion
fun int2string(integer:Int):String{
    Log.d("日志", "(CusView.kt:92) int2string ___ integer = [$integer]")

    return integer.toString()
}
复制代码

XML

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

    <data>

        <variable name="m" type="com.example.architecture.Model" />

    </data>

    <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">


       <com.example.architecture.CusView android:bindName="@={m.age}" android:layout_width="wrap_content" android:layout_height="wrap_content" />


    </FrameLayout>

</layout>
复制代码

我这代码实际上会报错, 由于涉及到双向数据绑定, @BindingConversion只会在数据设置视图的时候生效. 可是若是是视图设置数据则会走其余函数(get), 若是该函数返回的类型和Model中的类型不匹配则会报异常, 除非你将那个函数改成类型匹配的.

或者去掉=符号不使用双向数据绑定

android:text不能使用int转为string, 由于他自己能正常接收int(做为resourceID). 而后会报

android.content.res.Resources$NotFoundException: String resource ID #0xa
复制代码

@InverseMethod

该注解属于AndroidStudio3以后提供的inverse系列的新注解, 所有都是针对数据双向绑定.

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

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

咱们须要两个函数: 设置数据到视图的函数 称为set / 设置视图变动到数据的函数 称为get

  • set和get都至少要有一个参数
  • 自身参数必须和另外一个函数的返回值对应(否则怎么叫转换)

简单示例:

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

class Model {
  
  var name = "设计师"
  
   @InverseMethod("ui2data")
    fun data2ui():String{

        return "设计师金城武"
    }

    fun ui2data():String{
        return "设计师吴彦祖"
    }
}
复制代码

使用

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

    <data>

        <variable name="m" type="com.example.architecture.Model" />

    </data>

    <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity">


       <com.example.architecture.CusView android:text="@{m.data2ui(m.name)}" android:layout_width="wrap_content" android:layout_height="wrap_content" />

    </FrameLayout>

</layout>
复制代码

@InverseBindingAdapter

参数:

  • String attribute 属性值(必填)
  • String event 非必填, 默认值等于 <attribute>AttrChanged

他和@BindingAdapter配合实现双向数据绑定

彻底的双向数据绑定须要三个函数

  1. set (数据到视图)
  2. get (视图到数据)
  3. notify (通知Databinding视图已经刷新能够更新数据(Model)了)

set函数, 以前已经写过了

@BindingAdapter("android:bindName")
fun TextView.setBindName(name:String?){
    if (name.isNullOrEmpty() && name != text) {
        text = name
    }
}
复制代码

get函数

@InverseBindingAdapter(attribute = "android:bindName", event = "cus_event")
fun TextView.getBindName():String{

	// 这里你能够对视图上的数据进行处理最终设置给Model层

    return text.toString()
}
复制代码
  • 不容许存在更多参数
  • 返回值类型必须是绑定的数据类型

notify函数 视图变化后要通知Databinding开始设置Model层, 一样要用到@BindingAdapter, 不一样的是参数要求只能为InverseBindingListener.

@BindingAdapter("cus_event")
fun TextView.notifyBindName( inverseBindingListener: InverseBindingListener){

  // 这个函数是监听TextWatch 官方源码 固然不一样的需求不一样的监听器
   doAfterTextChanged {
       inverseBindingListener.onChange() // 这行代码执行即通知数据刷新
   }

}
复制代码

InverseBindingListener 是个接口只有一个函数, 他是notify函数必要的参数.

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

@InverseBindingMethods

@BindingMethods类似

可是@InverseBindingMethods是视图变动数据(get函数), 而BindingMethods是数据到视图(set函数)

参数

public @interface InverseBindingMethod {

    /** * 控件的类字节码 */
    Class type();

    /** * 自定义的属性 */
    String attribute();

    /** * nitify函数的名称 即用于通知数据更新的函数 */
    String event() default "";

    /** * 控件自身的函数名称, 若是省略即自动生成为 {attribute}AttrChange */
    String method() default "";
}
复制代码

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

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

@BindingMethods要多一个函数即notify函数用于通知更新

@BindingAdapter("cus_event")
fun TextView.notifyBindName( inverseBindingListener: InverseBindingListener){

   doAfterTextChanged {
       inverseBindingListener.onChange()
   }

}
复制代码

示例:

@InverseBindingMethods( InverseBindingMethod( type = CusView::class, attribute = "android:bindName", method = "getName", event = "cus_event" ) )
object Adapter {

}
复制代码
  • 若是attribute属性值属于不存在的属性, 则须要再建立一个BindingAdapter自定义属性来处理.

查看下生成类中的视图更新数据的实现源码

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方法可用的状况下)

  • BindingMethods系优先级高于BindingAdapter系列

  • 全部注解的功能都是基于XML属性值为Databinding表达式才生效(即@{})

建议参考官方实现源码:

DataBindingAdapter

表达式

这里指的是XML文件中使用的表达式(用于赋值变量), @{}里面除了能够执行方法之外还能够写表达式, 而且支持一些特有表达式

  • 算术 + - / * %
  • 字符串合并 +
  • 逻辑 && ||
  • 二元 & | ^
  • 一元 + - ! ~
  • 移位 >> >>> <<
  • 比较 == > < >= <=
  • 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不能直接经过静态导入来使用. 须要做为字段常量来使用

函数回调

DataBinding还支持在XML中绑定函数参数类型, 而且仍是Lambda和高阶函数类型, 这点比Java还先进.

对象

即直接将对象做为和属性等同的方式在XML使用. 这就必须先手动建立一个对象. 稍显麻烦.

高阶函数

建立自定义属性

object EventDataBindingComponent {

    /** * 在绑定视图时能够用于Model来处理UI, 因为破坏视图和逻辑解耦的规则不是很建议使用 * 这会致使不方便业务逻辑进行单元测试 * * @see OnBindViewListener 该接口支持泛型定义具体视图 * * @receiver View * @param block OnBindViewListener<View> */
    @JvmStatic
    @BindingAdapter("view")
    fun View.setView(listener: OnBindViewListener) {
        listener.onBind(this)
    }
}
复制代码

上面使用到的接口

interface OnBindViewListener {
    fun onBind(v: View) } 复制代码

高阶函数

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="v" 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="设计师吴彦祖" android:onClick="@{v::click}"/>

    </LinearLayout>
</layout>
复制代码

在XML中使用高阶函数须要匹配以下规则

  1. BindingAdapter的函数参数要求是一个接口, 不支持Kotlin的函数类型参数
  2. 接口只容许一个函数
  3. 接口的方法签名(返回值|参数)和传递的高阶函数匹配

Lambda

高阶函数不容许自定义传递参数(不然须要修改接口). 因此可使用Lambda来进行控制.

建立一个多参数的函数

fun onBinding(v:View, name:String){
  Log.d("日志", "(MainActivity.kt:45) this = [$v] name = [$name]")
}
复制代码

XML使用

view="@{(view) -> v.onBinding(view, `吴彦祖`)}"
复制代码

若是不使用参数

view="@{() -> v.onBinding(`吴彦祖`)} 复制代码

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);
      }
    });
复制代码

OnPropertyChangedCallback

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

默认状况下BindingAdapter注解针对全部的XML属性均可以使用. 而经过制定不一样的DatabindingComponent能够切换这些自定义属性.

建立DatabindingComponent的步骤:

  1. 建立自定义类, 类中存在包含使用@BindingAdapter的函数, 无需静态函数.

    这个时候AndroidStudio会自动生成DatabindingComponnent接口

  2. 建立DatabindingComponent派生类, 这个时候会提示有方法要求覆写. 若是你省略第一步骤则不会有.

  3. 经过DataBindingUtils工具将你自定义的派生类设置到Databinding中, 这里包含全局默认和单例.

第一步

class PinkComponent {

    @BindingAdapter("android:bindName")
    fun TextView.setBindName(name:String?){

        if (!name.isNullOrEmpty() && name != text) {
            text = "数据体"
        }
    }

    @BindingAdapter("android:bindNameAttrChanged")
    fun TextView.notifyBindName(inverseBindingListener: InverseBindingListener){

        doAfterTextChanged {
            inverseBindingListener.onChange()
        }

    }

    @InverseBindingAdapter(attribute = "android:bindName")
    fun TextView.getBindName():String{

        return text.toString()
    }
}
复制代码

第二步

class CusComponent : DataBindingComponent {

    override fun getPinkComponent(): PinkComponent {
        return PinkComponent() // 此处不能返回null
    }
}
复制代码

第三步

设置默认组件都是由DataBindingUtils设置, 可是方法也有所不一样

static void setDefaultComponent(DataBindingComponent bindingComponent) static DataBindingComponent getDefaultComponent() 复制代码

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

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

若是你没有执行setDefaultComponent则选择经过函数单独传入, 则每次都要传入不然报错.

DatabindingComponent只能使用@BindingAdapter注解

注意

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

推荐插件

DataBindingSupport

经过快捷键(alt + enter)在XML布局中自动建立表达式和节点 , AS4失效

DataBindingConvert

使用快捷键快速将包裹布局为layout, AS4可用

相关文章
相关标签/搜索