Skip to content

itrenjunhua/AndroidDataBinding

Folders and files

NameName
Last commit message
Last commit date

Latest commit

5ec0aa9 · Sep 7, 2021

History

30 Commits
Sep 7, 2021
Oct 30, 2018
Aug 25, 2021
Aug 25, 2021
Nov 6, 2018
Oct 30, 2018
Nov 5, 2018
Oct 30, 2018
Oct 30, 2018
Oct 30, 2018
Oct 30, 2018

Repository files navigation

Android DataBinding

DataBinding 文档

Demo 代码 github传送门

简书:ViewModel、LiveData 使用

CSDN:ViewModel、LiveData 使用

简书:ViewModel+LiveData+DataBinding使用

CSDN:ViewModel+LiveData+DataBinding使用

简单使用数据绑定

1、添加 Android DataBinding 支持

Android studio 需要在1.3以上,在module级别的gradle中添加大DataBinding支持

android {

    dataBinding {
        enabled = true
    }
}

2、在 xml 布局文件中绑定数据

使用 DataBinding 的xml布局文件和普通的布局文件唯一的区别就是根节点变为 layout ;然后增加了 data 节点,存放绑定的数据;其余的布局和普通的一样

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <!-- 定义绑定的数据 -->
    <data>

    </data>

    <!-- 显示的布局 -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

    </LinearLayout>

</layout>

3、定义数据绑定对象

public class UserBean {
    private String username;
    private String address;

    public UserBean(String username) {
        this.username = username;
    }

