Android databinding

配置环境

data binding是Google推出的一个实现MVVM的一个支持库。可以极大的简化开发工作。它非常灵活,且支持从android2.1开始。

为了使用data binding,Android gradle plugin必须是1.5.0-alpha1或者以上版本, Android Studio 1.3或者以上。

在项目项目下的build.gradle添加代码:

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

1.Layout Files

data-binding的layout文件与普通layout有点轻微的不一样,多了layout的根节点。

//activity_main.xml
<?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>

<variable name="user" type="com.example.User"/>声明变量

在布局中使用上面的变量。

<TextView android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="@{user.firstName}"/>

User的下面两种定义是等效的。

//POJO
public class User {
   public final String firstName;
   public final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
}

//JavaBean
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;
   }
}

2.绑定数据

比如上面我们创建了activity_main文件,就会自动生成绑定类MainActivityBinding,它会保持从layout属性(比如user变量)到 layout view的所有绑定。然后可以使用MainActivityBinding为绑定表达式设置值,如下代码:

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

下面两种是等效的:

MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);

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

如果是在ListView或者RecyclerView的adapter里,使用数据绑定items,下面两种是等效的。

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

3.事件处理

Data Binding允许使用表达式来处理View派发的事件,比如:View.OnClick,有两种方式可以处理事件:方法引用和监听Binding,方法引用和监听Binding的主要区别是:真实的监听器会被创建,仅当数据被绑定的时,而不是监听器被触发。如果想要事件发生时,调用表达式,可以使用监听Binding.

3.1 方法引用

通过”::”引用方法,

public class MyHandlers {
    public void onClickFriend(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="@{handlers::onClickFriend}"/>
   </LinearLayout>
</layout>

3.2 监听Bindings

Android Gradle Plugin 2.0及以后添加的特性。
例如:有一个Present类

public class Presenter {
    public void onSaveClick(Task task){}
}

然后像下面一样,把click事件绑定给Present,如下:

<?xml version="1.0" encoding="utf-8"?>
  <layout xmlns:android="http://schemas.android.com/apk/res/android">
      <data>
          <variable name="task" type="com.android.example.Task" />
          <variable name="presenter" type="com.android.example.Presenter" />
      </data>
      <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent">
          <Button android:layout_width="wrap_content" android:layout_height="wrap_content"
          android:onClick="@{() -> presenter.onSaveClick(task)}" />
      </LinearLayout>
  </layout>

上面的代码可以发现,监听器是通过lambda表达式实现的。上面的lambda表达式是没有参数,我们也可以添加参数:

 android:onClick="@{(view) -> presenter.onSaveClick(task)}"

这个参数也可以被使用如下:

public class Presenter {
    public void onSaveClick(View view, Task task){}
}
  android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"

也可以使用多个参数:

public class Presenter {
    public void onCompletedChanged(Task task, boolean completed){}
}
  <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
        android:onCheckedChanged="@{(cb, isChecked) -> presenter.completeChanged(task, isChecked)}" />

假如监听事件有返回值,相应的监听Bindings也必须有返回值,如下:

public class Presenter {
    public boolean onLongClick(View view, Task task){}
}
  android:onLongClick="@{(theView) -> presenter.onLongClick(theView, task)}"

也可以使用三元表达式,使用void作为符号。

<!--使用void作为符号,代表什么都不做-->
android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}" 

避免使用复杂的监听器,尽量把实现细节放到回调的方法里。

4.Layout布局更多细节

4.1 imports

可以像Java一样import类

<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}"/> //使用View类

假如类名会产生冲突,可以使用alias定义别名,如下:

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

如何在layout文件中定义一个List<User>变量

<data>
    <import type="com.example.User"/>
    <import type="java.util.List"/>
    <variable name="user" type="User"/>
    <variable name="userList" type="List&lt;User&gt;"/>//&lt;==<, &gt;==>
</data>

Android Studio会为layout文件自动Import

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

注意:java.lang.*被自动import了

4.2 变量

变量类型是编译时检测的,所以要想变量实现Observable,那么它对应的类型必须要实现它。

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

4.3 自定义绑定类名

一般情况,layout文件对应的绑定类是自动生成的,比如contact_item.xml对应ContactItemBinding,如果module的包名是com.example.my.app,那么ContactItemBinding会默认放在com.example.my.app.databinding.

通过class属性来自定义绑定类名

//通过class属性定义
<data class="ContactItem">
    ...
</data>

通过”.”修改绑定类的包名

//直接放在module的包名下,比如com.example.my.app.ContactItem
<data class=".ContactItem">
    ...
</data>

也可以指定完整的包名

//那么绑定类的全路径就是com.example.ContactItem
<data class="com.example.ContactItem">
    ...
</data>

4.4 includes

<?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和contacts.xml中必须有一个user变量
不支持merge标签下面的直接子元素,如下是不支持的:

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

4.5表达式语言

