开启DataBinding

在 Moudle 下面的build.gradle 中声明

android {
     …………
     dataBinding{
        enabled = true
     }    
     …………
}

这样就开启 DataBinding ,so easy

layout 标签

作用: 作为 DataBinding 的标志,省去 findViewById() 方法

<?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>
        <!--
            取一个 name ,并将她的 type 指向一个 Bean ,这样就可以绑定该对象了,
            使用的时候,通过@{}格将该对象的属性绑定到控件中式-->
        <variable
            name="student"
            type="top.hoyouly.framework.bean.Student"/>

        <variable
            name="controller"
            type="top.hoyouly.framework.mv.StudentActivity.Controller"/>

    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <EditText
            android:onTextChanged="@{controller.onChanged}"
            android:id="@+id/et_name"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <EditText
            android:id="@+id/et_nickName"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <!--@{student.name} 把 Student 中 names 属性的值设置给tv_name -->
        <TextView
            android:onClick="@{controller.onClicked}"
            android:id="@+id/tv_name"
            android:text="@{student.name}"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

        <!--@{student.nickName} 把 Student 中 nickName 属性的值设置给 tv_nickName -->
        <TextView
            android:id="@+id/tv_nickName"
            android:text="@{student.nickName}"
            android:onClick="@{()->controller.onClickListener(student)}"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

    </LinearLayout>

</layout>

data 标签

在 layou 标签下面,通过节点来引入我们要使用的数据源

variable 节点

定义在 data 标签里面的,可以有多个,

<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools">
    <data>
        <!--
            取一个 name ,并将她的 type 指向一个 Bean ,这样就可以绑定该对象了,
            使用的时候,通过@{}格将该对象的属性绑定到控件中式-->
        <variable
            name="student"
            type="top.hoyouly.framework.bean.Student"/>

    </data>

  </layou>  

name 属性表示变量的名称, type 表示这个变量的类型 其实可以理解为创建了一个 Student 类型的实例student 也可以换一种写法:使用 import 标签导入实体类,然后直接使用

<data>
    <import type="top.hoyouly.framework.bean.Student"/>
    <variable
        name="student"
        type="Student"/>

</data>

如果有两个同名的类,那该怎么办呢, import 里有一个 alias 属性,表示给该类起一个别名,使用别名处理就可以了,这样就可以直接在 variable 中直接使用别名了。

<data>
    <import type="top.hoyouly.framework.bean.Student" alias="Stu"/>
      <variable
          name="student"
          type="Stu"/>
</data>

设置完 variable 标签后,怎么使用呢,其实很简单,只要记号,在 xml 中使用这些标签属性,格式就是 @{}中间填写的就是 variable 中 name 的名字,这个就是 type 类型的变量,直接使用他做操作就行了,例如android:text="@{student.nickName}"就是把 student 变量中的 name 值设置到 TextView 中。

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

    <!--@{student.name} 把 Student 中 names 属性的值设置给tv_name -->
    <TextView
        android:id="@+id/tv_name"
        android:text="@{student.name}"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <!--@{student.nickName} 把 Student 中 nickName 属性的值设置给 tv_nickName -->
    <TextView
        android:id="@+id/tv_nickName"
        android:text="@{student.nickName}"
        android:onClick="@{()->controller.onClickListener(student)}"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

设置完之后,那么在 Activity 上要做啥操作呢?

在我们定义的 Activity 中,把原来 setContentView() 这行代码换掉,换成 ActivityStudentBinding mBinding = DataBindingUtil.setContentView(this, R.layout.activity_student);

这里解释一下, ActivityStudentBinding 这个类是自动生成的,生成的规则和我们设置的布局文件名字有关,因为我们的布局文件叫activity_student,所以生成的类名字就是取出下划线,然后根据驼峰命名法,后面再加上 Binding ,就是新类名,这个类就是 ViewModel , 有了这个 ViewModle ,我们就可以处理数据了

    student = new Student();
    student.setName("何磊");
    student.setNickName("hoyouly");
    mBinding.setStudent(student);

