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,"我是改变后的文本!");
}
运行效果和之前的类似,这里就不同贴出图片了。