    public UserBean(String username, String address) {
        this.username = username;
        this.address = address;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

4、在 XML 文件中定义数据

 <data>
        <variable
            name="user"
            type="com.renj.databinding.entity.UserBean" />
    </data>

5、Activity 类中修改

① 修改 setContentView() 方法为 ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
② 设置 XML 中需要的变量值 binding.setUser(new UserBean("张三", "浙江省杭州市"));

  • 注意:类名 ActivityMainBinding 是根据布局文件名 "activity_main" 自动生成的,规则为:第一个字母大写,下划线去掉,下划线之后的第一个字母大写(驼峰式命名方式),然后加上'Binding',组成Binding类的类名。当然也可以自定义,在后文中有说明【自定义 [Binding类名]】

事件绑定

① 定义事件处理对象

public class Presenter {
        public void click(View view) {
            int viewId = view.getId();
            if (viewId == R.id.bt_click1) {
                UIUtil.showToast("通过id区分事件1");
            } else if (viewId == R.id.bt_click2) {
                UIUtil.showToast("通过id区分事件2");
            }
        }

        public void clickMethod1(View view) {
            UIUtil.showToast("调用单个方法1");
        }

        public void clickMethod2(View view) {
            UIUtil.showToast("调用单个方法2");
        }

        public void lambdaMethod(View view) {
            UIUtil.showToast("Lambda表达式方式");
        }

        public void paramsMethod(View view, Context context, String params) {
            UIUtil.showToast(params);
        }
    }

② 在布局文件中定义并调用

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="presenter"
            type="com.renj.databinding.activity.EventBindingActivity.Presenter" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="@dimen/default_padding">

        <Button
            android:id="@+id/bt_click1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{presenter.click}"
            android:text="通过id区分事件1"
            android:textColor="@color/color_main_text"
            android:textSize="@dimen/default_text_size" />

        <Button
            android:id="@+id/bt_click2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{presenter::click}"
            android:text="通过id区分事件2"
            android:textColor="@color/color_main_text"
            android:textSize="@dimen/default_text_size" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{presenter.clickMethod1}"
            android:text="调用单个方法1"
            android:textColor="@color/color_main_text"
            android:textSize="@dimen/default_text_size" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{presenter::clickMethod2}"
            android:text="调用单个方法2"
            android:textColor="@color/color_main_text"
            android:textSize="@dimen/default_text_size" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{(view) -> presenter.lambdaMethod(view)}"
            android:text="Lambda表达式方式"
            android:textAllCaps="false"
            android:textColor="@color/color_main_text"
            android:textSize="@dimen/default_text_size" />

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick='@{(view) -> presenter.paramsMethod(view,context,"params")}'
            android:text="传递参数"
            android:textColor="@color/color_main_text"
            android:textSize="@dimen/default_text_size" />

    </LinearLayout>

</layout>

③ 设置XML需要的对象

binding.setPresenter(new Presenter());

事件监听

与事件绑定类似,不同的是参数要和监听的回调参数完全一致

① 定义事件处理对象

public class Presenter {
        public void onEditTextChange(Editable editable) {
                    btContent.set("输入内容:" + editable.toString());
                }
    }

② 在布局文件中定义并调用

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="btContent"
            type="android.databinding.ObservableField&lt;String>" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:padding="@dimen/default_padding">

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:afterTextChanged="@{presenter.onEditTextChange}"
            android:hint="绑定 afterTextChanged 事件"
            android:textColor="@color/color_main_text"
            android:textColorHint="@color/color_main_hint"
            android:textSize="@dimen/default_text_size" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{btContent}"
            android:textColor="@color/color_main_text"
            android:textSize="@dimen/default_text_size" />

    </LinearLayout>

</layout>

③ 设置XML需要的对象

binding.setPresenter(new Presenter());

在 data 标签中 导入类、定义别名

<data>
    <import type="com.renj.databinding.entity.OneWayBean" />
    <import
        alias="User"
        type="com.renj.databinding.entity.UserBean" />
    <variable
        name="user"
        type="User" />
    <variable
        name="oneWayBean"
        type="OneWayBean"/>
</data>
  • 注意别名和定义的变量名不能相同

自定义 Binding类名

 <!--自定义包名和类名-->
<!--<data class="com.ren.databinding.custom.CustomClassNameBinding">-->
<data class=".CustomClassNameBinding">

    <variable
        name="user"
        type="com.renj.databinding.entity.UserBean" />
</data>

表达式

支持的运算符:

  • 数学运算符: + - / * %
  • 字符串拼接: +
  • 逻辑运算符: && ||
  • 二进制: & | ^
  • 一元运算符: +
  • 位运算符: >> >>> <<
  • 比较: == > < >= <=
  • instanceof
  • ()
  • 数据类型: character, String, numeric, null
  • 类型转换(ClassCast)
  • 方法回调(Method calls)
  • 数据属性
  • 数组:[]
  • 三元操作符:? :

一些在java中常用而DataBinding xml中不支持的:

  • this
  • super
  • new
  • 泛型

“??”操作符:

android:text="@{user.displayName ?? user.lastName}"

它等于:

android:text="@{user.displayName != null ? user.displayName : user.lastName}"

双向绑定

我们使用 @{} 时表示单向绑定,而使用 双向绑定 也很简单,使用 @={} 即可。 前提:

我们的数据对象需要继承 android.databinding.BaseObservable 类,并且做如下处理: 使用 @Bindable 注解标记 getXxx 方法,在 setXxx 方法中通知发生改变(notifyPropertyChanged)

public class TwoWayBean extends BaseObservable {
    private String fastName;
    private String lastName;

    public TwoWayBean() {
    }

    public TwoWayBean(String fastName, String lastName) {
        this.fastName = fastName;
        this.lastName = lastName;
    }

    @Bindable
    public String getFastName() {
        return fastName;
    }

    public void setFastName(String fastName) {
        this.fastName = fastName;
        notifyPropertyChanged(BR.fastName);
    }

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

    public void setLastName(String lastName) {
        this.lastName = lastName;
        notifyPropertyChanged(BR.lastName);
    }
}

当然,当我们不想需要整个对象的字段都进行双向绑定,可以使用DataBinding框架提供的其他相关对象:

  • ObservableBoolean
  • ObservableByte
  • ObservableChar
  • ObservableShort
  • ObservableInt
  • ObservableLong
  • ObservableFloat
  • ObservableDouble
  • ObservableParcelable
  • ObservableField

例:

public final ObservableMap<String,String> observableMap = new ObservableArrayMap<>();
observableMap.put("firstName","Zhang");
observableMap.put("lastName","San");

目前Android支持的双向绑定控件(其他的我们也可以自定义):

android_two_way.png

有时候我们可能不知道Binding类的名称 ,比如 RecyclerView.Adapter 中item布局可能有很多,并不会对应特定的Binding类,但是仍然需要通过 onBindViewHolder(VH, int) 去绑定数据,下面的列子是,所有的子布局都有一个"item"变量,通过ViewDataBinding基类去完成绑定:

@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
    ViewDataBinding viewDataBinding = holder.getViewDataBinding();
    viewDataBinding.setVariable(BR.itemValue, dataList.get(position));
    viewDataBinding.executePendingBindings();
}
  • 当一个变量被绑定或者绑定的对象发生变化是,DataBinding会让这些改变排队去在下一帧刷新之前改变,有些时候binding效果必须立刻执行,这时候可以使用 executePendingBindings()

Include 使用

DataBinding 和 include 一起使用时 ,和普通的 include 使用并无太大不同,只需要把所需的绑定数据传给 include 布局即可。

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:bind="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="user"
            type="com.renj.databinding.entity.UserBean" />
    </data>

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