这里面就奇怪了,这个 setStudent() 方法是从哪里来的呢,其实还记得我们之前定义了一个 variable ,里面有一个 name 属性,值是 student ,这个 setStudent() 就是根据这个 student 变量,设置的 set 方法, 其实这个 mBinding 功能还不仅仅是这些,他可以直接取得我们设置的控件,只要添加有 id 的控件,都能通过 mBinding 得到,例如设置了一个 <EditText android:id="@+id/et_nickName",那么直接通过 mBinding.etNickName就得到这个 Edittext ,规则也是根据 id 的名字,然后驼峰命名法则取得。

@{}进行简单的计算

基本的三目运算

<TextView
           android:onClick="@{controller.onClicked}"
           android:id="@+id/tv_name"
           android:text="@{student.name??student.nickName}"
           android:layout_width="match_parent"
           android:layout_height="wrap_content" />

@{student.name??student.nickName},这里面有两个问号,注意,这表示,如果 username 属性为 null 则显示 nickname 属性,否则显示 username 属性。

字符串拼接

<TextView
            android:onClick="@{controller.onClicked}"
            android:id="@+id/tv_name"
            android:text="@{`name: `+student.name??student.nickName}"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />

拼接字符不是单引号,而是反单引号,也叫重音符,是在 ESC 上面那个键,使用 Markdown 语法插入代码的人肯定会经常用到这个符号,之前可能不支持中文,但是现在我测试了一下,是可以支持中文的。

根据数据显示样式

<TextView
         android:onClick="@{controller.onClicked}"
         android:id="@+id/tv_name"
         android:text="@{`中文: `+student.name??student.nickName}"
         android:background="@{student.age>30?0xFF0000FF:0xFFFF0000}"
         android:layout_width="match_parent"
         android:layout_height="wrap_content" />

这里给 TextView 设置背景,做了小判断,如果大于 30 ,设置 0xFF0000FF ,否则背景设置成 0xFFFF0000 这个颜色。

另外, DataBinding 对于基本的四则运算、逻辑与、逻辑或、取反位移等都是支持的,我这里不再举例。

绑定ImageView

DataBinding 自定义属性。 @BindingAdapter

通过@BindingAdapter 注解创建一个自定义属性。同时还需要有一个注解方法,当我们在布局文件中使用自定义属性的时候,会触发这个被我们注解的方法,

public class Student {
  public String name;
  public String nickName;
  public List<Courses> courses;
  public int age;
  public String userface;

  @BindingAdapter({"userface"})
  public static void getInternetImage(ImageView imageView,String userface){
        Picasso.get().load(userface).into(imageView);
    }
}

之前的方式是使用 @BindingAdapter("bind:userface") 中注解方式,但是会有一个警告的 警告: Application namespace for attribute bind:img will be ignored.,

主要看getInternetImage()方法。

  1. 使用了注解 BindingAdapter ,该注解表示用户在 ImageView 使用 userface 属性的时候,会触发这个方法
  2. 该方法必须是静态方法
<ImageView
           android:id="@+id/iv"
           app:userface="@{student.userface}"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"/>

ImageView 控件中使用 userface 属性的时候,使用的前缀不是 android 而是 app 哦

然后在 Activity 中怎么设置呢

student.userface="http://img2.cache.netease.com/auto/2016/7/28/201607282215432cd8a.jpg";
mBinding.setStudent(student);

绑定ListView

首先看 xml 布局文件

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <ListView
            android:id="@+id/lv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"></ListView>
    </LinearLayout>

</layout>

这个布局文件没啥可说的,就是一个 ListView 。与我们之前常见的唯一的区别就是最外层是 layout 标题,然后看看 item 的布局,

