1 扯淡
在DataBinding之前,我们不可避免地要编写大量诸如 findViewById、setText和setOnClickListener等代码。通过 Data Binding,我们可以通过声明式布局以精简的代码来绑定应用程序逻辑和布局,这样就不用编写大量的模板代码了。在DataBinding和MVVM架构的配合下,代码显得更加的简洁。MVVM和MVP相比没有太多的搭建框架代码,MVVM模型如下。
2 DataBinding
首先要说的是DataBinding是一个类似于便捷开发的工具,而MVVM是一种代码架构,两者并无关系,DataBinding也可以使用在MVP架构中,只是说DataBinding和MVVM和DataBinding的结合更加合适。再使用DataBinding时,首先需要在Module的build.gradle中添加如下的配置:
android {
...
dataBinding{
enabled = true
}
}
2.1 基础使用
首先常见一个简单的Model对象UserInfo。
public class UserInfo {
private String name;
private String pwd;
public UserInfo(String name, String pwd) {
this.name = name;
this.pwd = pwd;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
接着编写布局文件。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 定义该布局需要绑定的数据名称和类型-->
<data>
<variable
name="userInfo"
type="com.itzb.mvvmdemo.model.UserInfo" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入账号"
android:text="@{userInfo.name}" />
<EditText
android:id="@+id/et_pwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码"
android:text="@={userInfo.pwd}" />
<Button
android:id="@+id/bt_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="登录" />
</LinearLayout>
</layout>
根节点由某一个容器(比如LinearLayout)变为layout,layout中包含了data节点和传统的视图,在data中定义了variable节点。其中,name属性表示变量的名称,type表示这个变量的类型,也就是UserInfo这个实体类的位置。variable节点的每一个变量都会在Binding辅助类中生成对应的 getters 和 setters。接着将@{userInfo.name}和@{userInfo.pwd}赋值给TextView的text属性。最后在Activity中将实体类和布局文件进行了绑定。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//1.先rebuild,2.书写代码绑定
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
UserInfo userInfo = new UserInfo("123", "456");
binding.setUserInfo(userInfo);//给控件赋值
}
}
运行结果:
2.2 事件处理
Data Binding也可以对点击事件进行处理,其有两种写法。其中第一种写法如下所示:
<Button
android:id="@+id/bt_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="登录" />
首先在XML中定义一个Button,并设定它的id。接下来在Java代码中引用它:
binding.btLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
btNext指的就是XML中id为bt_next的Button。当我们给控件设定id时,就会在Binding辅助类中生成一个相应的public final 字段,以供调用。还有一种写法,在控件里通过@{}来设置自己的点击事件,接着在Java代码中引用它
<Button
android:id="@+id/bt_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{OnClickListener}"
android:text="登录" />
public class UserInfo {
...
public View.OnClickListener loginClickListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
}
};
}
2.3 布局属性
1. import用法与别名
data节点支持import用法,如下所示
<data>
<import type="com.itzb.mvvmdemo.model.UserInfo"/>
<variable
name="userInfo"
type="UserInfo" />
</data>
data节点中的import需要写明具体导入的类名,这一点和Java的import是不同的。如果import引用了两个相同的类名,我们就可以用别名来区分它们
<data>
<import type="com.itzb.mvvmdemo.UserInfo" alias="zhangsan"/>
<import type="com.itzb.mvvmdemo.model.UserInfo"/>
<variable
name="zhangsan"
type="UserInfo" />
<variable
name="userInfo"
type="UserInfo" />
</data>
2.变量定义
我们也可以定义一些基本数据类型和String类型。除了可以定义基本类型的变量外,我们还可以定义List、Map等这样的集合变量。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 定义该布局需要绑定的数据名称和类型-->
<data>
<import type="java.util.ArrayList" />
<import type="java.util.Map" />
<variable
name="name"
type="String" />
<variable
name="pwd"
type="int" />
<variable
name="list"
type="ArrayList<String>" />
<variable
name="map"
type="Map<String,String>" />
<variable
name="array"
type="String[]" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入账号"
android:text="@{name}" />
<EditText
android:id="@+id/et_pwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码"
android:text="@={String.valueOf(pwd)}" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={list.get(0)}" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={map.get('age')}" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={array[1]}" />
</LinearLayout>
</layout>
为了支持泛型,我们可以使用转义字符,虽然这样会在代码中标记红色,但却可以运行。List集合可以通过list[index]来获取数值,也可以调用list.get(index)达到相同的目的。同样,Map集合也是如此。在Activity中使用变量,如下:
binding.setName("123");
binding.setPwd(456);
ArrayList<String> list = new ArrayList<>();
list.add("111");
binding.setList(list);
Map<String, String> map = new HashMap<>();
map.put("age","18");
binding.setMap(map);
String[] array = {"111","222"};
binding.setArray(array);
3.静态方法调用
在布局文件中也可以直接调用静态方法,达到对数据进行转换的目的
public class Utils {
public static String getName(UserInfo userInfo) {
return userInfo.getName();
}
}
<data>
<import type="com.itzb.mvvmdemo.Utils"/>
<variable
name="userInfo"
type="com.itzb.mvvmdemo.model.UserInfo" />
</data>
<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入账号"
android:text="@{Utils.getName(userInfo)}" />
4.支持表达正式
XML中的表达式与 Java 表达式有很多相似之处。下面是二者的相同之处。
• 数学表达式:+-/*%
• 字符串拼接:+-
• 逻辑表达式:&&||
• 位操作符:&|^
• 一元操作符:+-!~
• 位移操作符:>>>>><<
• 比较操作符:==><>=<=
• instanceof
• 分组操作符:()
• 字面量
• character,String,numeric,null
• 强转、方法调用
• 字段访问
• 数组访问:[]
• 三元操作符:?:
2.4 动态更新和双向绑定
在前面的例子中,如果 Model 实体类的内容发生变化,那么界面 UI 是不会动态更新的。Data Binding提供了3种动态更新机制,根据Model实体类的内容来动态更新UI,分别对应于类(Observable)、字段(ObservableField)和集合类型(Observable 容器类)。从 MVVM 模式的角度来讲,通过动态更新机制,当Model发生变化时,就会通知ViewModel对View进行动态更新。结合动态更新机制,我们还可以实现双向绑定:当 View 发生变化时,ViewModel也会通知Model进行数据更新。
1.使用Observable
通过继承BaseObservable来实现动态更新
public class UserInfo extends BaseObservable {
private String name;
private String pwd;
public UserInfo(String name, String pwd) {
this.name = name;
this.pwd = pwd;
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(com.itzb.mvvmdemo.BR.name);
}
@Bindable
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
notifyPropertyChanged(com.itzb.mvvmdemo.BR.pwd);
}
}
我们只需要在getter上使用@Bindable注解,在setter中通知更新就可以了。其中BR是编译时生成的类,其功能与R.java类似,用@Bindable 标记过的getter方法会在BR中生成一个相应的字段。在setter中调用notifyPropertyChanged(BR.name) 通知系统BR.name这个字段的数据已经发生变化并更新UI。我们在Activity中使用UserInfo
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//1.先rebuild,2.书写代码绑定
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
userInfo = new UserInfo("123", "456");
binding.setUserInfo(userInfo);//给控件赋值
binding.btLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
userInfo.setName("zzz");
}
});
}
当点击Button时,更改了userInfo的name字段,这时候会发现,界面上显示的name也发生了改变。
2.使用ObservableField
除了继承BaseObservable来实现动态更新外,还可以使用系统为我们提供的所有基本数据类型对应的Observable 类,比如ObservableInt、ObservableFloat、ObservableBoolean等,也可以使用引用数据类型和基本数据类型通用的ObservableField。它们都继承自BaseObservable。
public class UserInfo {
public ObservableField<String> name = new ObservableField<>();
public ObservableField<String> pwd = new ObservableField<>();
public UserInfo(String name, String pwd) {
this.name.set(name);
this.pwd.set(pwd);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//1.先rebuild,2.书写代码绑定
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
userInfo = new UserInfo("123", "456");
binding.setUserInfo(userInfo);//给控件赋值
binding.btLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
userInfo.name.set("zzz");
}
});
}
3. 使用Observable容器类
如果有多个UserInfo类型的数据需要动态更新怎么办?系统同样也提供了Observable容器类帮助我们解决这一问题。Observable容器类包括ObservableArrayList和ObservableArrayMap。这个场景使用ObservableArrayList是最佳选择。这时无须创建符合动态更新机制的Model实体类,只需要在代码中应用ObservableArrayList就可以了。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//1.先rebuild,2.书写代码绑定
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
userInfo1 = new UserInfo("123", "456");
userInfo2 = new UserInfo("abc", "def");
list = new ObservableArrayList<>();
binding.setList(list);//给控件赋值
binding.btLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
userInfo1.name.set("zzz");
userInfo2.name.set("aaa");
}
});
}
4.双向绑定
从MVVM模式的角度来讲,双向绑定就是Model和VIew通过ViewModel进行双向动态更新。前面讲到了Model实体类发生变化,UI会动态更新;反过来,如果UI发生变化,Model实体类也可以动态更新。
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 定义该布局需要绑定的数据名称和类型-->
<data>
<variable
name="userInfo"
type="com.itzb.mvvmdemo.model.UserInfo" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入账号"
android:text="@={userInfo.name}" />
<EditText
android:id="@+id/et_pwd"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入密码"
android:text="@={String.valueOf(pwd)}" />
<Button
android:id="@+id/bt_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="登录" />
</LinearLayout>
</layout>
在布局文件中定义 EditText 来改变 UserInfo的 name 字段,关键就是将"@{userInfo.name}"改为"@={userInfo.name}"。定义TextView来动态显示ObSwordsman的name字段的变化。最后定义Button来重置ObSwordsman的name字段。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//1.先rebuild,2.书写代码绑定
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
userInfo = new UserInfo("123", "456");
binding.setUserInfo(userInfo);//给控件赋值
binding.btLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
userInfo.name.set("zzz");
}
});
}
这样运行程序,当EditText编辑的内容发生变化时,TextView中的内容也会相应地变化。当我们点击Button时,EditText的内容会改变。这样就实现了一个简单的双向绑定。
MVVM与DataBinding基础(下):https://blog.youkuaiyun.com/zb987570437/article/details/103377641