码迷,mamicode.com
首页 > 移动开发 > 详细

Android官方数据绑定框架DataBinding

时间:2016-04-19 20:02:33      阅读:312      评论:0      收藏:0      [点我收藏+]

标签:

一、Data Binding是什么?

2015年的Google IO大会上,Android 团队发布了一个数据绑定框架(Data Binding Library),官方原生支持 MVVM 模型。以后可以直接在 layout 布局 xml 文件中绑定数据了,无需再 findViewById 然后手工设置数据了。其语法和使用方式和 JSP 中的 EL 表达式非常类似。

Data Binding Library 是一个 support 库,支持 Android 2.1+ 版本 (API level 7+)。 由于该框架需要使用编译器来生成很多代码,所以需要配合新版本的 Android Studio (1.3.0-beta1 + 版本)才能使用,Gradle 插件1.5.0-alpha1以上。

例:

<?xml version="1.0" encoding="utf-8"?>
<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}"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"/>
   </LinearLayout>
</layout>

二、配置环境

  • Android Studio版本是1.3+,更新Support repository到最新的版本。
    新建一个project,在dependencies中添加以下依赖

    classpath "com.android.databinding:dataBinder:1.0-rc1"
  • 新建module,并且在module的build.gradle文件中添加

    apply plugin: ‘com.android.application‘
    apply plugin: ‘com.android.databinding‘
  • Studio1.5+忽略以上两点,只需在对应module的build.gradle中添加

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

    ps:即使依赖的库中使用了data binding,该module也必须在build.gradle中配置

三、体验第一个data binding

布局文件

<?xml version="1.0" encoding="utf-8"?>
<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}"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"/>
   </LinearLayout>
</layout>

数据对象

public class User {
   private final String firstName;
   private final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
   public String getFirstName() {
       return this.firstName;
   }
   public String getLastName() {
       return this.lastName;
   }
}

绑定数据

@Override
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
   User user = new User("Test", "User");
   binding.setUser(user);
}

Alternatively, you can get the view via:

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

在 ListView 或者 RecyclerView 的Adapter中使用:

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

绑定事件

例如,如果你的数据对象中有两个方法:

public class MyHandlers {
    public void onClickFriend(View view) { ... }
    public void onClickEnemy(View view) { ... }
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="handlers" type="com.example.Handlers"/>
       <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:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.lastName}"
           android:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/>
   </LinearLayout>
</layout>

四、详细

导包

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

像java一样,java.lang.* 自动导入,可以直接使用

当存在同名的类时,其中一个可以使用别名

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

导入类还可以用于在表达式中引用静态属性和方法

<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"/>

变量

自动生成的binding类会为每个声明的变量生成setter和getter方法,在调用setter方法之前变量的值为java默认值。
当表达式中需要使用时,会自动生成一个名为“context”的变量,“context”的值是由根布局的getContext()获得,如果手动声明了一个名为“context”的变量,默认的会被覆盖。

自定义Binding类名

默认Binding类会根据布局文件名自动生成,放在module包下的databinding包中,如布局文件contact_item.xml会自动生成ContactItemBinding,如果module包名是com.example.my.app,这个类将被放在com.example.my.app.databinding下。通过class属性,可以修改Binding类的类名和所在包。

  • databinding包下,类名为ContactItem

    <data class="ContactItem">
        ...
    </data>
  • module包下

    <data class=".ContactItem">
        ...
    </data>
  • 指定包名

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

Includes

使用include时,需要将变量传递到被包含的布局中,此时name.xmlcontact.xml中必须有user变量。

<?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>

Data binding不支持<merge>下直接的子元素使用include,比如下面的就不支持:

<?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>
   <merge>
       <include layout="@layout/name"
           bind:user="@{user}"/>
       <include layout="@layout/contact"
           bind:user="@{user}"/>
   </merge>
</layout>

五、表达式

普通

以下表达式和java的一样
* 算术表达式 + - / * %
* 字符串连接 +
* 逻辑运算符 && ||
* 位运算符 & | ^
* 一元运算符 + - ! ~
* 位移运算符 >> >>> <<
* 关系运算符 == > < >= <=
* instanceof
* 分组 ()
* 字面值 - 字符, 字符串, 数字, null
* 类型转换
* 方法调用
* 访问属性
* 访问数组 []
* 三元运算符 ?:

例:

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName=‘@{"image_" + id}‘

缺少的

java中可以使用而这里不能使用的
* this
* super
* new
* Explicit generic invocation

判断非空运算符

当表达式左边不为null时使用左边的值,如果为null使用右边的
android:text="@{user.displayName ?? user.lastName}"
等效于
android:text="@{user.displayName != null ? user.displayName : user.lastName}"

避免了 NullPointerException

生成的data binding代码会自动检查空值,避免空指针。例如表达式@{user.name},如果usernulluser.name将使用默认值nulluser.age将使用默认值0。

集合(Collections)

<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[&quot;firstName&quot;]}"

资源文件(Resources)

直接在表达式中使用resources

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

Format strings and plurals may be evaluated by providing parameters:

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

When a plural takes multiple parameters, all parameters should be passed:

  Have an orange
  Have %d oranges

android:text="@{@plurals/orange(orangeCount, orangeCount)}"

Some resources require explicit type evaluation.

Type Normal Reference Expression Reference
String[] @array @stringArray
int[] @array @intArray
TypedArray @array @typedArray
Animator @animator @animator
StateListAnimator @animator @stateListAnimator
color int @color @color
ColorStateList @color @colorStateList

六、数据对象

任何一个简单的Java对象(POJO)都可以用来绑定,但是修改一个POJO不能够触发UI更新。
以下是3种数据改变通知机制,Observable objects, observable fields, 和 observable collections,当这3种数据对象绑定到UI,并且数据改变时UI会自动更新

Observable Objects

实现 android.databinding.Observable 接口。为了方便,Android提供了基类BaseObservable,实现了监听器的注册机制。

private static class User extends BaseObservable {
   private String firstName;
   private String lastName;
   @Bindable
   public String getFirstName() {
       return this.firstName;
   }
   @Bindable
   public String getLastName() {
       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是在编译时生成在module的一个类,功能与R.java类似。

ObservableFields

属性较少时可以使用ObservableField,包含ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, 和 ObservableParcelable,简单的POJO就可以实现。

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

访问值时使用:

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

Observable Collections

ObservableArrayMap(当key是引用类型时)

ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
<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(当key是整数时)

ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);
<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中的View,就如前面说的,Binding的类名和包名都可以定制,生成的binding类都继承自ViewDataBinding

创建

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

If the layout was inflated using a different mechanism, it may be bound separately:

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

带ID的View

如果布局中某个View设置了id,则生成的binding类中会包含:

public final TextView firstName;
public final TextView lastName;

ViewStubs

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <LinearLayout
        ...>
        <ViewStub
            android:id="@+id/view_stub"
            android:layout="@layout/view_stub"
            ... />
    </LinearLayout>
</layout>
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);
    }
});

高级用法

动态变量

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

public void onBindViewHolder(BindingHolder holder, int position) {
   final T item = mItems.get(position);
   holder.getBinding().setVariable(BR.item, item);
   holder.getBinding().executePendingBindings();
}

当一个变量改变时,binding会在下一帧时改变UI,需要立刻执行,可以使用executePendingBindings()方法。

八、Attribute Setters

当一个绑定的值改变时,可以指定调用哪个方法来设置值

Automatic Setters

没有配置自定义属性,data binding会自动找到对应的setAttribute方法(命名空间无所谓),注意表达式的返回值,必要的时候进行类型转换。

<android.support.v4.widget.DrawerLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:scrimColor="@{@color/scrim}"
    app:drawerListener="@{fragment.drawerListener}"/>

Renamed Setters

如果不想调用根据名字匹配的setter,可以通过BindingMethods注解重新匹配。

@BindingMethods({
       @BindingMethod(type = "android.widget.ImageView",
                      attribute = "android:tint",
                      method = "setImageTintList"),
})

通常开发者不需要重命名setters

Custom Setters

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
   view.setPadding(padding,
                   view.getPaddingTop(),
                   view.getPaddingRight(),
                   view.getPaddingBottom());
}

还可以接收多个参数

@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
   Picasso.with(view.getContext()).load(url).error(error).into(view);
}
<ImageView app:imageUrl=“@{venue.imageUrl}”
    app:error=“@{@drawable/venueError}”/>