<?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">
    <data>
        <variable
            name="benefit"
            type="top.hoyouly.framework.bean.BenefitBean"/>
    </data>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="96dp"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/iv"
            android:layout_width="96dp"
            android:layout_height="96dp"
            android:padding="6dp"
            app:img="@{benefit.url}"/>

        <TextView
            android:id="@+id/description"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="8dp"
            android:layout_toRightOf="@id/iv"
            android:ellipsize="end"
            android:maxLines="3"
            android:text="@{benefit.desc}"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="8dp"
            android:layout_toRightOf="@id/iv"
            android:layout_alignParentBottom="true"
            android:layout_marginBottom="2dp"
            android:text="@{benefit.who}"
            android:textStyle="bold"/>
    </RelativeLayout>
</layout>

这个应该也看懂吧,定义的实体类是 BenefitBean ,实例名是 benefit ,然后就是一些设置。 ImageView 中设置图片的路径, TextView 中设置文本, 加载图片的方法写到任何类中都可以的,例如我写了一个专门加载图片的 Util 类,只要符合规则就行。一个是静态,二是使用 BindingAdapter 注解,自定义属性 img 就可以了。

public class ImageUtils {

  @BindingAdapter("bind:img")
  public static void loadInternetImage(ImageView iv, String img) {
    Picasso.get().load(img).into(iv);
  }
}

照样可以显示图片的。这样的话,我们就可以把所有处理图片的方法归纳到一个类里面了。

接下来看 Adapter 怎么处理的,这个可 NB 了,

public class MyBaseAdapter<T> extends BaseAdapter {
  private Context context;
  private LayoutInflater inflater;
  private int layoutId; //layoutId这个表示 item 布局的资源id
  private int variableId;//variableId是系统自动生成的,根据我们的实体类,直接从外部传入即可
  private List<T> list;

  public MyBaseAdapter(Context context, int layoutId , List<T> list, int resId) {
    this.context = context;
    this.layoutId = layoutId;
    this.list = list;
    this.variableId = resId;
    inflater = LayoutInflater.from(context);
  }

  public void setList(List<T> list) {
    this.list = list;
    notifyDataSetChanged();
  }

  @Override
  public int getCount() {
    return list.size();
  }

  @Override
  public Object getItem(int position) {
    return list.get(position);
  }

  @Override
  public long getItemId(int position) {
    return position;
  }

  @Override
  public View getView(int position, View convertView , ViewGroup parent) {
    ViewDataBinding dataBinding;
    if (convertView == null) {
      dataBinding = DataBindingUtil.inflate(inflater, layoutId , parent , false);
    } else {
      dataBinding = DataBindingUtil.getBinding(convertView);
    }
    dataBinding.setVariable(variableId, list.get(position));

    return dataBinding.getRoot();
  }
}

看着和我们之前写的 adapter 很像,区别有以下几点:

  1. 需要设置一个 variableId ,这个 variableId 是系统自动生成的,根据我们的实体类,直接从外部传入即可, 例如我们需要对应的实体类 是top.hoyouly.framework.bean.BenefitBean,那么对应的 variableId 就是 top.hoyouly.framework.BR.benefit, 其实就是 DataBinding 自动的在我们的包名下面创建了一个 BR 包,里面有一个 benefit 。至于这个 benefit 名字是怎么来的,没搞清楚
  2. 注意 getView() 方法中,使用的是 DataBindingUtil 中的方法加载布局和服用布局的。
  3. 这个几乎就是我们项目中的唯一一个 adapter 了,因为这个 Adapter 中没有一个变量和我们的 ListView 沾边,除非遇到非常奇葩的需求,你这个 App 中可能就只有这一个给 ListView 使用的 Adapter 了,

然后我们怎么使用呢?


public class GankActivity extends BaseBindingActivity<ActivityGankBinding> {
    private List<BenefitBean> benefitBeans=new ArrayList<BenefitBean>();
    private GankLoader loader;
    private MyBaseAdapter<BenefitBean> adapter;

    @Override
    protected void initView() {
        initData();
        getListData();
    }

    private void initData() {
        loader=new GankLoader();
        adapter = new MyBaseAdapter<>(GankActivity.this, R.layout.listview_item, benefitBeans , BR.benefit);//是 BR 中的,这个 BR 和我们项目中的 R 文件类似,都是系统自动生成的。
        mBinding.lv.setAdapter(adapter);
    }