        <include
            layout="@layout/include_layout"
            bind:user="@{user}" />

    </LinearLayout>

</layout>

include 布局 XML 文件

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="user"
            type="com.renj.databinding.entity.UserBean" />
    </data>

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

    </LinearLayout>

</layout>

注意:include 布局 XML 文件的根节点不能是 merge 标签

ViewStub 使用

DataBinding在ViewStub中使用 ,我们需要创建一个监听器,当ViewStub的布局完成时,将绑定数据设置进去。

包含 ViewStub 控件的布局

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

    </data>

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

        <ViewStub
            android:id="@+id/view_stub"
            android:layout="@layout/view_stub_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </LinearLayout>

</layout>

view_stub_layout 布局

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>

        <variable
            name="user"
            type="com.renj.databinding.entity.UserBean" />
    </data>

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

    </LinearLayout>

</layout>

在代码中使用

// 设置监听,控件显示完成之后绑定数据
binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
    @Override
    public void onInflate(ViewStub stub, View inflated) {
        // ViewStub 使用 DataBindingUtil 的方式
        ViewStubLayoutBinding viewStubLayoutBinding = DataBindingUtil.bind(inflated);
        viewStubLayoutBinding.setUser(new UserBean("李四", "湖南省长沙市"));
    }
});

// 显示 ViewStub 控件
binding.viewStub.getViewStub().inflate();

属性设置

当一个被绑定的数据的值发生改变时,Binding类会自动寻找该View上的绑定表达式上的方法去改变View,通过Google数据绑定框架我们可以去自定义这些方法。

对于一个xml的attribute,DataBinding会去寻找 setAttribute 方法,xml 属性的命名空间是没有关系的。比如TextView上的一个属性 android:text,会去寻找 setText(String)。如果表达式返回的是 int 则会去寻找 setText(int),所以必须确保xml中表达式返回正确的数据类型,必要时需要数据转换。 我们可以比较容易地为任何属性创造出setter去使用DataBinding。比如support包下的 DrawerLayout 没有任何属性,但是却有很多setter,下面利用这些已有的setter中的一个:

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

自定义属性

一些xml属性需要自己去定义并实现逻辑,那么我们可以使用 BindingAdapterBindingMethodsBindingMethod 注解去自定义个自己的 setter,三个注解,但是 @BindingMethods@BindingMethod 需要一起使用,所以是有两种方式来实现自定义的属性:

  • 方式1:使用 @BindingAdapter 方式
  • 方式2:使用 @BindingMethods@BindingMethod 相结合的方式

方式1:使用 @BindingAdapter

@BindingAdapter(value = {"url"})
public static void setImageUrl(ImageView imageView, String url) {
    Glide.with(imageView).load(url).into(imageView);
}

