DataBinding的使用心得

这篇以前在简书写的blog,由于已经不会在简书写东西了,就搬过来这边

前言

在项目中使用到了DataBinding,深感它的优秀,于是进行分享。

什么是DataBinding

DataBinding,数据绑定,可以直接在xml中绑定数据并实现一些处理逻辑,实时动态刷新数据。它的功能强大,可以节省很多手写的代码,而且性能也很好。

DataBinding的优点和缺点

优点

上面说了它的性能很好,因为它0反射,而且性能比直接findViewById要高。可以查看DataBinding的源码:
图片.png
图片.png
还有它生成的实体类:
图片.png
一个Activity会有一个Window对象,而一个Window对象也有一个DecorView。DecorView是一个ViewGroup,布局文件都是通过inflate转化为view,加入到DecorView中,可以说DecorView是最大的根布局,而这个android.R.id.content正是它的id。DataBinding通过获取这个根布局,然后通过for循环将里面的控件一个个return出去,然后在生成的实体类再一个个获取。这样子的效率比直接findViewByid要效率的多,因为每次findViewByid都需要进行一次for循环在ViewGroup里面来寻找指定id名的控件。

缺点

目前AS对DataBinding在xml编写时还是不太友善的,代码自动补全功能做得还是有点差,这个有待Google日后的改善。

怎么使用 DataBinding

使用方法很简单,Gradle 1.5 alpha 及以上自带支持 DataBinding,仅在app模块的build.gradle中加上几行代码就可以使用了:

android {
    ...
    dataBinding {
        enabled = true
    }
    ...
}

将一个layout变成DataBinding的layout也很简单的:

<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">

//原来的layout 代码

</layout>

当我们将layout改造完成后,DataBinding就会自动生成对应的编译文件了。假如没有,可以make project一下。
而编译文件的命名一般都是通过xml的文件名生成,如activity_main.xml,则会生成ActivityMainBinding,item_list_name则会生成ItemListNameBinding。

动态更新数据

上面有提到过的,DataBinding可以实时动态刷新数据。这是通过继承DataBinding的BaseObservable来做到的。

public class InfoBean extends BaseObservable{    
private String unit;    
private String rental;     
private List<String> list;    

public InfoBean(String unit, String rental,List<String> list) {       
   this.unit = unit;       
   this.rental = rental;    
   this.list = list;  
}   

 @Bindable    
public String getUnit() {   
     return unit;   
 }   

 public void setUnit(String unit) {     
   this.unit = unit;        
   notifyPropertyChanged(BR.unit); 
}    

 public String getRental() {      
  return rental;   
 }    

public void setRental(String rental) { 
       this.rental = rental;   
 }   

 @Bindable    
public List<String> getList() {      
  return list;    
}   

 public void setList(List<String> list) {      
  this.list = list;        
  notifyPropertyChanged(BR.list);   
 }
}

BR.java是类似R.java的资源文件,是 Binding Resources 的缩写,由框架自动生成。
注意,BR 中的 id 生成的依据是 @Bindable 修饰的方法名 getXXX(),而非方法体的内容。当在 getXXX() 方法前加 @Bindable 之后,BR.java中会自动生成常量BR.XXX。如果觉得自动生成速度不够快,也可以试下make project一下。
DataBinding还提供了其他已经实现好的BaseObservable子类,包括 ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, 和ObservableParcelable。
它们在xml中,使用方法和普通的String,int一样,只是会自动刷新,但在java中访问则会相对麻烦,因为它是通过get/set方法来进行获取和修改的。这里使用ObservableBoolean来举个例子吧:

public ObservableBoolean Show = new ObservableBoolean(true);
...
Boolean flag = Show.get();
Show.set(false);

简单的使用

1.xml的逻辑

上面有提到过,DataBinding是可以在xml里面写一些处理逻辑的。我们将一个layout文件改成DataBinding模式后,通过DataBinding的data标签,我们可以很轻松给控件绑定文本等属性和绑定点击方法的。我们来看下面这一段的xml逻辑吧:

