Android MVVM 架构搭建(1):DataBinding 入门指南
1. 简介
DataBinding 是谷歌官方推出的一个库,用于编写声明式的布局文件。它允许用最少的代码来绑定 App 逻辑和布局文件,从而减少样板代码。
Data Binding 库不仅灵活而且广泛兼容,它是一个 Support 库,因此你可以在所有的 Android 平台上使用,最低支持到 Android 2.1(API 等级 7+)。
需求: Android Plugin for Gradle 1.5.0-alpha1 或更高版本。
2. 环境搭建
在 app/build.gradle 文件中,需要添加 DataBinding 配置。如下所示:
android {
...
dataBinding {
enabled = true
}
}
Data Binding 插件将会在你的项目内添加必需的以及编译配置依赖。
3. 编写 Layout 文件
3.1 DataBinding 表达式
DataBinding 的 layout 文件与普通布局文件有所不同:起始根标签是 <layout>,接下来是一个 <data> 元素以及一个视图的根元素。这个视图元素就是你没有使用 DataBinding 时的普通布局文件的根元素。
<?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"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable name="user" type="demo.com.databindingdemo.User"/>
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/user_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.mUserName}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/user_age"
android:text='@{user.mUserage+""}'
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/user_name" />
</android.support.constraint.ConstraintLayout>
</layout>
在 <data> 内描述了一个名为 user 的变量属性,使其可以在这个 layout 中使用:
<variable name="user" type="demo.com.databindingdemo.User"/>
在 layout 的属性表达式写作 @{}。下面是一个 TextView 的 text 设置为 user 的 mUserName 属性:
<TextView
android:id="@+id/user_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.mUserName}" />
3.2 Data 对象
上面我们创建了一个 User 对象。这种类型的对象具有不可变的数据,通常在应用中被读取一次数据后再也不会改变。
public class User {
public final String mUserName;
public final int mUserage;
public User(String userName, int userAge) {
this.mUserName = userName;
this.mUserage = userAge;
}
}
也可以使用遵循一系列约定的对象,例如使用访问器方法(即 getter 方法),如下所示:
public class User {
private final String mUserName;
private final int mUserage;
public User(String userName, int userAge) {
this.mUserName = userName;
this.mUserage = userAge;
}
public String getUserName() {
return mUserName;
}
public int getUserage() {
return mUserage;
}
}
从 DataBinding 的角度来看,这两个类是等价的。用于 TextView 中的 android:text 属性的表达式 @{user.mUserName} 将访问前者 POJO 对象中的 mUserName 字段和后者 JavaBeans 对象中的 getUserName() 方法。
3.3 绑定数据
默认情况下,一个 Binding 类会基于 layout 文件的名称而产生,将其转换为 Pascal Case(首字母大写的命名规范)并且添加'Binding'后缀。上述的 layout 文件是 activity_main.xml,因此生成的类名是 ActivityMainBinding。此类包含从 layout 属性到 layout 的 Views 中所有的 bindings,并且它还知道如何给 Binding 表达式分配数值。
创建 bindings 的最简单的方式是在 inflating(layout 文件与 Activity/Fragment 的链接)期间:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
User user = new User("sam", 11);
binding.setUser(user);
}
}
运行之后,数据将会自动绑定。
如果你在 ListView 或者 RecyclerView adapter 使用 DataBinding 时,你可能会使用:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
如果将上面 User 的属性改为私有,则会遇到如下错误:
Found data binding errors.
****/ data binding error ****msg:Could not find accessor demo.com.databindingdemo.User.mUserName
3.4 事件处理
数据绑定允许你编写表达式来处理 View 分发的事件。事件属性名字取决于监听器方法名字。例如 View.OnLongClickListener 有 onLongClick() 的方法,因此这个事件的属性是 android:onLongClick。
处理事件有两种主要方法:Method References 和 Listener Bindings。
Method References
你可以直接引用 Activity 或 ViewModel 中的方法:
<Button
android:onClick="@{viewModel::onButtonClick}" />
Listener Bindings
对于复杂的监听器,可以使用 Lambda 表达式:
<Button
android:onClick="@{() -> viewModel.onButtonClick()}" />
4. 深入 DataBinding 用法
4.1 Import
零个或多个 import 元素可能在 data 元素中使用。这些只用在你的 layout 文件中添加引用,就像在 Java 中一样:
<data>
<import type="android.view.View"/>
</data>
现在,View 可以使用你的 Binding 表达式:
<TextView
android:visibility="@{user.isMan ? View.VISIBLE : View.GONE}" />
当类名有冲突时,其中一个类名可以重命名:
<data>
<import type="android.view.View"/>
<import type="demo.com.databindingdemo.View" alias="test"/>
<variable name="user" type="demo.com.databindingdemo.User"/>
</data>
导入的类型还可以在表达式中使用 static 属性和方法:
public class StringUtils {
public static String translateStr(String str) {
return str + "haha";
}
}
<TextView
android:text="@{StringUtils.translateStr(user.mUserName)}" />
4.2 Variables
在 data 中可以使用任意数量的 variable 元素。每一个 variable 元素描述了一个用于 layout 文件中 Binding 表达式的属性。
产生的 Binding 类对于每一个描述的 Variables 都会有 setter 和 getter。这些 Variables 会使用默认的 Java 值 - null(引用类型)、0(int)、false(boolean)等等,直到调用 setter 时。
4.3 自定义 Binding 类名称
默认情况下,Binding 类的命名是基于所述 layout 文件的名称。如果要想让该类生成在不同的包种,你需要添加前缀 .,或者提供整个包名:
<data class="com.demo.ContactItem">
...
</data>
4.4 表达式
常用表达式跟 Java 表达式很像。以下这些是一样的:
- 数学运算:+ - / * %
- 字符串连接:+
- 逻辑:&& ||
- 比较:== > < >= <=
- 三元运算:?:
- Null 合并操作:?? (左边的对象如果不是 null,选择左边;否则选择右边)
示例:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:text="@{user.displayName ?? user.lastName}"
集合常用的 arrays、lists、sparse lists 以及 maps,为了简便都可以使用 [] 来访问。
<data>
<variable name="list" type="List<String>"/>
<variable name="map" type="Map<String, String>"/>
</data>
...
android:text="@{list[index]}"
android:text="@{map[key]}"
Resources 使用正常的表达式来访问 resources 也是可行的:
android:padding="@{large ? @dimen/largePadding : @dimen/smallPadding}"
格式化字符串和复数可以通过提供参数来判断:
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
5. 可观察数据对象
DataBinding 库允许我们创建可观察的对象、字段或集合,当我们的数据发生改变时需要通知其他对象就可以使用 DataBinding,它主要有三种不同类型的可观察类型,分别为:对象、字段或集合。当这些可观察的数据对象绑定到 UI,并且数据对象的属性发生改变时,UI 也将自动更新。
5.1 Observable 对象
实现 android.databinding.Observable 接口的类可以允许附加一个监听器到 Bound 对象以便监听对象上的所有属性的变化。
BaseObservable 的基类为实现监听器注册机制而创建。Data 实现类依然负责通知当属性改变时。这是通过指定一个 @Bindable 注解给 getter 以及 setter 内通知来完成的。
public class UserObserve extends BaseObservable {
private String name;
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
@Bindable
public String getName() {
return name;
}
}
5.2 Observable 字段
一些开发者想要节省时间或者几乎没有几个属性,可以使用 ObservableFields。ObservableFields 是自包含具有单个字段的 observable 对象。它有所有基本类型和一个引用类型。
public class UserObserverField {
public final ObservableField<String> name = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
使用时:
user.firstName.set("Google");
int age = user.age.get();
5.3 Observable 集合
一些 App 使用更多的动态结构来保存数据。Observable 集合允许键控访问这些数据对象。
ObservableArrayMap 用于键是引用类型,如 String。
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("name", "Jack");
user.put("age", 17);
在 layout 文件中,通过 String 键可以访问 map:
<TextView
android:text='@{user["name"]}' />
ObservableArrayList 用于键是整数:
ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Jack");
user.add(17);
6. 总结与最佳实践
DataBinding 极大地简化了 Android 开发中 UI 与数据的交互过程。通过本文的介绍,我们了解了 DataBinding 的基本配置、表达式语法、变量绑定以及可观察对象的使用。
在实际项目中,建议遵循以下最佳实践:
- 避免复杂逻辑:尽量保持 Layout 中的表达式简洁,复杂的业务逻辑应放在 ViewModel 或 Presenter 层。
- 性能优化:注意避免在 Binding 表达式中进行耗时操作,如网络请求或数据库查询,这会导致 UI 卡顿。
- 空指针安全:虽然 DataBinding 提供了空指针检查,但在使用嵌套对象属性时仍需小心,建议使用
?? 操作符或确保数据源不为 null。
- 资源管理:合理使用 Resources 和 String 资源,避免硬编码字符串。
通过合理使用 DataBinding,我们可以构建出更清晰、更易维护的 MVVM 架构应用。后续章节将深入探讨 ViewModel 与 LiveData 的配合使用,进一步完善 MVVM 架构的实现。