美文网首页
data binding使用简介

data binding使用简介

作者: yong_history | 来源:发表于2017-03-09 10:17 被阅读0次

    Data Binding解决了Android UI编程中的一个痛点,官方原生支持MVVM模型可以让我们在不改变既有代码框架的前提下,非常容易的使用这些新特性。

    Data Binding库不仅灵活而且广泛兼容- 它是一个support库,因此你可以在所有的Android平台最低能到Android 2.1(API等级7+)上使用它。

    一、准备

    Android Studio更新到1.3及以上版本

    打开 Preferences,找到 Appearances & Behavior 下的 Updates 选项,把 Automatically Check updates for 修改成 Canary Channel。

    1433345455749011.png

    引入data binding的两种方式

    1.1 在Android SDK Manager的支持库里下载该库

    你的app要使用Data Binding,需要添加Data Binding到gradle构建文件里,如下:

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

    Data Binding插件将会在你的项目内添加必需提供的以及编译配置依赖。

    1.2 gradle配置依赖

    Gradle 版本为 1.2.3。

    classpath 'com.android.tools.build:gradle:1.2.3'
    classpath 'com.android.databinding:dataBinder:1.0-rc0'
    

    为用到 Data Binding 的模块添加插件,修改对应的 build.gradle。

    apply plugin: 'com.android.databinding'
    

    注意

    如果 Module 用到的 buildToolsVersion 高于 22.0.1,比如 23 rc1,那 com.android.databinding:dataBinder 的版本要改为 1.3.0-beta1,否则会出现如下错误:


    1433345521767514.png

    我使用这种方法会不成功,不知道为甚么。

    二、Data Binding Layout文件

    2.1 Data binding layout文件有点不同的是:起始根标签是layout,接下来是一个data元素及一个view的根元素。这个view元素就是没有使用data binding的layout文件的根元素。

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
      <data>
        <variable
            name="user"
            type="com.example.zhaoyong.databinding.model.User"/>
      </data>
      <FrameLayout
        android:layout_width="match_parent"
        android:background="#44ff0000"
        android:layout_height="wrap_content" >
    
        <TextView
            android:id="@+id/id_num"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:gravity="center"
            android:focusable="true"
            android:text="@{user.name}" />
      </FrameLayout>
    </layout>
    

    在data内描述了一个名为user的变量属性,使其可以在这个layout中使用:

     <variable
            name="user"
            type="com.example.zhaoyong.databinding.model.User"/>
    

    在layout的属性表达式写作@{},下面是一个TextView的text设置user name的属性

    <TextView
        android:id="@+id/id_num"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:focusable="true"
        android:text="@{user.name}" />
    

    2.2 Data对象

    假设你有一个user的plain-old Java Object(POJO):

    public class User {
      public String name;
      public User(String name) {
        this.name = name;
      }
    }
    

    这个类型的对象拥有从不改变的数据。在app中它是常见的,可以读取一次并且之后从不改变。当然也可以使用JavaBean对象:

    public class User {
      private String name;
      public User(String name) {
        this.name = name;
      }
      public String getName() {
        return this.name;
      }
    }
    

    从Data Binding的角度来看,这两个类是等价的。用于TextView中的android:text属性的表达式@{user.name}将访问前者的POJO对象中的name和后者JavaBean对象中的getName()方法。

    2.3 binding数据

    默认情况下,一个binding类会基于layout文件的名称而产生的,将其转换为Pascal case(首字母大写的命名规范)并且添加Binding后缀。上述的layout文件是main_activity.xml,因此生成的类名是MainActivityBinding。此类包含从layout属性到layout的Views中所有的恶binding(例如user变量),并且它还知道如何给Binding表达式分配数值。创建binding的最简单方式如下:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
    
        User user = new User(12, "aaaa");
        binding.setUser(user);
    }
    

    就是这样,运行app后,你将会看到Test User。或者你可以通过如下获取View:

    MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
    

    如果你在ListView或者RecyclerView adapter使用Data Binding时,你可能会使用:

    ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup,
    false);
    //or
    ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
    

    2.4 事件处理

    数据绑定允许你编写表达式来处理view分派的事件。事件属性名字取决于监听器方法名字。例如View.OnClickListener有onClick()方法,因此这个事件的属性是android:onClick。

    <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="button"
            android:layout_below="@+id/id_recyclerview"
            android:onClick="@{userViewModel::click}"/>
    

    三、深入Layout文件

    3.1 Import

    零个或多个import元素可能在data元素中使用。这些只用在你的layout文件中添加引用,就像在Java中:

    <data>
      <import type="android.view.View"/>
    </data>
    

    现在,View可以使用你的Binding表达式:

    <TextView
     android:text="@{user.lastName}"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
    

    当类名有冲突时,其中一个类名可以重命名为alias::

    <import type="android.view.View"/>
    <import type="com.example.real.estate.View"
        alias="Vista"/>
    

    这样,在该layout文件中Vista对应com.example.real.estate.View,而View对应android.view.View。导入的类型可以在Variable和表达式中使用作为引用来使用:

    <data>
      <import type="com.example.User"/>
      <import type="java.util.List"/>
      <variable name="user" type="User"/>
      <variable name="userList" type="List<User>"/>
    </data>
    

    导入的类型还可以在表达式中使用static属性和方法:

    <data>
      <import type="com.example.MyStringUtils"/>
      <variable name="user" type="com.example.User"/>
    </data>
    …
    <TextView
     android:text="@{MyStringUtils.capitalize(user.lastName)}"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"/>
    

    3.2 Variables

    在data中可以使用任意数量的variable元素。每一个variable元素描述了一个用于layout文件中Binding表达式的属性。

    <data>
      <import type="android.graphics.drawable.Drawable"/>
      <variable name="user"  type="com.example.User"/>
      <variable name="image" type="Drawable"/>
      <variable name="note"  type="String"/>
    </data>
    

    该Variable类型在编译时检查,因此如果一个Variable实现了Observable或observable collection,这应该反映在类型中。(译注:需要查找资料来理解)如果variable是一个没有实现Observable接口的基本类或者接口,Variables不会被observed!

    当对于多种配置有不同的layout文件时(如,横向或纵向),Variables会被合并。这些layout文件之间必须不能有冲突的Variable定义。

    产生的Binding类对于每一个描述的Variables都会有setter和getter。这些Variables会使用默认的Java值 - null(引用类型)、0(int)、false(boolean)等等,直到调用setter时。

    3.3 自定义Binding类名称

    默认情况下,Binding类的命名是基于所述layout文件的名称,用大写开头,除去下划线()以及()后的第一个字母大写,然后添加“Binding”后缀。这个类将被放置在一个模块封装包里的databinding封装包下。例如,所述layout文件contact_item.xml将生成ContactItemBinding。如果模块包是com.example.my.app,那么它将被放置在com.example.my.app.databinding。

    Binding类可通过调整data元素中的class属性来重命名或放置在不同的包中。例如:

    <data class="ContactItem">
    ...
    </data>
    

    在模块封装包的databinding包中会生成名为ContactItem的Binding类。如果要想让该类生成在不同的包种,你需要添加前缀.,如下:

    <data class=".ContactItem">
    ...
    </data>
    

    在这个情况下,ContactItem类直接在模块包种生成。或者你可以提供整个包名:

    <data class="com.example.ContactItem">
    ...
    </data>
    

    3.4 Include

    通过使用application namespace以及在属性中的Variable名字从容器layout中传递Variables到一个被包含的layout:

    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto">
     <data>
       <variable name="user" type="com.example.User"/>
     </data>
     <LinearLayout
       android:orientation="vertical"
       android:layout_width="match_parent"
       android:layout_height="match_parent">
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
     </LinearLayout>
    </layout>
    

    注意:在name.xml以及contact.xml两个layout文件中必需要有user variable

    3.5 表达式

    *常用表达式跟Java表达式很像,以下这些是一样的:

    ○ 数学 + - / * %
    ○ 字符串连接 +
    ○ 逻辑 && ||
    ○ 二进制 & | ^
    ○ 一元运算 + - ! ~
    ○ 移位 >> >>> <<
    ○ 比较 == > < >= <=
    ○ instanceof
    ○ 分组 ()
    ○ null
    ○ Cast
    ○ 方法调用
    ○ 数据访问 []
    ○ 三元运算 ?:

    示例:

    android:text="@{String.valueOf(index + 1)}"
    android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
    android:transitionName='@{"image_" + id}'
    
    *缺少的操作:

    ○ this
    ○ super
    ○ new
    ○ 显式泛型调用

    *Null合并操作

    ○ ?? - 左边的对象如果它不是null,选择左边的对象;或者如果它是null,选择右边的对象:

     android:text="@{user.displayName ?? user.lastName}"
    

    函数上的写法如下:

    android:text="@{user.displayName != null ? user.displayName : user.lastName}"
    
    *属性引用

    第一个已经在前边提到了a)Data Binding表达式:JavaBean引用的简短格式。
    当一个表达式引用一个类的属性,它仍使用同样的格式对于字段、getters以及ObservableFields。

    android:text="@{user.lastName}"
    
    *避免 NullPointerException

    Data Binding代码生成时自动检查是否为nulls来避免出现null pointer exceptions错误。例如,在表达式@{user.name}中,如果user是null,user.name会赋予它的默认值(null)。如果你引用user.age,age是int类型,那么它的默认值是0。

    *集合

    常用的集合:arrays、lists、sparse lists以及maps,为了简便都可以使用[]来访问。

    <data>
      <import type="android.util.SparseArray"/>
      <import type="java.util.Map"/>
      <import type="java.util.List"/>
      <variable name="list" type="List<String>"/>
      <variable name="sparse" type="SparseArray<String>"/>
      <variable name="map" type="Map<String, String>"/>
      <variable name="index" type="int"/>
      <variable name="key" type="String"/>
    </data>
    …
    android:text="@{list[index]}"
    …
    android:text="@{sparse[index]}"
    …
    android:text="@{map[key]}"
    
    *字符串

    当使用单引号包含属性值时,在表达式中使用双引号很容易:

    android:text='@{map["firstName"]}'
    

    使用双引号来包含属性值也是可以的。字符串前后需要使用"`":

    android:text="@{map[`firstName`]}"
    android:text="@{map["firstName"]}"
    
    *Resources

    使用正常的表达式来访问resources也是可行的:

    android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
    

    格式化字符串和复数可以通过提供参数来判断

    android:text="@{@string/nameFormat(firstName, lastName)}"
    android:text="@{@plurals/banana(bananaCount)}"
    

    当复数需要多个参数时,所有的参数都会通过:

    Have an orange
    Have %d oranges
    android:text="@{@plurals/orange(orangeCount, orangeCount)}"
    

    一些资源需要显式类型判断:

    类型 正常引用 表达式引用
    String[] @array @stringArray
    int[] @array @intArray
    TypedArray @array @typedArray
    Animator @animator @animator
    StateListAnimator @animator @stateListAnimator
    color int @color @color
    ColorStateList @color @colorStateList

    四、Data对象

    任何Plain old Java object(PO​​JO)可用于Data Binding,但修改POJO不会导致UI更新。Data Binding的真正能力是当数据变化时,可以通知给你的Data对象。有三种不同的数据变化通知机制:Observable对象、ObservableFields以及observable collections。

    当这些可观察Data对象​​绑定到UI,Data对象属性的更改后,UI也将自动更新。

    4.1 Observable 对象

    实现android.databinding.Observable接口的类可以允许附加一个监听器到Bound对象以便监听对象上的所有属性的变化。

    Observable接口有一个机制来添加和删除监听器,但通知与否由开发人员管理。为了使开发更容易,一个BaseObservable的基类为实现监听器注册机制而创建。Data实现类依然负责通知当属性改变时。这是通过指定一个Bindable注解给getter以及setter内通知来完成的。

    private static class User extends BaseObservable {
     private String firstName;
     private String lastName;
     @Bindable
     public String getFirstName() {
       return this.firstName;
     }
     @Bindable
     public String getFirstName() {
       return this.lastName;
     }
     public void setFirstName(String firstName) {
       this.firstName = firstName;
       notifyPropertyChanged(BR.firstName);
     }
     public void setLastName(String lastName) {
       this.lastName = lastName;
       notifyPropertyChanged(BR.lastName);
     }
    }
    

    在编译期间,Bindable注解在BR类文件中生成一个Entry。BR类文件会在模块包内生成。

    4.2 Observable 字段

    一些小工作会涉及到创建Observable类,因此那些想要节省时间或者几乎没有几个属性的开发者可以使用ObservableFields。ObservableFields是自包含具有单个字段的observable对象。它有所有基本类型和一个是引用类型。要使用它需要在data对象中创建public final字段:

    private static class User {
     public final ObservableField<String> firstName =
       new ObservableField<>();
     public final ObservableField<String> lastName =
       new ObservableField<>();
     public final ObservableInt age = new ObservableInt();
    

    }
    就是这样,要访问该值,使用set和get方法:

    user.firstName.set("Google");
    int age = user.age.get();
    

    4.3 Observable 集合

    一些app使用更多的动态结构来保存数据。Observable集合允许键控访问这些data对象。ObservableArrayMap用于键是引用类型,如String。

    ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
    user.put("firstName", "Google");
    user.put("lastName", "Inc.");
    user.put("age", 17);
    

    在layout文件中,通过String键可以访问map:

    <data>
      <import type="android.databinding.ObservableMap"/>
      <variable name="user" type="ObservableMap<String, Object>"/>
    </data>
    …
    <TextView
     android:text='@{user["lastName"]}'
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"/>
    <TextView
     android:text='@{String.valueOf(1 + (Integer)user["age"])}'
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"/>
    

    ObservableArrayList用于键是整数:

    ObservableArrayList<Object> user = new ObservableArrayList<>();
    user.add("Google");
    user.add("Inc.");
    user.add(17);
    

    在layout文件中,通过索引可以访问list:

    <data>
      <import type="android.databinding.ObservableList"/>
      <import type="com.example.my.app.Fields"/>
      <variable name="user" type="ObservableList<Object>"/>
    </data>
    …
    <TextView
       android:text='@{user[Fields.LAST_NAME]}'
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
    <TextView
       android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}'
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
    

    五、Binding生成

    Binding类的生成链接了layout中variables与Views。如前面所讨论的,Binding的名称和包名可以定制。所生成的Binding类都扩展了android.databinding.ViewDataBinding。

    5.1 创建

    Binding应在inflation之后就立马创建,以确保View层次结构不在之前打扰layout中的binding到views上的表达式。有几个方法可以绑定到一个layout。最常见的是在Binding类上使用静态方法.inflate方法载入View的层次结构并且绑定到它只需这一步。还有一个更简单的版本,只需要LayoutInflater还有一个是采用ViewGroup:

    MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
    MyLayoutBinding binding = MyLayoutBinding.inflate(LayoutInflater, viewGroup, false);
    

    如果使用不同的机制载入layout,他可一分开绑定:

    MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
    

    有时Binding不能提前知道,对于这种情况,可以使用DataBindingUtil类来创建Binding:

    ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
    parent, attachToParent);
    ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
    

    5.2 带ID的Views

    在layout中对于每个带ID的View会生成一个public final字段。Binding在View层次结构上做单一的传递,提取带ID的Views。这种机制比起某些Views使用findViewById还要快。例如:

    <layout xmlns:android="http://schemas.android.com/apk/res/android">
       <data>
         <variable name="user" type="com.example.User"/>
       </data>
       <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="@{user.firstName}"
            android:id="@+id/firstName"/>
         <TextView 
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.lastName}"
            android:id="@+id/lastName"/>
       </LinearLayout>
    </layout>
    

    它会生成如下的Binding类:

    public final TextView firstName;
    public final TextView lastName;
    

    IDs不像没有Data Bindings那样几乎没有必要,但是仍然会有一些实例需要从代码中访问Views。

    5.3 Variables

    每个Variable会有访问方法。

    <data>
      <import type="android.graphics.drawable.Drawable"/>
      <variable name="user"  type="com.example.User"/>
      <variable name="image" type="Drawable"/>
      <variable name="note"  type="String"/>
    </data>
    

    它会在Binding中生成setters和getters:

    public abstract com.example.User getUser();
    public abstract void setUser(com.example.User user);
    public abstract Drawable getImage();
    public abstract void setImage(Drawable image);
    public abstract String getNote();
    public abstract void setNote(String note);
    

    5.4 ViewStubs

    xml中的 ViewStub 经过 binding 之后会转换成 ViewStubProxy。
    简单用代码说明一下,xml 文件与之前的代码一样,根节点改为 layout,在 LinearLayout 中添加一个 ViewStub,添加 ID。

    <layout xmlns:android="http://schemas.android.com/apk/res/android">
    <LinearLayout
        ...>
        <ViewStub
            android:id="@+id/view_stub"
            android:layout="@layout/view_stub"
            ... />
    </LinearLayout>
    </layout>
    

    在 Java 代码中获取 binding 实例,为 ViewStubProy 注册 ViewStub.OnInflateListener 事件,搞定!

    binding = DataBindingUtil.setContentView(this, R.layout.activity_view_stub);
    binding.viewStub.setOnInflateListener(new ViewStub.OnInflateListener() {
    @Override
    public void onInflate(ViewStub stub, View inflated) {
        ViewStubBinding binding = DataBindingUtil.bind(inflated);
        User user = new User("fee", "lang");
        binding.setUser(user);
    }
    });
    

    5.5 Dynamic Variables

    以 RecyclerView 为例,Adapter 的 DataBinding 需要动态生成,因此我们可以在 onCreateViewHolder 的时候创建这个 DataBinding,然后在 onBindViewHolder 中获取这个 DataBinding。

    public static class BindingHolder extends RecyclerView.ViewHolder {
      private ViewDataBinding binding;
      public BindingHolder(View itemView) {
        super(itemView);
      }
      public ViewDataBinding getBinding() {
        return binding;
      }
      public void setBinding(ViewDataBinding binding) {
        this.binding = binding;
      }
    }
    @Override
    public BindingHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
    ViewDataBinding binding = DataBindingUtil.inflate(
            LayoutInflater.from(viewGroup.getContext()),
            R.layout.list_item,
            viewGroup,
            false);
    BindingHolder holder = new BindingHolder(binding.getRoot());
    holder.setBinding(binding);
    return holder;
    }
    @Override
    public void onBindViewHolder(BindingHolder holder, int position) {
    User user = users.get(position);
    holder.getBinding().setVariable(BR.user, user);
    holder.getBinding().executePendingBindings();
    }
    

    注意此处 DataBindingUtil 的用法:

    ViewDataBinding binding = DataBindingUtil.inflate(
    LayoutInflater.from(viewGroup.getContext()),
    R.layout.list_item,
    viewGroup,
    false);
    

    六、属性Setters

    有了 Data Binding,即使属性没有在 declare-styleable 中定义,我们也可以通过 xml 进行赋值操作。 为了演示这个功能,我自定义了一个 View - UserView,其中 R.styleable.UserView 中只定义了一个 age 属性,其中 firstName 和 lastName 只有对应的两个 setter 方法。

    只要有 setter 方法就可以这样为属性赋值:

    <com.liangfeizc.databindingsamples.attributesetters.UserView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingLeft="@dimen/largePadding"
    app:onClickListener="@{activity.clickListener}"
    app:firstName="@{@string/firstName}"
    app:lastName="@{@string/lastName}"
    app:age="27" />
    

    onClickListener 也是同样道理,只不过我们是在 Activity 中定义了一个 Listener。

    七、转换器

    在 xml 中为属性赋值时,如果变量的类型与属性不一致,通过 DataBinding 可以进行转换。

    <Button
    android:onClick="toggleIsError"
    android:text="@{isError.get() ? @color/red : @color/white}"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />
    

    只需要定义一个标记了 @BindingConversion 的静态方法即可:

    @BindingConversion
    public static int convertColorToString(int color) {
    switch (color) {
        case Color.RED:
            return R.string.red;
        case Color.WHITE:
            return R.string.white;
    }
    return R.string.app_name;
    }
    

    参考

    http://www.jianshu.com/p/b1df61a4df77
    http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0603/2992.html

    相关文章

      网友评论

          本文标题:data binding使用简介

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