扫描二维码关注公众号,回复: 2073667 查看本文章
<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="android.view.View" />

            <variable    
              name="presenter"    
              type="com.fritz.demo.DemoActivity.DemoPresenter" />

           <variable
              name="item"
              type="fritz.databinding.com.Bean.InfoBean" /> 
      </data>

      ...........//原来的布局lyaout

</layout>

我们先来解析上面的代码吧,我们通过data标签来给DataBinding来设置需要绑定的数据,如果是使用系统自带的静态变量就需要像上面那样导入

<import type="android.view.View" />

如果需要绑定给TextView之类的控件文本,就需要给它一个绑定数据源,这里就是上面的InfoBean,当然你还得给这个数据源起一个名字(就像findViewById),同时也得写清楚这个数据源的所在路径,DataBinding才能找到它,完成绑定操作。
上面是绑定数据源,除了数据源可以绑定,方法也可以做绑定的,正如上面的HomePresenter 。当然了,也得写清楚这个指定方法对象的所在路径和名字。好了,现在看下我们的layout主体应该要怎么玩耍吧:

 <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_horizontal"
        android:orientation="vertical"
        android:padding="@dimen/activity_vertical_margin"
        tools:context="com.fritz.demo.DemoActivity">

      <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="5dp"
            android:onClick="@{presenter.onSetListSize}"
            android:text="hide or show"
            android:textAllCaps="false" />

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="5dp"
            android:onTextChanged="@{presenter.onTextChanged}" />

        <TextView
            android:id="@+id/txtTitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:text="@{item.unit!=null?item.unit:item.rental}"
            android:visibility="@{item.list.size()>0?View.VISIBLE:View.GONE}" />

        <ImageView
            android:layout_width="100dp"
            android:layout_height="100dp"
            android:onClick="@{()->presenter.onItemClick(item)}"
            android:src="@mipmap/ic_launcher"
            android:visibility="@{txtTitle.visibility }" />
</LinearLayout>

上面的xml代码逻辑应该一眼就看明白一部分了吧。当item.unit不为null时就使用它,否则就使用item.rental。同时id命名为txtTitle的这个TextView是否显示依据item.list.size这个集合的大小是否大于0。
不仅如此,我们还可以看到下方的ImageView可以得到id命名为txtTitle这个TextView的显示属性的赋值,如果这些逻辑我们都放在Java代码去写,你猜需要写多少行Java代码才能实现上面的功能呢?
而且DataBinding在绑定数据时,发现空值的对象都会给它一个初始值的。如果String为null就直接给它一个”“字符,int,float,long之类的就是一个0了。
这就帮助我们有效避免了空指针。
但是需要注意的数组或集合越界的问题,xml里面目前是没有方法可以判定数组或集合的size。
(这里其实还有一点需要注意的,如果要通过控件的id来获取控件的visibility属性,控件的id命名不能有符号的,否则DataBinding是无法识别的)

这里贴下DataBinding在xml中支持的计算表达式:
算术 + - / * %
字符串合并 +
逻辑 && ||
二元 & | ^
一元 + - ! ~
移位 >> >>> <<
比较 == > < >= <=
Instanceof
Grouping ()
文字 - character, String, numeric, null
Cast
方法调用
Field 访问
Array 访问 []
三元 ?:
尚且不支持this, super, new, 以及显示的泛型调用

2.方法的绑定

估计不少人会疑惑上面的3个监听方法吧。现在来说明下DataBinding是如何绑定执行方法吧:
DataBinding在xml里面进行方法绑定有两个方法:
一种是使用lambda 表达式,如()->presenter.onItemClick(item)
一种方法引用,如presenter.onClick或presenter.onTextChanged
那么它们有什么区别呢?
其实从我们上面的代码逻辑其实就可以猜出来了,那就是方法引用不能传递引用方法以外的参数,而lambda 表达式可以传递更多的数据给指定方法。
它们的使用规则如下:
1).方法返回值确保一致:方法引用必须保证绑定的两个方法的返回值为一致,如View的onClick方法没有返回值,那么我们创建的绑定方法onClick也不能有任何返回值。不过,lambda 表达式就没有这么严谨,在绑定的View方法没有返回值的情况下,我们创建的绑定方法有无返回值都是ok的。但是如果绑定的View方法有返回值,使用lambda 表达式绑定方法时也必须保证方法的返回值是一致的。

