本文深入解析了DataBinding中的Observable模式和BindingAdapter两个知识点,适合有一定的DataBinding基础,想进一步理解掌握它的原理的Android开发者。强烈建议先阅读本人另一篇文章DataBinding实现原理探析,对理解本文帮助很大。
// TODO 测试RecyclerView中使用databinding后,还需要notifyItemChanged吗?
一、从最简单的Demo开始
我知道,一开始就丢出一堆代码给别人看,是一件很无趣的事儿,但这是必不可少的,因为我需要你先了解我所用的Demo,才能基于这个Demo进行探讨。所以,熟悉下这个再简单不过的Demo吧。
- AS中新建一个项目,开启DataBinding:
app ——> build.gradle:
android {
...
dataBinding {
enabled = true
}
...
}
- 项目中用到的数据类User:
public class User {
private String name ;
public User(String name) {
this.name = name ;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- MainActivity的布局如下:
<layout xmlns:android="http://schemas.android.com/apk/res/android" >
<data>
<variable
name="user"
type="com.milter.www.databinding_observable.User"/>
</data>
<RelativeLayout
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:gravity="center_vertical|center_horizontal"
tools:context="com.milter.www.databinding_observable.MainActivity">
<TextView android:id="@+id/username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="35sp"
android:text="@{user.name}" />
<Button android:id="@+id/changeUserName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/username"
android:layout_marginTop="50dp"
android:text="@string/changeusername"
/>
</RelativeLayout>
</layout>
MainActivity的代码如下:
package com.milter.www.databinding_observable;
import android.databinding.DataBindingUtil;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import com.milter.www.databinding_observable.databinding.ActivityMainBinding;
public class MainActivity extends AppCompatActivity {
private User user ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding mainActivityBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);
user = new User("milter");
mainActivityBinding.setUser(user);
mainActivityBinding.changeUserName.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
user.setName("Not milter");
}
});
}
}
在上面的代码中,我们用参数“milter”创建了一个user对象,然后把它设置给了mainActivityBinding,之后,我们给id为changeUserName的Button添加了一个点击监听器,点击它将会调用user 的setName方法将它的name属性改为“Not milter”。
程序运行起来的界面如下所示:
demo.png够简单吧!
二、点击按钮后,milter会变成 Not miter吗?
请你猜猜上面的问题,无论对错,希望你能够告诉我结果。
正确答案是:NO!
我们肯定要问:WHY?
先来看看milter是怎么显示在id为username的TextView中的。代码mainActivityBinding.setUser(user);
执行后,DataBinding框架会进行数据与UI的绑定,id为username的TextView通过android:text="@{user.name}"
绑定了user对象的name属性,所以显示了milter。
点击按钮后,user对象的name属性变成了“Not milter”,但是DataBinding框架并不知道这件事儿,所以它不会重新进行数据与UI的绑定,结果就是username中显示的还是milter。
解决问题的关键在于,怎么让user对象告诉DataBinding框架,我的name属性改变了,赶紧更新UI吧!
三、实现点击按钮后milter变 Not milter!
为了达到目标,有三件事要做,我把它称为绑定数据“三步曲”:
- 让User类成为一个可被观察的Observable
DataBinding框架提供了一个android.databinding.Observable接口,只要让User类实现这个接口,DataBinding框架就会向我们的user对象注册一个监听器,有了这个监听器,当user对象的属性发生变化后,它就可以通知DataBinding框架,DataBinding框架收到user对象的通知后,就会更新UI数据。
但是,实现Observable接口,相当于让我们自己实现一个观察者模式,还是有点麻烦的,所以DataBinding框架为我们提供了一个BaseObservable类,该类已经实现了Observable接口,所以,我们只要让User类继承它也就自然实现了Observable接口了。
实现了Observable接口的User类如下所示:
import android.databinding.BaseObservable; //这里不要搞错哦!
public class User extends BaseObservable {
private String name ;
public User(String name) {
this.name = name ;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- 确定User类中会发生变化的属性
虽然现在我们的User类只有一个name属性,但现实中,User类还可以有许多属性,比如sex,age,phoneNumber等。我们希望user对象通知DataBinding框架的时候,能够精确一点,比如告诉DataBinding框架,我的name属性变化了,请更新UI中使用我的name属性的View。这样可以大大减轻DataBinding框架的工作量。
假如user对象仅仅告诉DataBinding框架,我的属性发生了变化,请更新UI吧。那么DataBinding框架将不得不把UI中所有使用user对象属性的View更新一遍,显然,更新那些没有变化的属性纯粹是一种浪费。
确定会发生变化的属性非常简单,就是在相应的getter方法上加上@Bindable注解。在我们的Demo中,要给getName方法上面加上@Bindable注解,如下所示:
@Bindable
public String getName() {
return name;
}
加上这个注解后,DataBinding框架会在BR这个生成类中,为name属性生成一个唯一的标识符,如下所示:
public class BR {
...
public static final int name = 1;
...
}
这样,当user对象通知DataBinding框架时,可以用BR.name标识自己的name属性。
你可能会问,为什么不把@Bindable注解加到name field上,像下面这样:
@Bindable
private String name ;
答案在文首推荐的《DataBinding实现原理探析》中,这里不重复解释了。
- 当属性变化时通知DataBinding框架
现在,当user对象的name属性发生变化后,我们就可以通知DataBinding框架了,显然,应该在setName方法中通知DataBinding框架。如下所示:
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
notifyPropertyChanged方法来自BaseObservable类。
完成了我们的“三步曲”,本节的目的就达到了。
点击按钮前后对比如下:
四、更加简单的方法
上一部分中,仅仅为了让user在自己的name属性发生变化时通知DataBinding框架,我们要做许多工作,又是继承,又是加注解的,确实有点麻烦!
为此,DataBinding框架给我们提供一个简便方法,那就是使用ObservableField。使用它,我们的User类将变成这样:
public class User{
public final ObservableField<String> name = new ObservableField<>();
public User(String name) {
this.name.set(name);
}
}
然后,将按钮的点击监听器中的代码变成这样:
user.name.set("Not milter");
这就可以了,效果和之前是一样一样的。
更进一步,如果属性是基本数据类型,DataBinding框架还提供了专门的属性类:
ObservableBoolean,
ObservableByte,
ObservableChar,
ObservableShort,
ObservableInt,
ObservableLong,
ObservableFloat,
ObservableDouble,
ObservableParcelable
再进一步,如果属性是集合,DataBinding框架也提供了专门的类:
ObservableArrayMap
ObservableArrayList
使用ObservableField 等ObservableXXX类,可以让我们省去继承BaseObservable、添加注解,通知DataBinding框架等麻烦,但天下没有免费的午餐,代价就是性能会降低,所以只能在少量属性上这样用,如果大量使用,用户体验可能不太好。
好了,关于Observable的部分就到这里了,突然发现文章已经不短了。只好下一节再讲BindingAdapter。
网友评论
https://academy.realm.io/posts/data-binding-android-boyar-mount/