    private void getListData() {
        SubscriberOnNextListener<GankBean> onNextListener = new SubscriberOnNextListener<GankBean>() {
            @Override
            public void onNext(GankBean gankBean) {
                adapter.setList(gankBean.results);
            }
        };
        loader.getGankBenefitList(10,1).subscribe(new ProgressDialogSubscriber<GankBean>(onNextListener,GankActivity.this));
    }

    @Override
    protected int getLayouId() {
        return R.layout.activity_gank;
    }
}

//BaseBindingActivity.java
public abstract class BaseBindingActivity<VB extends ViewDataBinding> extends Activity {
  protected  VB mBinding;

  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    mBinding=DataBindingUtil.setContentView(this,getLayouId());
    initView();
  }

  protected abstract void initView();

  protected abstract int getLayouId();

}

这个应该能看懂了吧,主要是在 initData() 中创建一个 adapter 对象,里面传递 context ,布局资源 id ,集合,和 variableId ,即BR.benefit,然后因为我们给 listview 设置了 id 为 lv ,所以可以直接通过mBinding.lv得到这个 listVIew ,并且设置 Adapter , 在 getListData 中,我是使用的Retrofit + OkHttp+RxJava 封装的,直接在 onNext() 得到数据,设置进去就可以了,这样就展示数据了,

给 Item 设置点击事件,

这个也很简单, 在 BenefitBean 中添加一个方法

public void onItemClick(View view) {  
    Toast.makeText(view.getContext(), getDescription() , Toast.LENGTH_SHORT).show();  
}  

注意,这个方法名字可以随便起,比如 aaa ,这个完全可以,但是为了规范,建议命名有含义,还有必须要注意的是,参数是固定的,写死的。其实可以理解之前在 Button 中设置android:onClick="change"这种方式设置点击事件规则就可以了,

然后在 item 的布局文件的根节点,添加 onClick 属性。

<?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">
    <data>
        <import type="top.hoyouly.framework.utils.TextUtil"/>
        <variable
            name="benefit"
            type="top.hoyouly.framework.bean.BenefitBean"/>
    </data>

    <RelativeLayout
        android:onClick="@{benefit.onItemClick}"
        android:layout_width="match_parent"
        android:layout_height="96dp"
        android:orientation="vertical">

        ...
        </RelativeLayout>
</layout>

在 RelativeLayout 标签中,看到了吗,android:onClick="@{benefit.onItemClick}",意思就是点击这个相对布局的时候,调用 BenefitBean 中的 onItemClick() 方法,

使用类方法

在一个类中 添加静态方法中 例如我在 TextUtil 中添加了一个静态方法,就是把传递过来的文本,显示两遍。

public class TextUtil {

    public static String doubleWord(final String word) {
        return word+"_"+word;
    }
}

使用的是,只需要在 data 标签下面使用 import 导入这个类,就可以了,然后在要使用的地方 android:text="@{TextUtil.doubleWord(benefit.desc)}" 完美。。

<?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">
    <data>
        <import type="top.hoyouly.framework.utils.TextUtil"/>
        <variable
            name="benefit"
            type="top.hoyouly.framework.bean.BenefitBean"/>
    </data>

    <RelativeLayout
        android:onClick="@{benefit.onItemClick}"
        android:layout_width="match_parent"
        android:layout_height="96dp"
        android:orientation="vertical">
        <TextView
            android:id="@+id/description"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginLeft="8dp"
            android:layout_toRightOf="@id/iv"
            android:ellipsize="end"
            android:maxLines="3"
            android:text="@{TextUtil.doubleWord(benefit.desc)}"/>

    </RelativeLayout>
</layout>

搬运地址:

告别 findView 和ButterKnife

DataBinding实用指南

Android基础——框架模式 MVVM 之 DataBinding 的实践

玩转 Android 之 MVVM 开发模式实战,炫酷的 DataBinding !

完全掌握Android Data Binding

Android DataBinding:再见 Presenter ,你好 ViewModel !