2).方法参数需要匹配:方法引用必须保证绑定的两个方法的里面的参数都为一致。如EditText的onTextChanged方法,我们进行绑定时必须确保绑定方法的参数和EditText的onTextChanged一致。而使用lambda 表达式绑定方法的参数也必须有同样的参数,只是在这些基础上,我们使用lambda 表达式可以添加更多的参数进行传递。

3.双向绑定

目前这个我只在EditText里面使用到。很简单,在要使用双向绑定的地方,使用 “@={}” 即可。
在InfoBean里面添加一些新的属性吧:

private String price;
public InfoBean(String unit, String rental, String price,List<String> list) {
      this.unit = unit;
      this.rental = rental;
      this.price = price;
      this.list = list;
}

@Bindable
public String getPrice(){
 return price;
}
public void setPrice(String price) {
 this.price = price;
 notifyPropertyChanged(BR.price);
}

然后xml添加新的代码:

<EditText
android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:layout_marginBottom="5dp"
  android:text="@={item.price}"/>

  <Button
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:text="@{item.price}"/>

这样子就可以了愉快使用双向绑定了。
关于双向绑定我另开了一篇blog:DataBinding的双向绑定,对双向绑定有兴趣的可以去看看。

4.Include布局

Include这个布局标签在DataBinding布局里面使用有点特殊, 因为它需要我们传递绑定的方法和数据对象。比如我们有以下的include布局:

<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <import type="android.view.View" />
        <variable
            name="presenter"
            type="com.fritz.demo.DemoActivity.DemoPresenter" />
        <variable
            name="item"
            type="com.fritz.demo.Bean.InfoBean" />
    </data>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal"
        android:orientation="vertical">

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="5dp"
            android:onTextChanged="@{presenter.onTextChanged}" />

        <TextView
            android:id="@+id/txtTitle"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{item.unit!=null?item.unit:item.rental}"
            android:visibility="@{item.list.size()>0?View.VISIBLE:View.GONE}" />
    </LinearLayout>
</layout>

那么我们在另一个xml引用时,就需要传递这个include需要绑定的方法和数据:

<include
 layout="@layout/include_demo"
 app:item="@{item}"
 app:presenter="@{presenter}" />

5.Java代码的实现

绑定数据的bean类和xml逻辑已经写好了,现在到页面的Java代码应该怎么玩耍了。
我们页面使用DataBinding加载布局文件一般来说分Activity和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 = FragmentHomeBinding.inflate(inflater, container, false);
    return mBinding.getRoot();
}

xml里面一切有id的view,都已经在DataBinding生成的实体类中被初始化完成了,只需要直接通过DataBinding的实例访问即可,如:

mBinding.txtTitle.setText(“Hello ,DataBinding”);

不过一般都不需要用到DataBinding来获取控件的实例,因为大多可以在xml中将控件逻辑处理好。具体的Java代码处理逻辑如下:

public class DemoActivity extends AppCompatActivity {

    private ActivityDemoBinding mBinding;
    private InfoBean mBean;
    private List<String> mList = new ArrayList<>();
    private boolean mShow;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_demo);
        initData();
    }

    private void initData() {
        mList.add("welcome");
        mBean = new InfoBean("Hello DataBinding", "","", mList);
        //绑定数据
        mBinding.setItem(mBean);
        //绑定方法
        mBinding.setPresenter(new DemoPresenter());
    }

    public class DemoPresenter {

        public void onSetListSize(View view) {
            if (!mShow) {
                mList.clear();
            } else {
                mList.add("welcome");
            }
            mShow = !mShow;
            mBean.setList(mList);
        }

        public void onTextChanged(CharSequence sequence, int start, int before, int count) {
            if (!TextUtils.isEmpty(sequence.toString())) {
                mBean.setUnit(null);
                mBean.setRental(sequence.toString());
            } else {
                mBean.setUnit("Hello DataBinding");
            }
        }

        public void onItemClick(InfoBean bean) {
            Toast.makeText(DemoActivity.this, bean.getUnit(), Toast.LENGTH_SHORT).show();
        }
    }
}