这个适配器会在imageUrl和error都在ImageView中设置,imageUrl是字符串,error时drawable时调用

Binding adapter methods may optionally take the old values in their handlers. A method taking old and new values should have all old values for the attributes come first, followed by the new values:

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int oldPadding, int newPadding) {
   if (oldPadding != newPadding) {
       view.setPadding(newPadding,
                       view.getPaddingTop(),
                       view.getPaddingRight(),
                       view.getPaddingBottom());
   }
}

事件适配器必须使用带有一个抽象方法的接口或抽象类,如:

@BindingAdapter("android:onLayoutChange")
public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue,
       View.OnLayoutChangeListener newValue) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        if (oldValue != null) {
            view.removeOnLayoutChangeListener(oldValue);
        }
        if (newValue != null) {
            view.addOnLayoutChangeListener(newValue);
        }
    }
}

当一个listener有多个方法时,必须分成多个listener。比如View.OnAttachStateChangeListener有两个方法:onViewAttachedToWindow()onViewDetachedFromWindow(),就必须创建两个接口来区分他们。

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
    void onViewDetachedFromWindow(View v);
}

@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
    void onViewAttachedToWindow(View v);
}

因为改变一个listener经常会影响另一个,所以我们需要有3个不同的绑定适配器。

@BindingAdapter("android:onViewAttachedToWindow")
public static void setListener(View view, OnViewAttachedToWindow attached) {
    setListener(view, null, attached);
}

@BindingAdapter("android:onViewDetachedFromWindow")
public static void setListener(View view, OnViewDetachedFromWindow detached) {
    setListener(view, detached, null);
}

@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})
public static void setListener(View view, final OnViewDetachedFromWindow detach,
        final OnViewAttachedToWindow attach) {
    if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
        final OnAttachStateChangeListener newListener;
        if (detach == null && attach == null) {
            newListener = null;
        } else {
            newListener = new OnAttachStateChangeListener() {
                @Override
                public void onViewAttachedToWindow(View v) {
                    if (attach != null) {
                        attach.onViewAttachedToWindow(v);
                    }
                }

                @Override
                public void onViewDetachedFromWindow(View v) {
                    if (detach != null) {
                        detach.onViewDetachedFromWindow(v);
                    }
                }
            };
        }
        final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,
                newListener, R.id.onAttachStateChangeListener);
        if (oldListener != null) {
            view.removeOnAttachStateChangeListener(oldListener);
        }
        if (newListener != null) {
            view.addOnAttachStateChangeListener(newListener);
        }
    }
}

The above example is slightly more complicated than normal because View uses add and remove for the listener instead of a set method for View.OnAttachStateChangeListener. The android.databinding.adapters.ListenerUtil class helps keep track of the previous listeners so that they may be removed in the Binding Adaper.

By annotating the interfaces OnViewDetachedFromWindow and OnViewAttachedToWindow with @TargetApi(VERSION_CODES.HONEYCOMB_MR1), the data binding code generator knows that the listener should only be generated when running on Honeycomb MR1 and new devices, the same version supported by addOnAttachStateChangeListener(View.OnAttachStateChangeListener).

九、转换器(Converters)

Object Conversions

当binding表达式返回一个对象时,会从automatic, renamed, 和 custom setters 中选择一个,这个对象会被转换成选择的setter的参数类型。
这方便了用ObservableMaps保存数据,例如:

<TextView
   android:text=‘@{userMap["lastName"]}‘
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

userMap 返回了一个 Object ,这个 Object 将会自动转换成 setText(CharSequence)的参数类型。当参数类型不明确时,开发者需要在表达式中进行转换。

Custom Conversions

有时特殊的类型需要自动转换。比如,当设置背景时:

<View
   android:background="@{isError ? @color/red : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

这里,背景是一个 Drawable,但是颜色是一个整数,需要把 int 转换成 ColorDrawable,可以使用带有 BindingConversion 注解的一个静态方法:

@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
   return new ColorDrawable(color);
}

Note:不要将上面的和以下混淆

<View
   android:background="@{isError ? @drawable/error : @color/white}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Android官方数据绑定框架DataBinding

标签:

原文地址:http://blog.csdn.net/qian1127/article/details/51191956

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!