AD_IOSAndroid知识程序员

Android数据绑定(DataBinding)

2017-05-05  本文已影响2308人  滑稽的命运

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

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

数据绑定是如何工作的?

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

优点:

Android Studio对其的支持:

注意:数组和通用类型(如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);
      

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

改变数据并通知视图

双向绑定

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

用法:在绑定数据时将@{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。

注意:

例:

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中绑定数据支持的表达式

不支持:this, super, new

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

错误信息

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

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

觉得还不够?

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

上一篇下一篇

猜你喜欢

热点阅读