一.基础入门
DataBinding 是谷歌官方发布的一个框架,顾名思义即为数据绑定,是 MVVM 模式在 Android 上的一种实现,用于降低布局和逻辑的耦合性,使代码逻辑更加清晰。MVVM 相对于 MVP,其实就是将 Presenter 层替换成了 ViewModel 层。DataBinding 能够省去我们一直以来的 findViewById() 步骤,大量减少 Activity 内的代码,数据能够单向或双向绑定到 layout 文件中,有助于防止内存泄漏,而且能自动进行空检测以避免空指针异常
启用 DataBinding 的方法是在对应 Model 的 build.gradle 文件里加入以下代码,同步后就能引入对 DataBinding 的支持
android { dataBinding { enabled = true } }
1.使用DataBinding页面布局变化
<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.liquid.jetpackapp.data.Goods" />
<variable
name="goods"
type="Goods" />
</data>
<!--view根部局-->
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
...
</android.support.constraint.ConstraintLayout>
</layout>
和原始布局的区别在于多出了一个 layout 标签将原布局包裹了起来,data 标签用于声明要用到的变量以及变量类型,要实现 MVVM 的 ViewModel 就需要把数据(Model)与 UI(View)进行绑定,data 标签的作用就像一个桥梁搭建了 View 和 Model 之间的通道
2.创建model
public class Goods{
@Bindable
public String name;
private String details;
...
}
3.在 data 标签里声明要使用到的变量名、类的全路径
<data>
<import type="com.liquid.jetpackapp.data.Goods" />
<variable
name="goods"
type="Goods" />
</data>
4.这里声明了一个Goods类型的变量 goods,我们要做的就是使这个变量与两个 TextView 控件挂钩,通过设置goods的变量值同时使 TextView 显示相应的文本,完整的布局代码如下所示
<TextView
android:id="@+id/tv_name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:text="@{goods.name}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_details"
app:layout_constraintLeft_toRightOf="@+id/tv_name"
app:layout_constraintTop_toTopOf="parent"
android:text="@{goods.details}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
通过@{goods.name}使 TextView 引用到相关的变量,DataBinding 会将之映射到相应的 getter 方法
5.之后可以在 Activity 中通过 DataBindingUtil 设置布局文件,省略原先 Activity 的 setContentView() 方法,并为变量 goods 赋值
private Goods goods;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
goods = new Goods("goods","food",34);
activityMainBinding.setUserInfo(goods);
}
6.由于 @{goods.name}在布局文件中并没有明确的值,所以在预览视图中什么都不会显示,不便于观察文本的大小和字体颜色等属性,此时可以为之设定默认值(文本内容或者是字体大小等属性都适用),默认值将只在预览视图中显示,且默认值不能包含引号
android:text="@{goods.name,default=food}"
此外,也可以通过 ActivityMainBinding 直接获取到指定 ID 的控件
activityMainBinding.tvName.setText("food");
7.Databinding 同样是支持在 Fragment 和 RecyclerView 中使用 。例如,可以看 Databinding 在 Fragment 中的使用
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
FragmentBlankBinding fragmentBlankBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_blank,container, false);
return fragmentBlankBinding.getRoot();
}
二.单向绑定
实现数据变化自动驱动 UI 刷新的方式有三种:BaseObservable、ObservableField、ObservableCollection
1.BaseObservable
一个纯净的 ViewModel 类被更新后,并不会让 UI 自动更新。而数据绑定后,我们自然会希望数据变更后 UI 会即时刷新,Observable 就是为此而生的概念
BaseObservable 提供了 notifyChange() 和 notifyPropertyChanged() 两个方法,前者会刷新所有的值域,后者则只更新对应 BR 的 flag,该 BR 的生成通过注
释 @Bindable 生成,可以通过 BR notify 特定属性关联的视图
public class Goods extends BaseObservable {
//如果是 public 修饰符,则可以直接在成员变量上方加上 @Bindable 注解
@Bindable
public String name;
//如果是 private 修饰符,则在成员变量的 get 方法上添加 @Bindable 注解
private String details;
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
@Bindable
public String getDetails() {
return details;
}
public void setDetails(String details) {
this.details = details;
notifyChange();
}
}
在 setName() 方法中更新的只是本字段,而 setDetails() 方法中更新的是所有字段,当更新值的时候,相应的UI显示就会随之更新
2.实现了 Observable 接口的类允许注册一个监听器,当可观察对象的属性更改时就会通知这个监听器,此时就需要用到 OnPropertyChangedCallback 当中 propertyId 就用于标识特定的字段
goods.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
switch (propertyId) {
case BR.details:
Log.e("MainActivity","details");
break;
case BR.name:
Log.e(""MainActivity","name");
break;
case BR.price:
Log.e(""MainActivity","price");
break;
}
}
});
用来观察goods中属性的值的变化
3.ObservableField
继承于 Observable 类相对来说限制有点高,且也需要进行 notify 操作,因此为了简单起见可以选择使用 ObservableField。ObservableField 可以理解为官方对 BaseObservable 中字段的注解和刷新等操作的封装,官方原生提供了对基本数据类型的封装,例如 ObservableBoolean、ObservableByte、ObservableChar、
ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble 以及 ObservableParcelable ,也可通过 ObservableField 泛型来申明其他类型
public class Goods {
private ObservableField<String> name;
private ObservableFloat price;
private ObservableField<String> details;
public ObservableGoods(String name, float price, String details) {
this.name = new ObservableField<>(name);
this.price = new ObservableFloat(price);
this.details = new ObservableField<>(details);
}
```
}
对 ObservableGoods 属性值的改变都会立即触发 UI 刷新,概念上与继承BaseObservable区别不大
4.ObservableCollection
dataBinding 也提供了包装类用于替代原生的 List 和 Map,分别是 ObservableList 和 ObservableMap,当其包含的数据发生变化时,绑定的视图也会随之进行刷新
<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="androidx.databinding.ObservableList" />
<import type="androidx.databinding.ObservableMap" />
<variable
name="list"
type="ObservableList<String>" />
<variable
name="map"
type="ObservableMap<String,String>" />
<variable
name="index"
type="int" />
<variable
name="key"
type="String" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/tv_list"
app:layout_constraintTop_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:text="@{list[index],default=xx}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_map"
app:layout_constraintTop_toBottomOf="@+id/tv_list"
app:layout_constraintStart_toStartOf="parent"
android:text="@{map[key],default=gg}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
public class MainActivity extends AppCompatActivity {
private ObservableList<String> list;
private ObservableMap map;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);
list = new ObservableArrayList();
list.add("Ye");
list.add("leavesC");
activityMainBinding.setList(list);
activityMainBinding.setIndex(1);
map = new ObservableArrayMap();
map.put("name","lvdouwan");
map.put("age","24");
activityMainBinding.setMap(map);
activityMainBinding.setKey("name");
}
}
三、双向数据绑定 双向绑定的意思即为当数据改变时同时使视图刷新,而视图改变时也可以同时改变数据
比如当 EditText 的输入内容改变时,会同时同步到变量 goods,绑定变量的方式比单向绑定多了一个等号:android:text="@={goods.name}"
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.liquid.jetpackapp.data.Goods"/>
<variable
name="goods"
type="ObservableGoods" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
···
android:text="@{goods.name}" />
<EditText
···
android:text="@={goods.name}" />
</LinearLayout>
</layout>
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
Goods goods = new Goods("code", "hi", 23);
activityMainBinding.setGoods(goods);
}
}
四、事件绑定 严格意义上来说,事件绑定也是一种变量绑定,只不过设置的变量是回调接口而已 事件绑定可用于以下多种回调事件
android:onClick
android:onLongClick
android:afterTextChanged
android:onTextChanged
在 Activity 内部新建一个 GoodsHandler类来声明 onClick() 和 afterTextChanged() 事件相应的回调方法
public class GoodsHandler{
public void changeGoodsName() {
goods.setName("code " + new Random().nextInt(100));
goods.setPrice(new Random().nextInt(100));
}
public void changeGoodsDetails() {
goods.setDetails("hi " + new Random().nextInt(100));
goods.setPrice(new Random().nextInt(100));
}
public void afterTextChanged(Editable s) {
}
public void afterUserPassword(Editable s){
}
}
<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="goodsHandler"
type="com.liquid.jetpackapp.MainActivity.GoodsHandler" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/btn_one"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:text="改变属性 name 和 price"
android:onClick="@{()->goodsHandler.changeGoodsName()}"
android:textAllCaps="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn_two"
app:layout_constraintTop_toBottomOf="@+id/btn_one"
app:layout_constraintLeft_toLeftOf="parent"
android:text="改变属性 details 和 price"
android:onClick="@{()->goodsHandler.changeGoodsDetails()}"
android:textAllCaps="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main);
activityMainBinding.setGoodsHandler(new GoodsHandler());
}
public class GoodsHandler{
public void changeGoodsName() {
goods.setName("code " + new Random().nextInt(100));
goods.setPrice(new Random().nextInt(100));
}
public void changeGoodsDetails() {
goods.setDetails("hi " + new Random().nextInt(100));
goods.setPrice(new Random().nextInt(100));
}
public void afterTextChanged(Editable s) {
}
public void afterUserPassword(Editable s){
}
}
}