什么是Android数据绑定(DataBinding)?

Android数据绑定是一个Google官方发布的帮助开发者处理视图与数据交互的支持库。

数据绑定是如何工作的?

数据绑定在编译时运行,处理视图文件中发现的表达式并在应用程序中生成代码,该库包含了应用程序中的常见代码。

优点:

  • 省去了findViewById()
  • 兼容到Android2.1(API 7)
  • 不使用反射,保证了性能
  • 支持绝大部分的 Java 写法
  • 最大程度减少绑定应用程序逻辑与视图所必需的代码
  • 支持双向绑定,即数据改变时可更新视图,反之亦然
  • 支持在任意线程更新数据(RecyclerView 和 ListView的数据除外 )
  • 避免了因数据导致的空指针,当绑定的数据无效时,视图会显示绑定数据类型的默认值

Android Studio对其的支持:

  • 语法高亮显示
  • 标记错误语法
  • XML代码补全
  • 快速跳转引用

注意:数组和通用类型(如Observable类)可能会在没有错误时显示错误。

准备使用

为了更好地进行Android开发,本人强烈建议使用Android Studio并保持Android Studio 与 Gradle为最新版本

配置数据绑定使用环境:

  1. Android 数据绑定需要Android Studio 1.3及更高版本

  2. Gradle 1.5.0-alpha1及更高版本

  3. 配置相应模块(Module)的build.gradle(若其他模块要用到数据绑定也需要此配置)

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

在视图文件中绑定数据

  1. 首先准备准备一个数据类(请注意,由于视图要访问该对象的私有变量,所以必须提供getter)

    public class Person{    private String name;    private int age;    public Person(String name, int age) {        this.name = name;        this.age = age;    }    public String getName() {        return name;    }    public int getAge() {        return age;    }}
  2. 在相应的视图文件中引入这个数据类(根节点必须为layout)

    • 使用variable标签方式引入数据类,name为变量名,type为数据类,必须要有完整的包名(Android Studio 支持输入类名自动查找补全)
    <?xml version="1.0" encoding="utf-8"?> 
    • 使用import方式引入数据类,用这种方式引入数据类还可以使用它的静态变量和方法
    <?xml version="1.0" encoding="utf-8"?>

    ​ 若出现不同包的同名类则可以在import 时 使用 alias 来指定一个别名,例如:

  3. 在视图中绑定数据,请注意数据类型匹配,可在后用defalut设置默认值,默认值会显示在预览视图中。

                      

    还可以绑定任何位置的点击事件,使用::来绑定点击事件

    public class ClickEvents {    public void onBtnClick(View view) { ... }}
    <?xml version="1.0" encoding="utf-8"?>                       

    绑定Array、List、Map、Sparse的数据(不支持Set)

    String[] array = {"测试数组"};List list = new ArrayList<>();list.add("测试集合");Map map = new HashMap<>();map.put("测试1", "测试Map");SparseArray sparseArray = new SparseArray<>();sparseArray.append(0, "测试SparseArray");binding.setArray(array);binding.setList(list);binding.setMap(map);binding.setSparseArray(sparseArray);

    注意:

    • 在xml中设置范型时要将"<>"换成相应的实体,"<" 对应 <, ">" 对应 >
    • 除Array外,其他的还可以用get()来取值

    <?xml version="1.0" encoding="utf-8"?>                                                                                                                        

  4. 在程序中绑定视图并设置数据

    注意:

    • 绑定数据的视图会自动根据其视图名字去掉"_"并在最后加上Binding生成驼峰式类名的绑定文件,例:activity_main => ActivityMainBinding

    • 视图中设置了id的元素会在对应的绑定类中生成一个对象,其命名方式同上,首字母小写,例:tv_name => tvName


    1. 绑定视图

      • 在Activity中

        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
      • 在Fragment中

        FragmentMainBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_main, container, false);// orFragmentMainBinding binding = FragmentMainBinding.inflate(inflater, container, false);
      • 在RecyclerView或者ListView中

        ItemBinding binding = ItemBinding.inflate(layoutInflater, viewGroup, false);//orItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
    2. 设置数据

      Person person = new Person("GavinRowe", 21);binding.setPerson(person);

    运行程序就能看到数据了!

改变数据并通知视图

  • 继承BaseObservable方式,使用数据类继承BaseObservable

    public class Person extends BaseObservable{    private String name;    private int age;      public Person(String name, int age) {        this.name = name;        this.age = age;    }    @Bindable    public String getName() {        return name;    }    @Bindable    public int getAge() {        return age;    }        public void setName(String name) {        this.name = name;        notifyPropertyChanged(BR.name);    }        public void setAge(int age) {        this.age = age;      notifyPropertyChanged(BR.age);    }  }

    标注变量写法

    private @Bindable String name;

    继承BaseObservable后将会获得两个公共的通知视图更新的方法:1. 通知所有数据更新 notifyChange(); 2. 通知特定数据更新 notifyPropertyChanged(int fieldId),参数为BR.java文件中对应的变量标志。

    被@Bindable注解标注的getter或者变量将会在一个位于包名下的BR.java文件中生成一个对应的变量标志,例:上面已经被标注的getName()与getAge()对应BR.name与BR.age。

    注:@Bindable注解不是必须的,不使用时就必须调用notfyChange()或notifyPropertyChanged(BR._all)来通知视图更新所有数据

  • 使用ObservableField方式

    public class Person{    public ObservableField name = new ObservableField<>() ;    public ObservableInt age = new ObservableInt();      public Person(String name, int age) {        this.name.set(name);        this.age.set(age);    }  }

    对应的ObservableField将会提供getter和setter,通过setter设置数据后会自动通知视图更新数据

双向绑定

双向绑定,即数据改变时可通知视图改变,视图改变时同时改变数据

用法:在绑定数据时将@{data} 变为 @={data}

  1. 数据类

    public class Person{    public ObservableField name = new ObservableField<>();    public Person(String name) {        this.name.set(name);    }}
  2. 视图绑定数据

    <?xml version="1.0" encoding="utf-8"?>                 
  3. 绑定视图并设置数据

     ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main); binding.setPerson(new Person("GavinRowe"));
双向绑定

绑定数据在RecyclerView中的应用

通过一个多布局RecyclerView来演示数据绑定

  1. 数据类

    public class Person {    public ObservableField name = new ObservableField<>();    public ObservableInt age = new ObservableInt();    public Person(String name, int age) {        this.name.set(name);        this.age.set(age);    }}
  2. 页面视图,由于要为RecyclerView设置适配器以及LayoutManager所以需要为它设置一个ID便于查找

    <?xml version="1.0" encoding="utf-8"?>                            
  3. item_rv_people_01视图

    <?xml version="1.0" encoding="utf-8"?>                                        

  4. item_rv_people_02视图

    <?xml version="1.0" encoding="utf-8"?>                                        
  5. MultiLayoutPeopleAdapter多布局适配器

    注意:

    • ViewHolder的写法,通过ViewDataBinding父类来接收两个不同视图的绑定类,两个布局共享的数据就是person,由于ViewDataBinding没有setPerson(),所以通过setVariable(BR.person, person) 方法设置键值对的方式来将Person对象绑定到两个不同的视图
    • getItemViewType(int position)直接返回对应Item视图的ID,通过DataBindingUtil就可以绑定任何想绑定的视图了
    • 关于executePendingBindings(),当你的数据改变时,数据绑定在一个动画帧之前刷新,executePendingBindings()可以立即强制刷新,此操作必须在UI线程进行
    • 若要分别对视图操作,则可将绑定类引用向下转型,然后分别获取视图来设置进行操作
    public class MultiLayoutPeopleAdapter extends RecyclerView.Adapter {    private List people;    private static Activity mActivity;    MultiLayoutPeopleAdapter(Activity activity, List people) {        mActivity = activity;        this.people = people;    }    @Override    public PeopleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {        return PeopleViewHolder.create(LayoutInflater.from(parent.getContext()), parent, viewType);    }    @Override    public void onBindViewHolder(PeopleViewHolder holder, final int position) {        holder.bindTo(people.get(position));        // 判断布局        if (holder.mBinding instanceof ItemRvPeople01Binding) {            ItemRvPeople01Binding item01 = (ItemRvPeople01Binding) holder.mBinding;            item01.itemRvPeople.setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View v) {                    Toast.makeText(mActivity, "item01的" + position + "被点了!", Toast.LENGTH_SHORT).show();                }            });        } else {            ItemRvPeople02Binding item02 = (ItemRvPeople02Binding) holder.mBinding;            item02.itemRvPeople.setOnClickListener(new View.OnClickListener() {                @Override                public void onClick(View v) {                    Toast.makeText(mActivity, "item02的" + position + "被点了!", Toast.LENGTH_SHORT).show();                }            });        }    }    @Override    public int getItemCount() {        return people.size();    }    @Override    public int getItemViewType(int position) {        if (position % 2 == 0) {            return R.layout.item_rv_people_01;        } else {            return R.layout.item_rv_people_02;        }    }    static class PeopleViewHolder extends RecyclerView.ViewHolder {        ViewDataBinding mBinding;        static PeopleViewHolder create(LayoutInflater inflater, ViewGroup parent, int type) {            ViewDataBinding binding = DataBindingUtil.inflate(inflater, type, parent, false);            return new PeopleViewHolder(binding);        }        private PeopleViewHolder(ViewDataBinding binding) {            super(binding.getRoot());            mBinding = binding;        }        void bindTo(Person person) {            mBinding.setVariable(BR.person, person);            mBinding.executePendingBindings();        }    }}
  6. 绑定视图

     private List people; private MultiLayoutPeopleAdapter multiLayoutPeopleAdapter; @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);        people = new ArrayList<>();        multiLayoutPeopleAdapter = new MultiLayoutPeopleAdapter(this, people);        binding.rvPeople.setLayoutManager(new LinearLayoutManager(this));        binding.rvPeople.setAdapter(multiLayoutPeopleAdapter);    }    public void onAddDataClick(View view) {        people.add(new Person("国哥", 21));        people.add(new Person("哥哥", 27));        people.add(new Person("姐姐", 30));        people.add(new Person("小红", 16));        people.add(new Person("小蓝", 15));        people.add(new Person("小橙", 14));        people.add(new Person("小绿", 13));        people.add(new Person("小黄", 12));        people.add(new Person("小花", 6));        people.add(new Person("小德", 5));        people.add(new Person("小梦", 4));        multiLayoutPeopleAdapter.notifyDataSetChanged();    }
多布局RecyclerView

注解:@Bindable

此注解可用来标注变量和getter,被标注后将会在一个位于包名下的BR.java文件中生成一个对应的变量标志。

注解:@BindingAdapter

此注解可用来标注方法,当xml中使用到该属性时就会调用其标注的方法。

单参用法:@BindingAdapter("xml属性"),参数可以为已有的xml属性,比如android:src,也可以自定义属性直接在xml中使用,若自定义属性不带命名空间(如:android:, app:, xxx:等 )将默认为app:,在使用的时候请注意声明命名空间,如:xmlns:app="http://schemas.android.com/apk/res-auto"

多参用法:@BindingAdapter(value = {"imgUrl", "android:clickable"}, requireAll = false),requireAll表示是否为每个声明的属性添加绑定值,默认为true。

注意:

  • 标注的方法第一个参数必须为对应的视图对象
  • 标注的方法参数顺序必须与标注的xml属性顺序一致
  • 在xml使用标注的属性时,其值必须用数据绑定的形式
  • 标注的方法为实例方法时,该类必须先实现DataBindingComponent,然后在相应绑定类解析视图之前调用DataBindingUtil.setDefaultComponent

例:

public class Concat {    public static String content = "测试Binding";    @BindingAdapter("android:text")    public static void add(final TextView tv, String content) {        Log.d("Concat", content);        tv.setText(content.concat("Adapter"));    }}
<?xml version="1.0" encoding="utf-8"?>                   
@BindingAdapter注解

XML中绑定数据支持的表达式

  • 数学 + - / * %
  • 字符串连接 +
  • 逻辑 && ||
  • 二进制 & | ^
  • 一元运算 + - ! ~
  • 三元运算 ?:
  • 判断是否为空 ??(例:android:text="@{user.name ?? user.defaultName}",相当于android:text="@{user.name !=null ? user.name : user.defaultName}")
  • 位运算 >> >>> <<
  • 比较 == > < >= <=
  • instanceof
  • 方法调用
  • 变量引用
  • 获取数组、集合、Map的值 []

不支持:this, super, new

建议在视图中用与视图相关的简单明了的表达式,否则建议使用方法或者@BindingAdapter

错误信息

在这里我会提供一些使用数据绑定时曾遇到过的错误和可能的原因,仅供参考!

更多的如果我遇到了会更新出来,如果正在阅读此文的你遇到过一些我没有提到的问题欢迎联系我

  • android.content.res.Resources$NotFoundException 数据类型错误导致,例:给android:text绑定数据时,该数据类型为int

  • Error:(6, 27) 错误: 找不到符号 符号: 类 DataBindingComponent 位置: 程序包 android.databinding 遇到此类错误请往下拉,一般在后面会有具体错误原因

    Error:(55, 29) Could not find accessor… 绑定数据时xml参数名写错了或者访问的私有变量未提供getter

    Error:(148, 30) Identifiers must have user defined types from the XML file 某个数据未在xml的data标签进行导入,如果已在data标签中用variable标签导入,检查绑定位置是否用类名来引用其数据,若是,换成import标签导入数据​

觉得还不够?

传送门:
官方DataBinding API
官方DataBinding 使用手册

更多相关文章

  1. Android(安卓)开发之详解 IPC 进程通信
  2. Android使用KeyStore对数据进行加密的示例代码
  3. 安卓ListView详解
  4. android中如何通过硬件获取H264帧?
  5. Android中SharedPrerence的apply和commit方法
  6. [置顶] 【Android】 基于Socket 的即时通信软件 YQ(源码下载)
  7. android 滑动相关(一) : OnScrollListener
  8. 2017年11月1日Android职位数据分析
  9. Android中基于NuPlayer的RTSP框架学习

随机推荐

  1. Android Unit Test学习
  2. android关于多dex打包的理解
  3. Android 强大的开发支持库组件AppFromwor
  4. Android系统的内存管理研究
  5. Android事件分发机制浅析
  6. 还在用android.support?该考虑迁移Android
  7. Android 2.3新特性:Web Apps概述
  8. Android系统架构概述
  9. [Android--Tool]不在Android设备运行而打
  10. Android Studio NDK开发在C代码中将Log输