通过前面的学习,我们已经会使用DataBinding的一些基础语法了,而这一篇,我就和大家一起开启DataBinding的进阶之旅,下面就让我们通过几个小例子一起起飞吧。
1.BindingAdapter注解设置自定义属性
BindingAdapter对于自定义属性是非常有用的,举个例子,如果你的项目中用到了Glide,或者是其他的图片加载框架,由于这些框架一般通过url给ImageView设置图片的,但是ImageView中并没有设置url的属性,这个时候,BindingAdapter就派上用场了。一起来看看是如何实现的:
public class ImageHelper { /** * 1.加载图片,无需手动调用此方法 * 2.使用@BindingAdapter注解设置自定义属性的名称,imageUrl就是属性的名称, * 当ImageView中使用imageUrl属性时,会自动调用loadImage方法, * * @param imageView ImageView * @param url 图片地址 */ @BindingAdapter({"imageUrl"}) public static void loadImage(ImageView imageView, String url) { Glide.with(imageView.getContext()).load(url) .placeholder(R.mipmap.fruit) .error(R.mipmap.fruit) .into(imageView); } }
使用@BindingAdapter注解设置自定义属性的名称,如上所示,imageUrl就是属性的名称,当ImageView中使用imageUrl属性时,会自动调用loadImage方法,参数imageView为当前使用imageUrl属性的ImageView,参数url为图片地址。
xml中使用自定义属性
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <import type="com.zx.databindingdemo.bean.UserBean" /> <variable name="user" type="UserBean" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="15dp" android:orientation="vertical"> <!-- 当imageUrl属性存在时,会自动调用ImageHelper的loadImage方法 --> <ImageView android:layout_width="120dp" android:layout_height="120dp" android:scaleType="centerCrop" app:imageUrl="@{user.picUrl}" /> </LinearLayout></layout>
Activity中设置图片的url
public class BasicActivity extends AppCompatActivity { //用户头像 private static final String URL_USER_PIC = "https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=4138850978,2612460506&fm=200&gp=0.jpg"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityBasicBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_basic); UserBean userBean = new UserBean(URL_USER_PIC, "张三", 24); binding.setUser(userBean); } }
这样就可以在DataBinding中使用Glide来加载图片了,此外,BindingAdapter也是允许设置多个属性的,如下:
@BindingAdapter({"imageUrl", "placeHolder",error"}) public static void loadImage(ImageView view, String url, Drawable holderDrawable, Drawable errorDrawable) { Glide.with(imageView.getContext()) .load(url) .placeholder(holderDrawable) .error(errorDrawable) .into(imageView); }
2.数据对象 (Data Objects)
任何plain old Java object (POJO)对象都能用在 Data Binding 中,但是有个问题,当我们的数据源发生改变的时候,UI界面上显示的还是之前的数据,并没有同步更新,这显然是不行的。不知道大家还记不记得,RecyclerView或者ListView是如何更新数据的?数据是通过Adapter提供的,当数据发生改变时,我们通过notifyDatasetChanged通过UI去改变数据,这是通过一个观察者模式来实现的,很幸运,查阅源码后发现DataBinding也是通过观察者模式来实现的。
DataBinding动态更新数据的2种方式
BaseObservable
ObservableFields
BaseObservable
我们可以通过Observable的方式去通知UI刷新数据,Observable 是一个包含添加/移除 listener 的机制的接口,为了简化开发,Android 原生提供了一个基类 BaseObservable 来实现 listener 注册机制。这个类也实现了字段变动的通知,只需要在 getter 上使用 Bindable 注解,并在 setter 中通知更新即可。
定义一个实体类,并继承BaseObservable
public class DoubleBindBean extends BaseObservable { //BR 是编译阶段生成的一个类,功能与 R.java 类似,用 @Bindable 标记过 getter 方法会在 BR 中生成一个 entry,当我们 //通过代码可以看出,当数据发生变化时还是需要手动发出通知。 通过调用notifyPropertyChanged(BR.firstName)来通知系统 BR.firstName 这个 entry 的数据已经发生变化,需要更新 UI。 private String content; //内容 public DoubleBindBean(String content) { this.content = content; } @Bindable public String getContent() { return content; } public void setContent(String content) { this.content = content; notifyPropertyChanged(BR.content); //通知系统数据源发生变化,刷新UI界面 } }
BR 是编译阶段生成的一个类,功能与 R.java 类似,用 @Bindable 标记过 getxxx() 方法会在 BR 中生成一个 entry。 当数据发生变化时需要调用 notifyPropertyChanged(BR.content) 通知系统 BR.content这个 entry 的数据已经发生变化以更新UI。
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> <variable name="doubleBindBean" type="com.zx.databindingdemo.bean.DoubleBindBean" /> <variable name="onClickListener" type="android.view.View.OnClickListener" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="15dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{doubleBindBean.content}" /> <Button android:id="@+id/change_content_btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="@{onClickListener}" android:text="BaseObservable方式改变内容" /> </LinearLayout></layout>
Activity:
public class DoubleBindActivity extends AppCompatActivity implements View.OnClickListener { private DoubleBindBean doubleBindBean; private boolean flag; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityDoubleBindBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_double_bind); doubleBindBean = new DoubleBindBean("我是原始内容"); binding.setDoubleBindBean(doubleBindBean); binding.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.change_content_btn: //BaseObservable flag = !flag; if (flag) { doubleBindBean.setContent("我是更新后的内容"); } else { doubleBindBean.setContent("我是原始内容"); } break; } }
ObservableFields
如果想要省时,或者数据类的字段很少的话,可以使用 ObservableField 以及它的派生 ObservableBoolean、 ObservableByte ObservableChar、ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble、 ObservableParcelable 等。
它的实现就更简洁了,如下
public class DoubleBindBean2 { //变量需要为public public final ObservableField<String> username = new ObservableField<>(); }
Activity:
public class DoubleBindActivity extends AppCompatActivity implements View.OnClickListener { private DoubleBindBean2 doubleBindBean2; private boolean flag2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityDoubleBindBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_double_bind); doubleBindBean2 = new DoubleBindBean2(); doubleBindBean2.username.set("我是原始内容2"); binding.setDoubleBindBean2(doubleBindBean2); binding.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.change_content_btn2: //ObservableFields flag2 = !flag2; if (flag2) { doubleBindBean2.username.set("我是更新后的内容2"); } else { doubleBindBean2.username.set("我是原始内容2"); } break; } } }
Observable Collections
除了支持ObservableField,ObservableBoolean,ObservableInt等基础变量类型以外,当然也支持集合框架拉,比如:ObservableArrayMap,ObservableArrayList。使用和普通的Map、List基本相同
<?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="list" type="android.databinding.ObservableArrayList<String>" /> <variable name="map" type="android.databinding.ObservableArrayMap<String,Object>" /> <variable name="onClickListener" type="android.view.View.OnClickListener" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="15dp"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{list[0]}" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text='@{map["key0"]}' /> <Button android:id="@+id/change_content_btn3" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="@{onClickListener}" android:text="list改变内容" /> <Button android:id="@+id/change_content_btn4" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="@{onClickListener}" android:text="map改变内容" /> </LinearLayout></layout>
Activity:
public class DoubleBindActivity extends AppCompatActivity implements View.OnClickListener { private ObservableArrayList<String> list=new ObservableArrayList<>(); private ObservableArrayMap<String,Object> map = new ObservableArrayMap<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityDoubleBindBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_double_bind); list.add("list1"); list.add("list2"); binding.setList(list); map.put("key0","key0_value0"); map.put("key1","key1_value1"); binding.setMap(map); binding.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.change_content_btn3: list.set(0,"after change list"); break; case R.id.change_content_btn4: map.put("key0","after change key0_value0"); break; } } }
View with ID
如果我们需要在Activity中获取某个View来进行一些操作,那该怎么办呢?别担心,有办法,布局中每一个带有 ID 的 View,都会生成一个 public final 字段。binding 过程会做一个简单的赋值,在 binding 类中保存对应 ID 的 View。这种机制相比调用 findViewById 效率更高。举个例子:
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="user" type="com.connorlin.databinding.model.User"/> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/userName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.userName}"/> </LinearLayout></layout>
Activity
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); binding.userName.setText("content") } }