看一眼运行效果吧:
show.gif

自定义属性

我们在平时在xml里面总有一些控件的属性方法是没有法子去调用,但是通过DataBinding就可以比较方便去调用。

1.SetXXX的方法

DataBinding在遇到绑定属性时会自动去查找它的get/set方法。就拿TextView来举个例子好了,我们将上面的TextView的xml代码做如下的修改:

<TextView
  android:id="@+id/txtTitle"
  android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   app:text="@{item.unit!=null?item.unit:item.rental}" />

将android:text改为上面的app:text,这时候编译运行是没有问题的。但是如果DataBinding无法找到对应的方法,那么在编译期间就会报错了。利用这个特性,对于一些自定义View,就让它没有提供我们需要的自定义属性或setXXX方法,我们可以选择直接依据它的setXXX方法的名字来绑定属性或者继承这个View,添加我们需要的setXXX方法。不过,只要添加一个setXXX方法就来再创建一个类来继承View也太浪费了,DataBinding还有更好的方法。

2.@BindingMethod注解

这个注解是用来关联控件中提供的方法的。当控件的某些属性的setXXX方法并没有对应的自定义属性,就需要 @BindingMethod 来“牵绳拉线”,创建一个新的自定义属性。关于这个,可以看下DataBinding的源码里面的ImageViewBindingAdapter这一部分的代码:

图片.png
它将ImageView里面的两个方法关联了两个新的xml属性,这样子可以更加方便我们在xml中使用这两个方法。

3.@BindingAdapter注解

终于说着这个强大的家伙了。当控件里面没有提供某个属性的setXXX方法,又或者这个setXXX方法名字我们不喜欢,还有就是明明就是设置控件的某个属性的,但方法名却不是以set开头的,这些情况我们可以使用BindingAdapter这个强大的注解。
关于这个BindingAdapter的使用我们是可以到SDK里面这个位置查看:

sources\android-25\android\databinding\adapters

如果没有android-25,可以到android-24或23里面,都一样的。这里有很多Google写好的demo,我们有什么不懂的地方可以直接看这些demo来照着写。这里我分享下我平时利用BindingAdapter来使用RecyclerView时的写法,如下所示:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable
            name="adapter"
            type="android.support.v7.widget.RecyclerView.Adapter" />
        <variable
            name="layoutManager"
            type="android.support.v7.widget.RecyclerView.LayoutManager" />
    </data>

    <android.support.v7.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:padding="16dp"
        app:adapter="@{adapter}"
        app:layoutManager="@{layoutManager}" />
</layout>

直接在xml里面给RecyclerView绑定adapter和layoutManager,要做到这一点当然少不了强大的BindingAdapter:

public class UtilsBindingAdapter {
   @BindingAdapter("layoutManager")
    public static void setLayoutManager(RecyclerView view, RecyclerView.LayoutManager manager) {
        view.setLayoutManager(manager);
   }

   @BindingAdapter("adapter")
    public static void setAdapter(RecyclerView view, RecyclerView.Adapter adapter) {
        view.setAdapter(adapter);
    }
}

如果还需要给RecyclerView设置什么的话,完全可以按照上面的去写。在Java代码中的调用就是:

   mBinding.setLayoutManager(new LinearLayoutManager(this));
   mBinding.setAdapter(new RecyclerViewAdapter(this));

4.还有一个@BindingAdapter注解

在 xml 中为属性赋值时,如果变量的类型与属性不一致,这时候我们就可以使用BindingConversion。
比方说下面代码中如果要为属性 android:background 赋值一个 int 型的 color 变量:

<View
    android:background="@{isError.get() ? @color/red : @color/white}"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_height="@{height}" />

