先吐槽下,不说不爽,不说不通达
不吐不快,集合我这几天学习 DataBinding 的经历说几句。DataBinding 这东西也不是 android 的专利,android 引入这个功能是后知后觉的,一步一趋的跟着别的系统脚步发展的。纵观全局,14-16年是 android 技术大爆发的年头,各种新技术层出不穷,目不暇接,到17年中呢android 技术的进步就停下来了,android 开发呢也是进入生命周期内最辉煌的时候,技术已经非常成熟了,可以遇见的未来,android 的技术短期内没有什么大的进步,改变了。作为一个普通的 android 开发者,我们要在这个时间点上努力的吸收之前几年 android 开发技术的精髓,和各种优秀的思路,思想,手段和套路,我觉得这就是我们做 android 开发应该详细了解,举一反三的,这些也移动开发的核心精髓,换个平台,换个系统,除了基础的开发语言和构建工具,和系统知识体系的变化外,剩下的都是要重复或者再走 android 这些年这些技术发展的老路,我认为相同领域技术思路都是趋同的,区别是不同平台,不同语言的具体实现罢了。
什么是 DataBinding
什么是 DataBinding 呢,简单说来就是帮我们实现 view 和 data 绑定的工具,把数据映射到 view 的 xml中,可以在 xml 布局文件中实现 view 的赋值,方法调用。使用 DataBinding 后,我们不同再写 findViewById,不用再获取控件对象,不用再设置监听,可以节省我们 activity 中的很多获取控件,赋值,添加监听所需要的代码。
DataBinding 是个好东西啊,15年 google IO 大会就开始推了,最直接的变化就是催生了 android 中 MVVM 的出现,MVVM = MVP + DataBinding 。一线公司早就普及 MVVM 了,大伙作为一个普普通通的 andoid 开发者,一定要身上时代,什么是时代,大厂就是时代,大厂都在干什么,我们就干什么,不求跟上大厂,但求不落后太多,所以小伙伴们走起,MVVM 作为 MVP 的进阶,我们一定要学好,这期中的重中之重 DataBinding 一定不要落下,其实没多难,DataBinding 初步学习半天就可以,其中涉及到列表和多module的部分是比较复杂 的,我们多看几个开源的 app 例子就行,这里我也尽量详细的说一说。经验是干什么的,就是带着我们少走弯路,快速学习的,有的可以快,有的不能快,必须去体会,恰巧 DataBinding 就是可以快起来的部分。
DataBinding 的初步使用
写了一段时间的博客后,我是深深体会到了,一不论多复杂的事一定要按步骤拆解成一段段简单的步奏,这样才能学的快,学的明白,中间断掉了,之后也好捡起来继续学。
千里之行,始于足下,所以呢,我们先来把 DataBinding 集成进来,做个最简单的实现。
先导入 DataBinding 功能
因为 DataBinding 是 google 强推的嘛,所以 1.5版以上 gradle 就自带 DataBinding 库了,只要我们在 gradle 配置中打开 DataBinding 这个功能就可以了,不用再引入远程依赖了。哪个 module 需要就在哪个 module 的 build.gradle 编译配置文件中声明启动 dataBinding 就可以了。注意 Gradle 最低要 1.5 alpha 版。
android {
...
dataBinding {
enabled = true
}
...
}
这里我在 app 这个 module 里面声明的。DataBinding 在 gradle 中的表现形式就是 DataBinding 相关的 task 编译任务了,我们 enabled = true 之后,在编译时就会执行 DataBinding 的 task 了。
在 xml 中使用
<?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>
<import type="com.bloodcrown.bwdatabindingdemo.Book"/>
<variable name="book" type="Book"></variable>
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{book.name}"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_editor_absoluteY="20dp"
tools:text="name"/>
<TextView
android:id="@+id/tv_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@{book.price}"
app:layout_constraintLeft_toLeftOf="@id/tv_name"
app:layout_constraintTop_toBottomOf="@id/tv_name"
tools:text="price"/>
</android.support.constraint.ConstraintLayout>
</layout>
注意 dataBinding 在 xml 中的使用规则:
- 使用 DataBinding 必须要使用 <layout> 这个标签作为 xml 的根部局
- 所有 DataBinding 的内容都写在 <data >标签内
- <import > 是导入所使用的对象的类型
- <variable > 是具体声明一个数据对象,<name> 是这个对象的名字,<type> 是这个对象的类型,<variable > 可以有多个
- 使用 @{ } / @{book.price} 的方式可以把这个声明出来的数据对象的莫个数据设置给我们的 view
java 代码的使用部分
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding mainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
Book book = new Book("AAA", "35");
mainBinding.setBook(book);
}
}
- 首先我们使用 DataBinding 的方式替换我们的 setContentView,使用 DataBindingUtil 这个类的 setContentView 方法,里面传入activity 和 layout 布局
- 然后 DataBinding 的 setContentView 方法会根据类名给我们返回一个 DataBinding 的辅助类,这里就是这个 ActivityMainBinding 了。名字规则是 Activity 类型名在前 + 类中其他的名字,这个 ActivityMainBinding 辅助类我们不用编译 AS 也会自动帮我们生成,但是呢要是你在这里有问题,那就手动编译一下。
- 我们拿到辅助类之后,new 一个数据对象出来,然后把这个数据对象设置给辅助类 ActivityMainBinding,因为我们在 xml 中的<data>中声明几个数据对象,那我们在 activity onCreate 中就得给 DataBinding 的辅助类 ActivityMainBinding,设置几个数据对象进去,然后 DataBinding 才可以根据我们设置进来的数据对象,给相关 view 设值。
DataBinding 的数据更新
view 在本质的职责是反应数据的变化,那么我们就来说说在 DataBinding 中我们怎么更新数据。DataBinding 的数据本质是我饿们在 xml 中 <data> 标签中声明的数据对象,我们在java 代码中把相关的数据对象设置给 DataBinding 的辅助类,那么我们有一下几种方式:
- 整体再设置一次数据 bean 对象,这个适合更新整体数据
- 使用 BaseObservable ,操作数据 bean 的 set 方法更新字段数据,适合更新部分数据
- 使用 ObservableFields ,只更新有需求的数据字段,这个不够灵活
- 使用 DataBinding 也有的集合类型:ObservableArrayMap , ObservableArrayList
- 双向数据绑定
更新整体数据
这个本质上就是重复一次我们给 DataBinding 辅助类设置数据的过程,使用很简单
Book book = new Book("BBBB", "65");
mainBinding.setBook(book);
这样就可以了,说实话,我更倾向于这样方式,因为大多数时候,数据都是休要整体更新的
使用 BaseObservable
BaseObservable 是一个基类,需要我们的数据 bean 继承这个基类,然后给属性的 get 方法
添加@Bindable 这个注解,然后在属性的 set 方法中添加上 DataBinding 更新某个字段的方法
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
这样做呢,其实是在 DataBinding 的辅助类中把相关属性的更新和 view 的赋值方法关联在一起,完整的数据 bean 如下:
public class Book extends BaseObservable {
public String name;
public String price;
public Book() {
}
public Book(String name, String price) {
this.name = name;
this.price = price;
}
@Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
@Bindable
public String getPrice() {
return price;
}
public void setPrice(String price) {
this.price = price;
notifyPropertyChanged(BR.price);
}
}
在 activity 中操作数据 bean 的 set 方法就可以同步把数据更新到 view 中了
mBook.setName("CCC");
有个更好的解释:
Obserable接口有一个自动添加和移除监听器的机制,但是通知数据更新取决于开发者。为了使开发变得简单,谷歌创建了BaseObserable这个基础类来集成监听器注册机制。通过给getter方法添加Bindable注解,通知setter方法。
使用 ObservableFields
ObservableFields 是一个对属性添加 DataBinding 更新功能的代理类,针对不同的数据类型有不同类型的 ObservableFields :ObservableBoolean、 ObservableByte ObservableChar、ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble、 ObservableParcelable 等。
这种方式不是主流类型,使用不便,不能扩展属性,所以这里我简单的放个例子:
// 数据 bean 中声明一个属性
public ObservableField<String> age = new ObservableField<>();
// activity 中更新数据
mBook.age.set("ABABA");
ObservableArrayMap , ObservableArrayList
这2个集合类型是 DataBinding 为了方便数据刷新提供的,不用我们再手动通知集合的变化,只要我们更新了集合的某些数据,就能自动更新相关的 view 的数据
简单举个例子:
// 声明一个集合对象
private ObservableArrayMap<String, Object> mapUser = new ObservableArrayMap<>();
// 这样直接更新集合数据就可以了
mapUser.put("firstName", "zhu");
mapUser.put("lastName", "chen");
mapUser.put("age", 27);
双向数据绑定
双向数据绑定就是当 view 改变时,data 会跟着改变;data 改变时,view 会跟着改变,核心就是给 view 指定 :@={book.name},这里用 EditText 做个例子
<EditText
android:text="@={book.name}"
/>
这样当输入不同的内容时,数据就会同步更新到绑定的 book 这个数据对象中。
DataBinding 的更丰富使用
上文书书说 DataBinding 的精髓都在布局的 xml 文件中,其中有着丰富的操作,DataBinding 在xml 有越多的用法,那么就会越多的替代我们 java 中的代码,越多的减少 activity 的代码量,而且使用 DataBinding 后,页面的赋值逻辑在 xml 也会显得刚加清晰,可见。那么现在我们就来看看 DataBinding 都有那些玩法。
1. variable 标签支持的数据类型
java 能使用的 variable 标签当然都能
- 基本数据类型,我们按照 java 的写法即可,比如 String,int
- 若是引入了名字相同的类,可以给类添加别名
- 引用类型使用 import 引入全包名即可
- list,map 结合类型同引用类型 import 导下包名就行
// 引用类型
<import type="com.bloodcrown.bwdatabindingdemo.Book"/>
<variable name="book" type="Book"></variable>
android:text="@{book.name}"
------------------------------------
// 基本数据类型
// 基本数据类型不用 import 导入,直接在 type 类型里写就行qw,写 int 可能会报错,忽略就行,写相应的包装类型就得引入包了
<variable name="name" type="String"></variable>
<variable name="price" type="int"></variable>
// 在使用 int 等基本数据类型时,注意转成字符串再赋值,DataBinding 不会帮我们做类型转换的
android:text="@{String.valueOf(price)}"
------------------------------------
// 集合数据类型
// 集合的使用方式包括 [] 和 get 2种方式
<import type="java.util.List"/>
<import type="java.util.Map"/>
<variable name="bookList" type="List<Book>"></variable>
<variable name="bookMap" type="Map<String,Book>"></variable>
android:text="@{bookList.get(1).name}"
android:text="@{bookList[1].price}"
android:text='@{bookMap["111"].name}'
android:text='@{bookMap.get("111").price}'
// 注意其中特殊特好的使用,集合枚举的 <> 符号直接写xml 不认,需要用转移符号,写 map 时,key 要是 String 的,那么你可以再里面用 " " ,但是这行的 xml 外面就得用 ' ' 才行,这点注意啊,要不 xml 总是报错
------------------------------------
// 设置别名
// 我们引入的不同包的类可能重名,那么自然我们需要加一个别名来加以区分了,用alias表示
<import type="com.zx.databindingdemo.bean.UserBean" />
<import type="com.zx.databindingdemo.bean.user.UserBean" alias="UserBean2"/>
<variable name="user" type="UserBean" />
<variable name="user2" type="UserBean2"/>
2. 如何调用,注册各种方法
大家想啊,既然在 xml 中都可以直接操作属性值了,那么我调用一个方法还不是妥妥的啊。这里要区分方法的调用和监听方法的注册
对于一个 button 的点击事件来说,我们可以走下面2中方式:
- DataBinding 会根据 id 名,生成相应的 view 对象,然后我们给这个 view 设置监听
mainBinding.btnTest.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText( MainActivity.this,"AAA",Toast.LENGTH_SHORT ).show();
}
});
- 在 xml 中声明一个点击事件的对象,然后设置进 android:onClick 属性里
// 先声明点击事件对象
<import type="android.view.View.OnClickListener"/>
<variable name="testClick" type="OnClickListener"></variable>
// 再使用
<Button android:onClick="@{testClick}" />
// activity 里填充这个点击事件对象进去
mainBinding.setTestClick(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText( MainActivity.this,"BBB",Toast.LENGTH_SHORT ).show();
}
});
总体感觉和原来的注册方式没啥区别
除了各种事件的注册外,我们在利用 DataBinding 可以在 XML 中使用各种方法,静态方法,和对象中的方法都是可以直接调用的,注意是在给 view 属性赋值时可以直接调用
// Utils 里面有一个静态方法
// 导入包含静态方法的类,DataBinding 中要想使用任何类型,除了基本数据类型,都得导包
<import type="com.bloodcrown.bwdatabindingdemo.Utils"/>
<import type="com.bloodcrown.bwdatabindingdemo.MainPersenter"/>
<variable name="persenter" type="MainPersenter"></variable>
// 方法直接使用即可,直接传参也是可以的
<Button
android:text="@{Utils.getName(book.name)}"
<Button
android:text="@{ android:text="@{persenter.getPrice()}"}"
3. DataBinding 对 lambda 表达式的支持
DataBinding 支持我们直接在 xml 书写 lambda 表达式,常用的就是注册 click 点击事件了,在 view 的 onClick 属性中我们需要传入一个对象,通过上面的内容学习,我们是声明了一个 ClickListener 类型的对象数据出来,然后在 java 中传入这个对象的方式做的。但是我们在这里可以直接实现 lambda 表达式书写一个匿名实现类对象出来,这样就省了我们在 java 中传入对象的代码了。这里说一下 lambda 表达式就是对匿名实现类对象的简写,所以我们虽然看着像是调用了方法的样子,其实我们是写了一个匿名实现类对象出来,本质上还是传入了一个对象。不熟悉 lambda 表达式的看这里:Lambda表达式以及AS 对其的支持 ,还是推荐大家去学习一下 Lambda的,现在各大语言都在往这种函数式编程上靠,像 lambda 表达式这种从函数式编程上借鉴过来的东西,以后只会越来越多的,抗拒这种变化是不明智的。
大家可能会这么写 Lambda 表达式
<Button
android:onClick="@{() -> persenter.speak(book.name)}"
但是很遗憾,你这么写会报错,一个类型转换异常的错误,知道为什么吗?这个还是要从 Lambda表达式说起。Lambda 的特征是隐藏实现类的 类名 和 方法名,因为 java 8可以知道从上下文(方法中对于参数的限定)知道类的类型,并且规定了类的里面只能有一个方法,那么这里我们使用的就是这个方法:
public void onClick(View v) {
......
}
注意我们使用 Lambda 重写的就是这个 onClick 方法,onClick 方法要求传入一个 view 的参数的,上面我们没有传这个参数,所以报错了。那么我们把 Lambda 表达式修改一下:
<Button
android:onClick="@{(view) -> persenter.speak(book.name)}"
view 表示点击事件的 view 对象,这样 Lambda 就可以跑了。不过呢,使用 Lambda 写点击事件对象是属于动态类型的,因为每点击一次都会从心生成一个 点击的 匿名实现类 设置 view,所以注意这个动态的特性,合理利用,因为这样点击事件的数据也是可以使用新的了。
Class | Listener Setter | Attribute |
---|---|---|
SearchView | setOnSearchClickListener(View.OnClickListener) | android:onSearchClick |
ZoomControls | setOnZoomInClickListener(View.OnClickListener) | android:onZoomIn |
ZoomControls | setOnZoomOutClickListener(View.OnClickListener) | android:onZoomOut |
4. 其他一些监听器
除了 onClick 之外,还提供了一些特定的点击事件,这里需要注意,下面几个我没用到过,也是从别人那里摘过来的:
Class | Listener Setter | Attribute |
---|---|---|
SearchView | setOnSearchClickListener(View.OnClickListener) | android:onSearchClick |
ZoomControls | setOnZoomInClickListener(View.OnClickListener) | android:onZoomIn |
ZoomControls | setOnZoomOutClickListener(View.OnClickListener) | android:onZoomOut |
这里不知道为啥出现2个相同的表格出来,郁闷啊
表达式语言和java语法很相似。以下是相同的:
- 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}'
java中的一些特性在DataBinding语法中不支持
- this
- super
- new
- Explicit generic invocation
android:text="@{user.displayName ?? user.lastName}"
就等价于
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
可以在属性值使用单引号,在表达式中字符串的值使用双引号:
android:text='@{map["firstName"]}'
也可以在属性值使用双引号,表达式中的值字符串的值应该使用单引号或者是"`"。
android:text="@{map[`firstName`}"
android:text="@{map['firstName']}"
默认情况下,binding 类的名称取决于布局文件的命名,以大写字母开头,移除下划线,后续字母大写并追加 “Binding” 结尾。这个类会被放置在 databinding 包中。举个例子,布局文件 contact_item.xml 会生成 ContactItemBinding 类。如果 module 包名为 com.example.my.app ,binding 类会被放在 com.example.my.app.databinding 中。
通过修改 data 标签中的 class 属性,可以修改 Binding 类的命名与位置。
<data class="CustomBinding">
...
</data>
以上会在 databinding 包中生成名为 CustomBinding 的 binding 类。如果需要放置在不同的包下,可以在前面加 “.”
<data class=".CustomBinding">
...
</data>
这样的话, CustomBinding 会直接生成在 module 包下。如果提供完整的包名,binding 类可以放置在任何包名中
<data class="com.example.CustomBinding">
...
</data>
资源 id 方面我们同传统方式去写就行,但是还是稍有些差别的。不如看这个例子:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
运行起来,可能有的版本会报错误,因为这是 DataBinding 的 bug,有人说修了,有人说还有,碰上的朋友这样改
android:padding="@{large? (int)@dimen/largePadding : (int)@dimen/smallPadding}"
因为 DataBinding 生成数据的数据格式可能和我们实际需要的不同,注意这点。
其他的例子我们举一个,替换 String.xml 中的占位符:
// 先声明字符串资源
<string name="nameFormat">Full Name: %1$s:%2$s</string>
// DataBinding 在 xml 中可以直接引用使用
android:text="@{@string/nameFormat(firstName, lastName)}"
DataBinding 对于资源的文件头命名可能和 android 传统的有些地方不一样,下面的表是官方文档上的,找资料的话都是这张表,没有其他的资料,大家实际要到问题可以参考这样表,但是优先还是按照 android 原先的资源引用方式来:

DataBinding 使用技巧
xml 布局中不可避免的要使用 include 标签,那么 DataBinding 怎么兼容这个 include 标签呢。其实只要 外层的 xml 把 include 声明的 DataBinding 数据对象传给 include 就行,但是要注意 DataBinding 不支持 include 的 merge 标签
include 的布局 name.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.liangfeizc.databinding.model.User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}" />
</LinearLayout>
</layout>
总体的布局文件
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="com.liangfeizc.databinding.model.User" />
<variable
name="user"
type="User" />
<variable
name="listener"
type="com.liangfeizc.databinding.listener.OkListener" />
<variable
name="okText"
type="String" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="@+id/layout_input"
layout="@layout/layout_input" />
<include
layout="@layout/user"
app:user="@{user}" />
<include
layout="@layout/layout_btn_ok"
app:okText="@{okText}"
app:listener="@{listener}"/>
</LinearLayout>
</layout>
另一个 include 的 xml ,layout_input.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:id="@+id/et_name"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</layout>
注意啊,如何调 include 布局的某一个组件呢
binding.layoutInput.etName.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
User user = new User(s.toString(), "Liang");
binding.setUser(user);
}
@Override
public void afterTextChanged(Editable s) {
}
});
上面我们演示的 activity,是通过 DataBinding 通过 setContentView 方法实现 DataBinding 和 activity xml 布局实现绑定的,那么问题来了 fragment 怎么办
activity 我们这么写
private ActivityDemoBinding mBinding;
protected void onCreate(Bundle savedInstanceState) {
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_demo);
}
Fragment 我们这么写:
private FragmentHomeBinding mBinding;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mBinding = DataBindingUtil.inflate(inflater, R.layout.homepage_fragment, container, false);
return mBinding.getRoot();
}
// 我们给 ViewStub 设置初始化函数
mainBinding.bookStub.setOnInflateListener(new ViewStub.OnInflateListener() {
@Override
public void onInflate(ViewStub stub, View inflated) {
bookStubBinding = DataBindingUtil.bind(inflated);
bookStubBinding.setBook(mBook);
}
});
// 模拟一个点击事件加载 ViewStub
mainBinding.setTestClick(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!mainBinding.bookStub.isInflated()) {
mainBinding.bookStub.getViewStub().inflate();
} else {
mBook.setName("stub");
mBook.setPrice("888");
}
}
});
DataBinding 可以使用的注解:
- @BindingMethod :修改view 某个方法名字
- @BindingAdapter : 给 view 添加set/get 方法,相当于添加自定义属性
- @BindingConversion : 提供数据类型转换方法
@BindingMethod 用的不多,这里就不说了,我们来看下 @BindingAdapter 这个注解,可以给 view 添加自定义属性,相当的好用啊,我们不用再定义复杂的 view 自定义属性了,@BindingMethod 书写简单,功能强大,xml 中的自顶提示很友好。
先定义给 view 设置这个自定义属性的具体执行方法
public class TextBingUtils {
@BindingAdapter("info")
public static void setInfo(TextView view, String info) {
view.setText(info);
}
}
注意 @BindingAdapter 注解里面的参数就是这个自定义属性的名字,在 xml 里 app:xx 就可以使用了。另外这个方法写在哪里都没事,但是方法必须是静态的,DataBinding 框架在编译时会自动检索 @BindingAdapter 这个注解的所有方法。
然后在就可以在 xml 中使用了
<TextView
app:info='@{"info"}'
/>
@BindingConversion 用的不多,但是也得说一下,这个注解会给 DataBinding 提供默认的2个数据类型之间的转换方法,可以放置一些类型转换错误,写法上和 @BindingAdapter 相同。但是使用这转换器属性时我们必须要小心,因为DataBinding是不懂得区分是否真的需要使用整个转换器的。比方说我们创建两个相同类型的转换方法,DataBinding 就只会使用第一个
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
<View
android:background="@{isError.get() ? @color/red : @color/white}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_height="@{height}" />
DataBinding 对 RecyclerView 的优化
为啥要单开一章说呢,因为的确是太重要了,DataBinding 对 RecyclerView 支持真的是一大亮点啊,直接的就是使用 DataBinding ,我们就不用再写 Viewholder 了。Viewholder 的工作就是 findviewbyid 持有相应的 view 的引用好让我们来设置数据,DataBinding 会自动根据 view 的 id 生成相关的对象,至少从这点出来,DataBinding 对我们都是非常有意义的了。好了,来看看在 RecyclerView 中使用 DataBinding 的基础方式。
新的 ViewHolder 书写方式,每个 ViewHolder 只需要持有相应 view 对应的 DataBinding 辅助类对象,通过他恩那个找到所有的子 view 引用。
public class BookBindingViewHolder extends RecyclerView.ViewHolder {
private ItemListBinding t;
public BookBindingViewHolder(ItemListBinding t) {
super(t.getRoot());
this.t = t;
}
public ItemListBinding getBinding() {
return t;
}
public void setT(ItemListBinding t) {
this.t = t;
}
}
item 的布局文件,要相生成 DataBinding 辅助类,必须在 xml 中显示使用 DataBinding
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
>
<data>
<import type="com.bloodcrown.bwdatabindingdemo.Book"/>
<variable name="book" type="Book"></variable>
</data>
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="10dp"
android:text="@{book.name}"
android:textColor="@color/colorPrimary"
android:textSize="22sp"
/>
</layout>
新的 adapter 通过 DataBinding 类刷新数据
public class BookBindingAdapter extends RecyclerView.Adapter<BookBindingViewHolder> {
private List<Book> data;
public BookBindingAdapter(List<Book> data) {
this.data = data;
}
@Override
public BookBindingViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new BookBindingViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_list, parent, false));
}
@Override
public void onBindViewHolder(BookBindingViewHolder holder, int position) {
Log.d("AAA", "position:" + position);
holder.getBinding().setBook(data.get(position));
holder.getBinding().setVariable(BR.book, data.get(position));
holder.getBinding().executePendingBindings();
}
@Override
public int getItemCount() {
return data == null ? 0 : data.size();
}
public void setData(List<Book> data) {
this.data = data;
}
}
holder.getBinding().executePendingBindings() 这句话是刷新界面,否则可能会出现这个问题: RecyclerView使用databinding出现数据闪烁问题
另外注意列表在更新数据时也可以使用 holder.getBinding().setVariable(BR.book, data.get(position)); 这个方法是更新xml 里面定义的数据对象的,BR.book 是个 int 值,指向xml 中声明的数据对象的id地址
当然上面这是最简单的 DataBinding 列表实现,甚至多 itemType 都不支持,下面优化下多 itemType 的问题
// 写一个返回 itemType 的接口,然后数据 bean 实现这个接口
public interface IBaseBindingAdapterItemProvide {
int getItemType();
}
// 然后处理一下 adapter ,能够兼容多类型的 item
public class CatBindingAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<IBaseBindingAdapterItemProvide> data;
public CatBindingAdapter(List<IBaseBindingAdapterItemProvide> data) {
this.data = data;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == R.layout.item_list) {
return new BookBindingViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_list, parent, false));
}
if (viewType == R.layout.item_cat) {
return new CatBindingViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_cat, parent, false));
}
return new BookBindingViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_list, parent, false));
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (holder instanceof BookBindingViewHolder) {
Book book = (Book) data.get(position);
((BookBindingViewHolder) holder).getBinding().setBook(book);
((BookBindingViewHolder) holder).getBinding().executePendingBindings();
return;
}
if (holder instanceof CatBindingViewHolder) {
Cat cat = (Cat) data.get(position);
((CatBindingViewHolder) holder).getBinding().setCat(cat);
((CatBindingViewHolder) holder).getBinding().executePendingBindings();
}
}
@Override
public int getItemCount() {
return data == null ? 0 : data.size();
}
@Override
public int getItemViewType(int position) {
return data.get(position).getItemType();
}
public void setData(List<IBaseBindingAdapterItemProvide> data) {
this.data = data;
}
}
恩,现在可以支持多类型的列表了,但是要是我们在实际开发中对这段原始代码不经任何雕琢(封装,设计),那么说明我们做开发真的是没长进。能够初步封装根据效果可以认为是初中级水平,能够写封装出一个易于扩展的优秀的库出来,可以视为高级水平了。期望大家多多用心,在封装自己的库时,是水平提升最快的时候了,知识点的学习是偏记忆,理解,考研智商和记忆能力。那么封装原始代码为库就是考研我们的基础代码水平了,设计到的都是硬知识点,也是最难以提升的部分,需要大毅力才行,但也是最重要的代码技能了。
本文先于篇幅,对于用 DataBinding 来优化 RecyclerView 就写到这里了,更多更优秀的内容我会开单章的,放到通过 MVP 学习代码封装的那部分里。
最后
-
不要拒绝 findViewById
DataBinding 和 findViewById() 并不是互斥的,DataBinding的源码里面也是用到了findViewById()。如果某些情况真的不适合使用DataBinding,那就用回findViewById吧。 -
xml中的表达式尽量简单
xml 文件中不要出现过于复杂业务逻辑,只出现简单的 UI 相关的表达式。不要以为Data Binding是万能的,而想尽办法把逻辑写在xml中。往往这个时候你就应该将逻辑写在绑定的ViewModel里面。 -
注意clean
有时候因为修改接口等原因要修改绑定的bean类,这时候偶尔会遇到一些神奇的bug。不要慌张,直接clean一下项目再make project一次往往就好了 -
使用BindingConversion注解时需要慎重
原因上面已经说了。但是它也不失为一个很好用的注解,比方说使用它来进行字体的自定义。具体可以参照下面的文章:使用DataBinding来进行字体的自定义
参考资料
- DataBinding系列(一):DataBinding初认识
- DataBinding系列(二):DataBinding的基本用法
- DataBinding系列(三):RecyclerView中使用DataBinding
- DataBinding系列(四):DataBinding进阶之路
- DataBinding的使用心得
- DataBinding(一)-初识
-
DataBinding(二)-实战
这里面说了一些 DataBinding 常用的方法,用到了看一下 - 通过Data Binding为RecyclerView打造通用Adapter4
这里有一个 DataBinding 的使用规范的文章,不是赞同所有内容,但还是推荐大家看看
网友评论
我编译不出来是为什么呢??无法生成这个类