mvvm设计模式之Data Binding库的使用

DataBinding是google推出的一个mvvm设计模式的官方类库,想比与mvp设计模式,该模式提供了view与数据直接绑定的能力,今天这票文章就来教大家如何使用该库。

准备条件

1、Android studio版本大于1.3,并且Gradle 版本大于1.5.0-alpha1

2、在module模块的build文件中添加下面的代码块,然后同步工程

android {
    ....
    dataBinding {
        enabled = true
    }
}
下面开始正式使用。


布局文件

       布局文件和以前的布局文件不同的是现在的布局文件需要以layout做根布局,然后里面包含两个部分,一个是节点<data>另一个就是我们以前的布局文件原封不动的代码,类似与下面这样:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </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}"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"/>
   </LinearLayout>
</layout>

这里我们需要注意的就是<data>节点,在这里我们定义了一个<variable>子节点,相当于定义了一个变量,该变量可以是一个基本数据类型比如int float,也可以是一个类,比如上面的代码。我们可以在这个布局文件中使用这个变量的属性和方法,使用方式为:@{},在大括号中就可以和使用java代码的方式一样去使用属性和调用方法。

我们来看下User类的代码:

public class User {
   public final String firstName;
   public final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
}

当然你也可以给属性定义为私有的,然后通过get方法去获取,不过那样性能有所降低。

到这里我们还只是将数据和view绑定起来,还需要在java代码中将变量赋值,来看代码:

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
   User user = new User("Test", "User");
   binding.setUser(user);
}

其实我们如果在布局文件中使用了databinding,那么其自动会为我们生成一个以布局文件名去掉下划线加Binding结尾的类,向上面的DataBindingUtil返回的MainActivityBinding类,我们也可以使用这个类来给activity和布局绑定并且给其赋值,比如下面这样:

MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
为了代码的风格一致和在某些情况下不清楚返回的类名,我推荐大家使用DataBindingUtil这个类来绑定。



事件处理


事件处理有两种方式,一种是方法关联(Method References)、一种是监听绑定(Listener Binding).他们两者的不同之处是后一个在事件触发的时候才创建回调方法。

1、方法关联的使用

这种方式与以前在onclick方法中写方法名然后在activity中定义方法类似,不同之处有两点:第一就是现在可以给回调方法定义在别的类中;第二就是现在能在编译阶段就检查出回调方法是否编写错误,避免了以前在运行的时候因为找不到回调方法而奔溃。

首先在xml文件中这样写:

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

    <data>
        <variable
            name="clickCallBack"
            type="com.example.leixinxue.databindingdemo.ClickCallBack" />
    </data>

    <RelativeLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:onClick="@{clickCallBack.onClick}"
            android:paddingBottom="10dp"
            android:paddingTop="10dp"
            android:text="吐司"
            android:textSize="14dp" />
    </RelativeLayout>
</layout>

需要指出的是clickCallBack.onClick也可以使用这种方式:clickCallBack::onClick.然后我们来看看这个类是什么样的:

public class ClickCallBack {
    public void onClick(View view) {
        Log.e("kk", "-------->点到");
        Toast.makeText(view.getContext(), "点到我啦!", Toast.LENGTH_SHORT).show();
    }
}

最后还需要在activity中给该类绑定到xml文件中:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setClickCallBack(new ClickCallBack());
    }

我们来看一下运行效果图:



2、绑定监听的使用

这种方式是使用lambda表达式去书写,类似于这样@{(xx)->xxx.xxx(xx,xx,..)},可以选择多种参数,也可以一个参数也没有,除了这里的写法不一样外,别的也和上面一样,需要在activity中设置,创建一个xxx类和其带N个参数的方法。比如这样:

<RelativeLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:onClick="@{()->clickCallBack.onClick()}"
            android:paddingBottom="10dp"
            android:paddingTop="10dp"
            android:text="吐司"
            android:textSize="14dp" />
    </RelativeLayout>

使用了一个没有参数的方式,假如我们需要参入被点击的view到CallBack中,我们需要这样写:

 <RelativeLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:onClick="@{(view)->clickCallBack.onClick(view)}"
            android:paddingBottom="10dp"
            android:paddingTop="10dp"
            android:text="吐司"
            android:textSize="14dp" />
    </RelativeLayout>

这里就需要给onClick使用一个带View的参数,我们也可以给onClick创建多个参数,比如这样:

    <RelativeLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:onClick="@{(view)->clickCallBack.onClick(task,extraString,view)}"
            android:paddingBottom="10dp"
            android:paddingTop="10dp"
            android:text="吐司"
            android:textSize="14dp" />
    </RelativeLayout>

在activity中的代码:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setTask(new Task());
        binding.setExtraString("我是额外的信息");
        binding.setClickCallBack(new ClickCallBack());
    }

需要注意的一点是这种方式得参数的位置别弄错了,当然我们这里还可以给onclick一个点击后的返回值,这和定义一个有返回值的方法是一样的,需要我们自己返回一个值。



布局文件的细节


我们可以使用<import>标签导入类,然后直接使用类名来访问类,如果类名一致的情况下我们可以给类名一个别名,比如这样使用:

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

    <data>

        <import
            alias="CallBack"
            type="com.example.leixinxue.databindingdemo.ClickCallBack" />

        <variable
            name="clickCallBack"
            type="CallBack" />

        <variable
            name="task"
            type="com.example.leixinxue.databindingdemo.Task" />

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

    <RelativeLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:onClick="@{(view)->clickCallBack.onClick(task,extraString,view)}"
            android:paddingBottom="10dp"
            android:paddingTop="10dp"
            android:text="吐司"
            android:textSize="14dp" />
    </RelativeLayout>