那么只需要定义一个标记了 @BindingConversion 的静态方法即可(方法的定义位置可以随意):

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
    return new ColorDrawable(color);
}

但是使用这转换器属性时我们必须要小心,因为DataBinding是不懂得区分是否真的需要使用整个转换器的。比方说我们再创建两个:

    @BindingConversion
    public static int convertColorToStringOne(int color) {
        switch (color) {
            case Color.RED:
                return R.string.red;
            case Color.WHITE:
                return R.string.white;
        }
        return R.string.app_name;
    }

   @BindingConversion
    public static int convertColorToStringTwo(int color) {
        switch (color) {
            case ColorBLACK:
                return R.string.black;
            case Color.GREEN:
                return R.string.green;
        }
        return R.string.app_name;
  }

这是两个将一个color的整型数转为指定的颜色文本的方法. DataBinding只要找到了这个convertColorToStringOne就不懂得再往下找了。

RecyclerView的绑定

RecyclerView是Google推出用于取代ListView和GridView的控件,它的布局,装饰和刷新机制都足以让我们放弃ListView和GridView。
上面已经提到了RecyclerView,现在具体说下DataBinding应该怎么绑定RecyclerView。
首先,我们需要定义一个基类的ViewHolder,方便我们使用DataBinding:

public class BindingViewHolder<T extends ViewDataBinding> extends RecyclerView.ViewHolder {

    private T mBinding;

    public BindingViewHolder(T binding) {
        super(binding.getRoot());
        mBinding = binding;
    }

    public T getBinding() {
        return mBinding;
    }
}

接着RecyclerView的Adapter直接使用这个BindingViewHolder,在onCreateViewHolder里面生成该ViewHolder:

public BindingViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        ViewDataBinding binding = DataBindingUtil.inflate(mInflater, R.layout.item_xxx,parent, false);
        return new BindingViewHolder(binding);
}

然后在onBindViewHolder里面进行数据绑定和设置Listener:

  @Override
  public void onBindViewHolder(BindingViewHolder holder, int position) {
        final String item = mDatas.get(position);
        //给ViewHolder的xml里面的item变量进行数据绑定
        holder.getBinding().setVariable(BR.item, item);
        //建立绑定关系,在主线程中实时更新任何进行了绑定的数据
        holder.getBinding().executePendingBindings();
        holder.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mListener != null) {
                    mListener.onItemClick(item);
            }
          }
       });
   }

这里创建接口并回调就不过多赘述了。核心的东西已经说完了,接着说下RecyclerView的DataBinding的第三方库吧。
目前Github上关于简化RecyclerView的DataBinding的方案我知道的有三种:
https://github.com/evant/binding-collection-adapter
https://github.com/radzio/android-data-binding-recyclerview
https://github.com/markzhai/DataBindingAdapter
前两者都可以让你少写代码,并且功能十分强大。但是我觉得灵活性是第三个比较好。这也是我目前在项目中使用的。这个项目的使用方法比较简单,可以直接去看它的ReadMe,这里就不多说了。

最后,一些建议

1.不要拒绝 findViewById

DataBinding 和 findViewById() 并不是互斥的,DataBinding的源码里面也是用到了findViewById()。如果某些情况真的不适合使用DataBinding,那就用回findViewById吧。

2.xml中的表达式尽量简单

xml 文件中不要出现过于复杂业务逻辑,只出现简单的 UI 相关的表达式。不要以为Data Binding是万能的,而想尽办法把逻辑写在xml中。往往这个时候你就应该将逻辑写在绑定的ViewModel里面。

3.注意clean

有时候因为修改接口等原因要修改绑定的bean类,这时候偶尔会遇到一些神奇的bug。不要慌张,直接clean一下项目再make project一次往往就好了

4.使用BindingConversion注解时需要慎重

原因上面已经说了。但是它也不失为一个很好用的注解,比方说使用它来进行字体的自定义。具体可以参照下面的文章:
使用DataBinding来进行字体的自定义

猜你喜欢

转载自blog.csdn.net/f409031mn/article/details/80868870