以上代码我们给 ImageView 控件自定义了一个 url 属性,那么在XML布局中使用 ImageView 控件时就可以直接使用该属性,并且会自动使用Glide加载图片( 以上代码的位置随意,建议写在一个新的独立类中 ):

<ImageView
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:layout_marginTop="@dimen/default_line_space"
    app:url="@{url}" />

@BindingAdapter 说明:用于自定义属性,需要设置多个属性时可以这样写 @BindingAdapter({"url","loading","error"});同时 @BindingAdapter 还有一个参数 requireAll,设置 @BindingAdapter(value = {"url","loading","error"}, requireAll = false)requireAll 的作用表示我们在 @BindingAdapter 中定义的所有属性是不是都需要在 xml 文件中进行设置,如果为 true ,表示定义的所有属性都需要在 xml 中设置,默认为 true,为 false表示可以在 xml 中不需要设置所有定义的属性。

方式2:使用 @BindingMethods@BindingMethod 相结合的方式

@BindingMethods({
        @BindingMethod(type = MyTextView.class, attribute = "textChangeToast", method = "onTextChangeToast")
})
public class MyTextView extends android.support.v7.widget.AppCompatTextView {
    public MyTextView(Context context) {
        super(context);
    }

    public MyTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public void onTextChangeToast(String content) {
        UIUtil.showToast(content);
    }
}
  • 说明:
    • type:要操作的属性属于哪个View类,类型为class对象,比如:ImageView.class
    • attribute:xml属性,类型为String ,比如:"android:tint"
    • method:指定xml属性对应的set方法(触发方法),类型为String,比如:"setImageTintList"

开发者自定义的@BindingAdapter和android自带的发生冲突时,DataBinding会优先采用开发者自定义的

自定义双向绑定

前面我们说到了双向绑定,并提到了Android中可以直接使用双向绑定的一些控件, 但是当我们自定义控件时,默认并不会绑定属性。而单向(正向)绑定,我们可以使用 @BindingAdapter@BindingMethods@BindingMethod 注解来自定义。

但是如果我们需要实现双向绑定,除了实现正向绑定外,还需要实现逆向绑定。DataBinding也为我们 实现逆向绑定 提供了相关的注解:@InverseBindingAdapter@InverseBindingMethods@InverseBindingMethod,同样的有三个注解,但是 @InverseBindingMethods@InverseBindingMethod 需要一起使用,所以是有两种方式来实现逆向绑定:

  • 方式1:使用 @InverseBindingAdapter 方式
  • 方式2:使用 @InverseBindingMethods@InverseBindingMethod 相结合的方式

方式1:使用 @InverseBindingAdapter

@InverseBindingAdapter 需要与 @BindingAdapter 配合使用。

public class MySeekBar extends android.support.v7.widget.AppCompatSeekBar {
    private static InverseBindingListener inverseBindingListener;

    public MySeekBar(Context context) {
        super(context, null);
        init();
    }

    public MySeekBar(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MySeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        this.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                // 触发反向数据的传递
                if (inverseBindingListener != null)
                    inverseBindingListener.onChange();
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });
    }

    @BindingAdapter(value = "myProgress", requireAll = false)
    public static void setMyProgress(MySeekBar mySeekBar, int progress) {
        // 防止死循环
        if (getMyProgress(mySeekBar) != progress) {
            mySeekBar.setProgress(progress);
        }
    }

    @InverseBindingAdapter(attribute = "myProgress", event = "myProgressAttrChanged")
    public static int getMyProgress(MySeekBar mySeekBar) {
        return mySeekBar.getProgress();
    }

    @BindingAdapter(value = {"myProgressAttrChanged"}, requireAll = false)
    public static void setMyProgressAttrChanged(MySeekBar mySeekBar, InverseBindingListener inverseBindingListener) {
        if (inverseBindingListener != null) {
            MySeekBar.inverseBindingListener = inverseBindingListener;
        } else {
            Log.e("MySeekBar", "InverseBindingListener Null Exception");
        }
    }
}
  • 说明:
    • attribute:String 类型,必填,表示当值发生变化时,要从哪个属性中检索这个变化的值;例:"android:text"
    • event: String 类型,非必填,如果填写,则使用填写的内容作为 event 的值;如果不填,在编译时会根据 attribute 的属性名再加上后缀 "AttrChanged" 生成一个新的属性作为 event 的值。作用: 当 View 的值发生改变时用来通知 dataBinding 值已经发生改变了。开发者一般需要使用 @BindingAdapter 创建对应属性来响应这种改变(简单来说就是 @InverseBindingAdapter 注解的 event 值需要和@BindingAdapter 的 value 值相同,对被 @BindingAdapter 注解的方法名没有特殊要求,建议使用 setEventValue的方式[EevntValue表示]@InverseBindingAdapter 注解的 event 值名)