4.5.1 有点像Java,如下支持:
  • 数学表达式: + - / * %
  • String连接符 +
  • 逻辑运算符 && ||
  • 二元运算符 & | ^
  • 一元运算符 + - ! ~
  • 位移 >> >>> <<
  • 比较 == ,> ,< ,>= ,<=
  • instanceof
  • 分组 ()
  • 字符- character, String, numeric, null
  • 转换 Cast
  • 方法调用
  • 字段调用
  • 数组访问 []
  • 三元运算符 ?:

比如例子:

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
4.5.2 在Java中支持,但是databinding不支持:
  • this
  • super
  • new
  • 显式泛型调用
4.5.3 “??”的功能,下面两断代码是等效的。
android:text="@{user.displayName ?? user.lastName}"
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
4.5.4 自动处理空指针异常

下面这段代码,如果user等于Null,那么user.lastName给使用默认分配的值(null).

android:text="@{user.lastName}"
4.5.5 使用集合
<data>
    <import type="android.util.SparseArray"/>
    <import type="java.util.Map"/>
    <import type="java.util.List"/>
    <variable name="list" type="List&lt;String&gt;"/>
    <variable name="sparse" type="SparseArray&lt;String&gt;"/>
    <variable name="map" type="Map&lt;String, String&gt;"/>
    <variable name="index" type="int"/>
    <variable name="key" type="String"/>
</data>
…
android:text="@{list[index]}"
…
android:text="@{sparse[index]}"
…
android:text="@{map[key]}"
4.5.6 使用字符串文字,下面三种是等效的
android:text='@{map["firstName"]}'
android:text="@{map[`firstName`}"
android:text="@{map['firstName']}"
4.5.7 使用resources, 允许如下使用resources
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

如果是格式化strings和plurals

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

一些resources需要显式调用,对应关系如下:
这里写图片描述

5. 数据对象

POJO可以在data binding中使用,但是修改对象不会导致UI刷新。有三种机制提供对象变化通知

  • observable 对象
  • observable 字段
  • observable集合

当这三种对象发生变化,UI会自动刷新。

5.1 observable对象

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类

5.2 observable字段

ObservableField,ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, ObservableParcelable

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();

5.3 observable集合

提供ObservableArrayMap

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&lt;String, Object&gt;"/>
</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);
<data>
    <import type="android.databinding.ObservableList"/>
    <import type="com.example.my.app.Fields"/>
    <variable name="user" type="ObservableList&lt;Object&gt;"/>
</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"/>

6. 生成绑定

我们上面讨论了,绑定类的类名和包名是可以自定义的。所有的绑定类都是继承ViewDataBinding

6.1 创建

提供了如下几种方式:

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

有些时候,绑定类名不能提前预知(比如在做封装类时),那么可以用DataBindingUtil类创建绑定。

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

6.2 Views With IDs

布局文件:

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

在绑定类中,有自动生成如下

public final TextView firstName;
public final TextView lastName;

6.3 变量

布局如下:

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

绑定类中,会自动生成如下代码:

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

6.4 ViewStubs

通过ViewStubProxy来代理。

6.5 高级绑定

有些时候,我们不知道具体的绑定类,比如RecyclerView.Adapter填充任意的layout,

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

7. 属性setters

当绑定值发生变化时,生成的绑定类必须使用绑定表达式在View上调用setter方法。 data binding有方法来自定义调用哪个方法来设置值.

7.1 自动的setters

DrawerLayout没有属性,但是有setScrimColorsetDrawerListerner,可以使用自动的setters来完成方法调用

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

7.2 重命名setters

可以通过BindingMethods来实现重命名setters

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

然后android:tint调用的是setImageTintList(ColorStateList),而不是setTint
开发人员不太可能需要重命名设置器; android框架的属性已经被实现了。

7.3 自定义setters

比如没有android:paddingLeft,因为setPadding(left, top, right, bottom) 已经存在了,但是我们可以通过BindingAdapter来定义一个android:paddingLeft,如下代码
@BindingAdapter(“android:paddingLeft”)
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}

BindingAdapter非常适用于加载图片,比如

<ImageView app:imageUrl="@{venue.imageUrl}"
app:error="@{@drawable/venueError}"/>

可以通过BindingAdapter来实现

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

BindingAdapters可以选择性的提供oldValue,oldValue必须放在第一个。如下

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

如果有多个方法,需要分解,比如View.OnAttachStateChangeListerner有两个方法: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);
}
@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);
        }
    }
}

8.转换器

8.1 对象转换

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

userMap返回一个对象,这个对象类型会自动转换成TextView.setText(CharSequence)指定的CharSequence,当类型无法自动转换时,开发者性需要显式转换。

8.2 自定义转换

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

background接受Drawable,但是color是int,所以使用到了@BindingConversion来实现自定义转换

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

注意:不允许使用混合模式,如下例子:

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

9.AS对databinding的支持

支持如下:
1. 语法高亮
2. 表达式语法错误提示
3. 代码自动完成
4. 引用使用navigation查找和定位,支持文档。

预览区域支持default值

<TextView android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:text="@{user.firstName, default=PLACEHOLDER}"/>

怎么使用默认值:参考http://tools.android.com/tips/layout-designtime-attributes

DataBinding例子:Google的https://github.com/google/android-ui-toolkit-demos

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值