</layout>

这种方式和我们在代码中使用import导入是一样的。如果是类中有静态的变量或者类,我们在导入类后就可以直接使用类名.xxx的方式访问。

这里有2点需要注意:

1、变量的默认值是变量类型的默认值,比如引用类型为null,数字为0.

2、layout中默认定义了一个context的变量,我们直接可以使用



使用include标签的时候可以给定义的变量传递到include标签里面去,不过在include中还是得申明变量,不支持<merge>标签定义变量,使用示例如下:

<RelativeLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <include
            layout="@layout/a"
            bind:clickCallBack="@{clickCallBack}" />
    </RelativeLayout>
</layout>

bind是一个命名空间。


下面的运算符可以直接在xml文件中使用:

  • Mathematical + - / * %
  • String concatenation +
  • Logical && ||
  • Binary & | ^
  • Unary + - ! ~
  • Shift >> >>> <<
  • Comparison == > < >= <=
  • instanceof
  • Grouping ()
  • Literals - character, String, numeric, null
  • Cast
  • Method calls
  • Field access
  • Array access []
  • Ternary operator ?:

使用示例:

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'


可是下面的不能使用:

  • this
  • super
  • new
  • Explicit generic invocation

“??”表示不为null,比如:

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

和这样的表达式是一样的:

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

集合数组的使用方式都是用[index]来访问,如下:

<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List<String>"/>
    <variable name="sparse" type="SparseArray<String>"/>
    <variable name="map" type="Map<String, String>"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"


资源的引用:

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
资源格式化:

android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"



给数据添加一个监听,实现数据的改变自动改变UI

添加监听有三种,分别是:Observable objects, observable fields, and observable collections.

object方式是给整个类定义一个监听,如果类中的属性改变就会导致UI改变,这种方式需要我们写代码触发更新,比较麻烦,google给我们封装好了一个BaseObservable,我们只需要继承它就可以了。下面看一个例子,我们点击按钮的时候改变绑定textview上面的变量,然后看其是否改变textview的内容。首先看xml代码:

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

    <data>

        <variable
            name="mainActivity"
            type="com.example.leixinxue.databindingdemo.MainActivity" />

        <variable
            name="inFoType"
            type="com.example.leixinxue.databindingdemo.InfoType" />
    </data>

    <LinearLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{mainActivity.onClick}"
            android:text="改变文本内容" />

        <TextView
            android:id="@+id/txtv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="@{inFoType.info}" />
    </LinearLayout>
</layout>

然后看我们的activity文件:

public class MainActivity extends AppCompatActivity {
    ActivityMainBinding binding;
    InfoType infoType = new InfoType();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setInFoType(infoType);
        binding.setMainActivity(this);
    }

    public void onClick(View view) {
        infoType.setInfo("我是改变后的文本!");
    }
}

最后来看一下我们的Pojo类:

public class InfoType extends BaseObservable {
    private String info = "我是默认的文本";

    @Bindable
    public String getInfo() {
        return info;
    }


    public void setInfo(String info) {
        this.info = info;
        notifyPropertyChanged(BR.info);
    }
}

我们来看下运行效果:



我们看到完美的改变了,这种方式代码量少,使用简单。


ObservableFields

这种方式可以直接定义一个自带改变通知的属性,目前有:ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable

实现和上面一样的功能的例子,首先是pojo文件:

public class InfoType {
    public final ObservableField<String> info = new ObservableField<>();
}

xml布局文件没有变化,来看activity的代码:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setInFoType(infoType);
        infoType.info.set("我是默认的文本!");
        binding.setMainActivity(this);
    }

    public void onClick(View view) {
        infoType.info.set("我是改变后的文本!");
    }

运行效果是一样的,这里就不再贴图了。


Observable Collections

这种方式是定义一个双向绑定的map,还是上面的例子,我们首先是pojo文件:

public class InfoType {
    //ObservableArrayMap<String, Object> info = new ObservableArrayMap<>();
   public ObservableArrayList<String> info = new ObservableArrayList<>();

}

然后是布局文件:

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

    <data>

        <variable
            name="mainActivity"
            type="com.example.leixinxue.databindingdemo.MainActivity" />

        <variable
            name="inFoType"
            type="com.example.leixinxue.databindingdemo.InfoType" />
    </data>

    <LinearLayout
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:onClick="@{mainActivity.onClick}"
            android:text="改变文本内容" />

        <TextView
            android:id="@+id/txtv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:text="@{inFoType.info[0]}" />
    </LinearLayout>
</layout>

最后是activity文件:

  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setInFoType(infoType);
        infoType.info.add("我是默认的文本!");
        binding.setMainActivity(this);
    }

    public void onClick(View view) {
        infoType.info.set(0,"我是改变后的文本!");
    }

运行效果和之前的类似,这里就不同贴出图片了。



总结

          到这里databinding的语法基本都介绍完了,类似于google的中文版教程,如果喜欢看英文版本的可以移步到官网:https://developer.android.com/topic/libraries/data-binding/index.html#data_binding_layout_files。google花大力气就设计这个库,想必是以后会成为主流的。即使我们的项目引入了该库,我们也可以控制其风险,因为我们可以在自己想使用的地方去使用,不想使用了我们完全可以按照原来的方式去写代码。有的UI和变量绑定实现变量改变UI自动改变的情况使用这个类库是非常方便的,其实语法也很简单,和java代码和类似,我们可以使用逻辑运算符在xml文件中定义一些依赖可以是开发代码大大减少,推荐大家使用这个类库!






欢迎关注我的微信公众号“android教科书”,最新最好的文章第一时间送到手!可以扫描下面的二维码来关注:




















  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值