方式2:使用 @InverseBindingMethods@InverseBindingMethod 相结合的方式

@InverseBindingMethods@InverseBindingMethod 的使用 与@BindingMethods@BindingMethod 的使用类似。

@InverseBindingMethods({
        @InverseBindingMethod(type = MySeekBar2.class,attribute = "myProgress2",event = "myProgress2AttrChange",method = "getMyProgress2")
})
public class MySeekBar2 extends android.support.v7.widget.AppCompatSeekBar {
    private static InverseBindingListener inverseBindingListener;

    public MySeekBar2(Context context) {
        super(context, null);
        init();
    }

    public MySeekBar2(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MySeekBar2(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        this.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                // 触发反向数据的传递
                if (inverseBindingListener != null)
                    inverseBindingListener.onChange();
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {

            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {

            }
        });
    }

    public void setMyProgress2(int progress) {
        // 防止死循环
        if (getMyProgress2() != progress) {
            setProgress(progress);
        }
    }

    public int getMyProgress2() {
        return getProgress();
    }

    public void myProgress2AttrChange(InverseBindingListener inverseBindingListener) {
        if (inverseBindingListener != null) {
            MySeekBar2.inverseBindingListener = inverseBindingListener;
        } else {
            Log.e("MySeekBar2", "InverseBindingListener Null Exception");
        }
    }
}
  • 说明:
    • type:要操作的属性属于哪个View类,类型为class对象,比如:ImageView.class
    • attribute:xml属性,类型为String ,比如:"android:tint"
    • event: String 类型,非必填,如果填写,则使用填写的内容作为 event 的值;如果不填,在编译时会根据 attribute 的属性名再加上后缀 "AttrChanged" 生成一个新的属性作为 event 的值。比如,如果自己定义了attribute=”xxx”,那么Android系统自动会匹配查找xxxAttrChanged方法,该方法是set开头,那么就最终变成:setXxxAttrChanged;如果开发者在event里面自己随意定义了一个方法名,那么必须严格一致确保类里面有这个方法,比如,如果用户在InverseBindingMethod的event里面任意定义了一个方法“abcdefg”,那么必须在该注解类有一个同名方法如setAbcdefg()(set是否有都能成功,但是建议加上)。作用: 当 View 的值发生改变时用来通知 dataBinding 值已经发生改变了。
    • method:非必填。 如果未提供,则使用属性名称查找方法名称,前缀为“is”或“get”

Converters 使用

使用@BindingConversion注解,作用:用于将表达式类型自动转换为setter中使用的值

有时候我们想这样写xml属性:

<View
    android:id="@+id/view_converter"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@{status ? @color/color_converter_color1 : @color/color_converter_color2}" />

但是xml属性 android:background 的对应set方法 setBackground() 接收的参数是一个Drawable,那么就需要通过 BindingConversion 注解来实现转换:

/**
 * 定义一个 Converter ,将 {@code int} 类型的 {@link android.graphics.Color} 转换为 {@link ColorDrawable}
 *
 * @param color
 * @return
 */
@BindingConversion
public static ColorDrawable colorToColorDrawable(int color) {
    return new ColorDrawable(color);
}
DataBindingUtil.setDefaultComponent(new TestBindingComponent());

通过修改Component我们可以对指定的使用了DataBinding的控件属性进行统一修改,具体使用 请查看代码

About

Android DataBinding 使用

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages