美文网首页AD_IOSAndroid知识程序员
Android数据绑定(DataBinding)

Android数据绑定(DataBinding)

作者: 滑稽的命运 | 来源:发表于2017-05-05 21:55 被阅读2308次

    什么是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"?>
      <layout xmlns:android="http://schemas.android.com/apk/res/android">
      <data>
       <variable name="person" type="包名.Person" />
      </data>
      </layout>
      
      • 使用import方式引入数据类,用这种方式引入数据类还可以使用它的静态变量和方法
      <?xml version="1.0" encoding="utf-8"?>
      <layout xmlns:android="http://schemas.android.com/apk/res/android">
      <data>
      <import type="包名.Person" />
      <variable name="person" type="Person" />
      </data>
      </layout>
      

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

      <import type="包名.Person" alias="OtherPerson"/>
      <variable name="otherPerson" type="OtherPerson"/>
      
    3. 在视图中绑定数据,请注意数据类型匹配,可在后用defalut设置默认值,默认值会显示在预览视图中。

       <LinearLayout
             android:orientation="vertical"
             android:layout_width="match_parent"
             android:layout_height="match_parent">
             <TextView android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:text="@{person.name}"/>
             <TextView android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:text="@{String.valueOf(person.age})}"/>
         </LinearLayout>
      

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

      public class ClickEvents {
          public void onBtnClick(View view) { ... }
      }
      
      <?xml version="1.0" encoding="utf-8"?>
      <layout xmlns:android="http://schemas.android.com/apk/res/android">
         <data>
             <variable name="clickEvents" type="包名.ClickEvents"/>
         </data>
         <LinearLayout
             android:orientation="vertical"
             android:layout_width="match_parent"
             android:layout_height="match_parent">
             <Button 
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:text="BTN"
                 android:onClick="@{clickEvents::onBtnClick}"/>
         </LinearLayout>
      </layout>
      

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

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

      注意:

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

      <?xml version="1.0" encoding="utf-8"?>
      <layout xmlns:android="http://schemas.android.com/apk/res/android">
          <data>
              <import type="java.util.Map" />
              <import type="java.util.List" />
              <import type="java.lang.String" />
              <import type="android.util.SparseArray"/>
              <!--以下爆红正常,运行无错。-->
              <variable name="array" type="String[]" />
              <variable name="list" type="List<String>" />
              <variablename="map" type="Map<String, String>" />
              <variablename="sparseArray" type="SparseArray<String>" />
          </data>
      
          <LinearLayout
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:gravity="center"
              android:orientation="vertical">
              <TextView
                  android:id="@+id/tv_array"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:text="@{array[0]}"
                  android:textSize="18sp" />
              <TextView
                  android:id="@+id/tv_list"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:layout_marginTop="10dp"
                  android:text="@{list[0]}"
                  android:textSize="18sp" />
              <TextView
                  android:id="@+id/tv_map"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:layout_marginTop="10dp"
                  android:text="@{map[`测试1`]}"
                  android:textSize="18sp" />
              <TextView
                  android:id="@+id/tv_sparseArray"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:layout_marginTop="10dp"
                  android:text="@{sparseArray[0]}"
                  android:textSize="18sp" />
          </LinearLayout>
      </layout>
      
      

    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<String> 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<String> name = new ObservableField<>();
          public Person(String name) {
              this.name.set(name);
          }
      }
      
    2. 视图绑定数据

      <?xml version="1.0" encoding="utf-8"?>
      <layout xmlns:android="http://schemas.android.com/apk/res/android">
      <import type="包名.Person" />
      <variable name="person" type="Person" />
        <LinearLayout
             android:orientation="vertical"
             android:layout_width="match_parent"
             android:layout_height="match_parent">
             <TextView
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:text="@{person.name}"/>
           <EditText
                  android:gravity="center"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:inputType="text"
                   android:text="@={person.name}"/>
         </LinearLayout>
      </layout>
      
    3. 绑定视图并设置数据

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

    绑定数据在RecyclerView中的应用

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

    1. 数据类

      public class Person {
          public ObservableField<String> 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"?>
      <layout xmlns:android="http://schemas.android.com/apk/res/android">
          <data>
              <variable name="person" type="包名.Person" />
          </data>
          <LinearLayout
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:gravity="center"
              android:orientation="vertical">
              <Button
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:onClick="onAddDataClick"
                  android:text="添加数据" />
              <android.support.v7.widget.RecyclerView
                  android:id="@+id/rv_people"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent" />
          </LinearLayout>
      </layout>
      
    3. item_rv_people_01视图

      <?xml version="1.0" encoding="utf-8"?>
      <layout>
          <data>
              <variable name="person" type="包名.Person" />
          </data>
          <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/item_rv_people"
              android:layout_width="match_parent"
              android:layout_height="40dp"
              android:gravity="center"
              android:paddingEnd="12dp"
              android:paddingStart="12dp">
              <TextView
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:text="@{person.name}"
                  android:textSize="16sp" />
              <TextView
                  android:id="@+id/tv_age"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:layout_marginStart="10dp"
                  android:text="@{String.valueOf(person.age)}"
                  android:textSize="16sp" />
          </LinearLayout>
      </layout>
      

    4. item_rv_people_02视图

      <?xml version="1.0" encoding="utf-8"?>
      <layout>
          <data>
              <variable name="person" type="包名.Person" />
          </data>
          <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/item_rv_people"
              android:layout_width="match_parent"
              android:layout_height="40dp"
              android:gravity="center"
              android:paddingEnd="12dp"
              android:paddingStart="12dp">
              <TextView
                  android:id="@+id/tv_age"
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:text="@{String.valueOf(person.age)}"
                  android:textSize="16sp" />
              <TextView
                  android:layout_width="wrap_content"
                  android:layout_height="wrap_content"
                  android:layout_marginStart="10dp"
                  android:text="@{person.name}"
                  android:textSize="16sp" />
          </LinearLayout>
      </layout>
      
    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<MultiLayoutPeopleAdapter.PeopleViewHolder> {
          private List<Person> people;
          private static Activity mActivity;
          MultiLayoutPeopleAdapter(Activity activity, List<Person> 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<Person> 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"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
        <data>
            <variable name="concat" type="包名.Concat" />
        </data>
       <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@{concat.content}"
                android:textSize="18sp" />
    </layout>
    
    @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)

        本文链接:https://www.haomeiwen.com/subject/ojgstxtx.html