Android数据绑定(DataBinding)

什么是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 支持输入类名自动查找补全)
    
    
    
     
    
    
    
    • 使用import方式引入数据类,用这种方式引入数据类还可以使用它的静态变量和方法
    
    
    
    
    
    
    
    

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

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

     
           
           
       
    

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

    public class ClickEvents {
        public void onBtnClick(View view) { ... }
    }
    
    
    
       
           
       
       
           

    绑定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()来取值

    
    
        
            
            
            
            
            
            
            
            
            
        
    
        
            
            
            
            
        
    
    
    

  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);
        // or
        FragmentMainBinding binding = FragmentMainBinding.inflate(inflater, container, false);
        
      • 在RecyclerView或者ListView中

        ItemBinding binding = ItemBinding.inflate(layoutInflater, viewGroup, false);
        //or
        ItemBinding 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. 视图绑定数据

    
    
    
    
      
           
         
       
    
    
  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便于查找

    
    
        
            
        
        
            
  3. item_rv_people_01视图

    
    
        
            
        
        
            
            
        
    
    

  4. item_rv_people_02视图

    
    
        
            
        
        
            
            
        
    
    
  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"));
    }
}


    
        
    
   

@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 使用手册

你可能感兴趣的:(Android数据绑定(DataBinding))