在对应 Model 的 build.gradle 文件里加入以下代码,同步后就能引入对 DataBinding 的支持
android {
dataBinding {
enabled = true
}
}
public class User {
private String Username;
private String Password;
public User(String name, String psw) {
this.Username = name;
this.Password = psw;
}
public String getUsername() {
return Username;
}
public void setUsername(String username) {
Username = username;
}
public String getPassword() {
return Password;
}
public void setPassword(String password) {
Password = password;
}
}
<data>
<variable
name="userInfo"
type="com.sky.databindingapplication.User" />
</data>
<data>
<import type="com.sky.databindingapplication.User"/>
<variable
name="userInfo"
type="User"/>
</data>
<data>
<import type="com.sky.databindingapplication.User" />
<import
alias="TempUser"
type="com.sky.databindingapplication.User" />
<variable
name="userInfo"
type="User" />
<variable
name="tempUserInfo"
type="TempUser" />
</data>
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="com.sky.databindingapplication.User" />
<variable
name="userInfo"
type="com.sky.databindingapplication.User" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<TextView
android:id="@+id/tv_username"
...
android:text="@{userInfo.username, default=username}"
/>
<TextView
android:id="@+id/tv_psw"
...
android:text="@{userInfo.password, default=password}"
/>
</LinearLayout>
</layout>
@{userInfo.name}
使 TextView 引用到相关的变量,DataBinding 会将之映射到相应的 getter 方法。@{userInfo.name}
在布局文件中并没有明确的值,所以在预览视图中什么都不会显示,不便于观察文本的大小和字体颜色等属性,此时可以为之设定默认值(文本内容或者是字体大小等属性都适用),默认值将只在预览视图中显示,且默认值不能包含引号。userInfo
赋值public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
User user = new User("sky","123");
binding.setUserInfo(user);
binding.tvUsername.setText("pdd");
binding.tvUsername.setText("pdd");
注意:如果先在xml中设置了text的内容:android:text="@{userInfo.password, default=defaultValue}"
那么就不能通过获取TextView实例的方法动态修改text内容了,如下
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.tvName.setText("pdd");
而应该通过设置userInfo的变量值来改变text内容
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
User user = new User("sky","123");
binding.setUserInfo(user);
以上实现数据绑定的方式,每当绑定的变量发生变化的时候,都需要重新向 ViewDataBinding 传递新的变量值才能刷新 UI 。接下来看如何实现自动刷新 UI
实现数据变化自动驱动 UI 刷新的方式有三种:BaseObservable、ObservableField、ObservableCollection
BaseObservable 提供了 notifyChange()
和 notifyPropertyChanged()
两个方法,前者会刷新所有的值域,后者则只更新对应 BR 的 flag,该 BR 的生成通过注释 @Bindable
生成,可以通过 BR notify 特定属性关联的视图
package com.sky.databindingapplication;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
public class Goods extends BaseObservable {
//如果是 public 修饰符,则可以直接在成员变量上方加上 @Bindable 注解
@Bindable
public String name;
//如果是 private 修饰符,则在成员变量的 get 方法上添加 @Bindable 注解
private String details;
private float price;
public Goods(String name, String details, float price) {
this.name = name;
this.details = details;
this.price = price;
}
public String getName() {
return name;
}
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(); //更新所有字段
}
@Bindable
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
}
public class MainActivity extends AppCompatActivity {
private Goods goods;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
goods = new Goods("book", "story book", 12);
binding.setGoods(goods);
binding.setGoodsHandler(new GoodsHandler());
}
public class GoodsHandler {
public void changeName() {
goods.setName("name" + new Random().nextInt(100));
goods.setPrice(new Random().nextInt(100));
}
public void changeDetails() {
goods.setDetails("details" + new Random().nextInt(100));
goods.setPrice(new Random().nextInt(100));
}
}
}
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{goods.name, default=goodsname}"
android:textSize="20dp"/>
<TextView
android:id="@+id/tv_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{goods.details, default=goodsdetails}"
android:textSize="20dp"/>
<TextView
android:id="@+id/tv_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(goods.price), default=goodsprice}"
android:textSize="20dp"/>
<Button
android:id="@+id/btn_name_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="改变name和price"
android:onClick="@{() -> goodsHandler.changeName()}"/>
<Button
android:id="@+id/btn_details_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="改变details和price"
android:onClick="@{() -> goodsHandler.changeDetails()}"/>
注意:price的数据类型是float,而这里setText()只能传String类型的值,需要用String.valueOf(goods.price)
,将float类型的数转成String类型。否则将会报错
报错详情 -> android.content.res.Resources$NotFoundException: String resource ID #0x0
可以看到,name 视图的刷新没有同时刷新 price 视图,而 details 视图刷新的同时也刷新了 price 视图
goods.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(Observable sender, int propertyId) {
if (propertyId == com.leavesc.databinding_demo.BR.name) {
Log.e(TAG, "BR.name");
} else if (propertyId == com.leavesc.databinding_demo.BR.details) {
Log.e(TAG, "BR.details");
} else if (propertyId == com.leavesc.databinding_demo.BR._all) {
Log.e(TAG, "BR._all");
} else {
Log.e(TAG, "未知");
}
}
});
我们刚刚介绍的通知UI更新的方法是用Goods类继承自BaseObservable,然后在getter上添加注解、在setter中添加notify方法,这感觉总是有点麻烦,步骤繁琐,于是,Google推出ObservableFields类,使用它我们可以简化我们的Model类,如:
public class ObservableGoods {
public final ObservableField<String> name = new ObservableField<>();
public final ObservableField<String> details = new ObservableField<>();
public final ObservableFloat price = new ObservableFloat();
}
<TextView
android:id="@+id/tv_ob_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{observableGoods.name, default=obgoodsname}"
android:textSize="20dp"/>
<TextView
android:id="@+id/tv_ob_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{observableGoods.details, default=obgoodsdetails}"
android:textSize="20dp"/>
<TextView
android:id="@+id/tv_ob_price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{String.valueOf(observableGoods.price), default=obgoodsprice}"
android:textSize="20dp"/>
<Button
android:id="@+id/btn_ob"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="改变ob"/>
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
observableGoods = new ObservableGoods();
observableGoods.name.set("phone");
observableGoods.details.set("iphoneX");
observableGoods.price.set(5180);
binding.setObservableGoods(observableGoods);
binding.btnOb.setOnClickListener(this);
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_ob:
observableGoods.name.set("toothbrush");
observableGoods.details.set("philips");
observableGoods.price.set(299);
}
}
当然ObservableField中传入的泛型可以是Java中的基本类型,官方原生提供了对基本数据类型的封装,例如 ObservableBoolean、ObservableByte、ObservableChar、ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble 以及 ObservableParcelable ,也可通过 ObservableField 泛型来申明。其他类型效果也和ObservableField是一样的,如:
public class User{
public final ObservableField<String> userName = new ObservableField<>();
public final ObservableField<Integer> userPassword = new ObservableField<>();
public final ObservableInt userAge = new ObservableInt();
}
dataBinding 也提供了包装类用于替代原生的 List 和 Map,分别是 ObservableList 和 ObservableMap,当其包含的数据发生变化时,绑定的视图也会随之进行刷新
双向绑定的意思即为当数据改变时同时使视图刷新,而视图改变时也可以同时改变数据
很简单,和单项数据绑定基本一样,只是多了一个等号android:text="@={observableGoods.name}"
看以下例子,当 EditText 的输入内容改变时,会同时同步到变量ObservableGoods
<TextView
android:id="@+id/tv_ob_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{observableGoods.name, default=obgoodsname}"
android:textSize="20dp"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={observableGoods.name}"/>
其他用法
android:text="@{`姓名:` + user.name}"
android:text="@{`年龄:` + String.valueOf(user.age)}"
android:text="@{user.gender == 0 ? `性别:男` : `性别:女`}"
<Button
android:id="@+id/btn_cancel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="长按删除信息"/>
使用btnCancel设置长按事件:
binding.btnCancel.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
Toast.makeText(getApplicationContext(),"长按了",Toast.LENGTH_SHORT).show();
return true;
}
});
<variable
name="listener"
type="android.view.View.OnClickListener"/>
在Button中给android:onClick设置listener的变量。
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="提交信息"
android:onClick="@{listener}"/>
通过setListener传入点击监听给listener对象。
binding.setListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(),"点击了",Toast.LENGTH_SHORT).show();
}
});
要注意的坑:&& 和 <
有些情况下我们需要双重判断也就是&&正常情况下我们这样写觉得没有问题,但是&&始终会报错,强制编译运行就出问题像下面这种错误
那是因为&&,< 在databinding中做逻辑判断使用时 &&需要转译为:&& < 需要转译为:< 修改后:
android:text="@{coin>=30&&coin < 60?`未完成`:``}"
参考文章