Android Jetpack 架构组件(二) Data Binding  

google官方文档:https://developer.android.google.cn/topic/libraries/data-binding/expressions

看Data Binding之前,建议先看一下Android Jetpack 架构组件(一) View Binding

 

有了Data Binding,连都不需要了,还更简便!

layout xml 《-----》viewmodel或bean之间,直接通过Binding交互,开发者不需要参与。 activity或fragement只管理绑定关系,类里面基本是空的。

如果只是简单的页面, 那只用View Binding替换findviewbyId()就可以了, 毕竟Data Binding会占用内存,以及增加编译时间。

 

1.配置Data Binding

和View Binding一样也要在build.gradle文件配置一下。即使应用模块不直接使用Data Binding,但是依赖模块使用了Data Binding,那么也必须要配置如下

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

 

2.布局

和我们平时用的layout 文件稍有不同,数据绑定layout 文件,以根标记 <layout> 开头,后跟< data> 元素和 view 根元素。此view 根元素就是我们平常的layout文件内容。

以下代码展示了示例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.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>

有没有发现, 没有定义View的Id。因为不需要。User bean的数据直接绑上去了。

再来看看Activity的内容:

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(LayoutInflater.from(this));
        setContentView(binding.getRoot());

        User user = new User();
        user.setFirstName("alan");
        user.setLastName("gong");

        binding.setUser(user);
    }
}

也不像view binding一样,用binding.txtFirstName.setText("alan")这样。

而是直接binding.setUser(user);就好了。

data 中的 user 变量描述了可在此布局中使用的属性。

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

布局中的表达式使用“@{}”语法写入特性属性中。在这里TextView文本被设置为 user 变量的 firstName 属性:

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

3.binding表达式

这一块能灵活应用对开发效率有很大帮助。

属性引用 

表达式可以使用以下格式在类中引用属性,这对于字段、getter 和ObservableField对象都一样:

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

可以使用的运算符和关键字:

  • 算术运算符 + - / * %
  • 字符串连接运算符 +
  • 逻辑运算符 && ||
  • 二元运算符 & | ^
  • 一元运算符 + - ! ~
  • 移位运算符 >> >>> <<
  • 比较运算符 == > < >= <=(请注意,< 需要转义为 &lt;
  • instanceof
  • 分组运算符 ()
  • 字面量运算符 - 字符、字符串、数字、null
  • 类型转换
  • 方法调用
  • 字段访问
  • 数组访问 []
  • 三元运算符 ?:

不能使用的的运算符:

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

Null 合并运算符

如果??左边不为null, 就取左边值,相反取右边值。

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

等效于:

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

避免出现 Null 指针异常

生成的数据绑定代码会自动检查有没有 null 值并避免出现 Null 指针异常。例如,在表达式 @{user.name} 中,如果 user 为 Null,则为 user.name 分配默认值 null。如果您引用 user.age,其中 age 的类型为 int,则数据绑定使用默认值 0

view之间绑定

使用 Id 引用布局中的其他View,绑定类会自动将 ID 转换为驼峰式大小写。正式项目中必须直接用驼峰式,不然容易搞不清楚。

android:text="@{exampleText.text}"
    
    <EditText
        android:id="@+id/example_text"
        android:layout_height="wrap_content"
        android:layout_width="match_parent"/>
    <TextView
        android:id="@+id/example_output"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{exampleText.text}"/>
    

这个本来是十分好用的。但是目前功能很有限。我试了一下View的background绑定是不行的。编译不过。

 

另外,Data Binding最要命的是编译错误问题。错了都不知道哪里错了。所以写表达式的时候要仔细一点。

DataBinderMapperImpl.java:9: error: cannot find symbol
import com.alan.study.databinding.ActivityMainBindingImpl;
                                          ^
  symbol:   class ActivityMainBindingImpl
  location: package com.alan.study.databinding

 

 

集合

为方便起见,可使用 [] 运算符访问常见集合,例如数组、列表、hash表。

<data>
        <import type="android.util.SparseArray"/>
        <import type="java.util.Map"/>
        <import type="java.util.List"/>
        <variable name="list" type="List&lt;String>"/>
        <variable name="sparse" type="SparseArray&lt;String>"/>
        <variable name="map" type="Map&lt;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]}"

还可以使用 object.key 表示法。例如,以上示例中的 @{map[key]} 可替换为 @{map.key}

说实话, 我都写了一遍, 老是编译错误.

 

String常量

可以使用单引号括住特性值,这样就可以在表达式中使用双引号,如以下示例所示:

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

也可以使用双引号括住特性值。如果这样做,则还应使用反单引号 ` 将字符串字面量括起来:

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

以上是官方文档里说的。实际使用,android:text='@{map["firstName"]}' 才不会报错。
   

Resource资源

表达式可以使用以下语法引用应用资源:

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

字符串格式的使用:

<string name="nameFormat">My name is %s %s ,and %d years old</string>
android:text="@{@string/nameFormat(firstName, lastName)}"

复数形式:

<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:text="@{@plurals/numberOfSongsAvailable(20,20)}" />

结果:20 songs found.

查看绑定的源代码可以看到:
mboundView2AndroidPluralsNumberOfSongsAvailableUserAgeUserAge = mboundView2.getResources().getQuantityString(R.plurals.numberOfSongsAvailable, userAge, userAge);

可以将属性引用和view引用作为资源参数进行传递:

android:text="@{@string/example_resource(user.lastName, textViewLastName.text)}"
    

某些资源需要显式类型求值,如下表所示:

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

就是像“@{@xxxx}”这样使用。

 

事件处理

通过数据绑定,可以在layout, 用绑定表达式, 编写View的处理事件(例如,onClick() 方法)。事件名称由监听器方法的名称确定,但有一些例外情况。例如,View.OnClickListener 有一个 onClick() 方法,所以该事件名称是android:onClick

另外还有一些名称是这样一一对应的:

监听器 setter 属性
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

 

有两种事件绑定的机制:

方法引用

属性值绑定是., 而方法的绑定是用::。表达式中的方法签名必须与监听器对象中的方法签名完全一致,不然会编译错误。

如果表达式的求值结果为 null,则数据绑定不会创建监听器,而是设置 null 监听器。

方法引用和监听器绑定的区别在于实际监听器实现是在绑定数据时创建的,而不是在事件触发时创建的。如果您希望在事件发生时对表达式求值,则应使用监听器绑定。

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

 

监听器绑定

监听器绑定,就是用了 lambda 表达式。事件被触发后,监听器会对 lambda 表达式进行求值。

 public class Presenter {
        public void onSaveClick(Task task){}
    }
<?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>
    

监听器绑定提供两个监听器参数选项:1. 忽略方法的所有参数;2. 命名所有参数。如果想在表达式中使用这些参数,则必须命名参数。

例如,上面的表达式可以写成如下形式:

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

或者,如果您想在表达式中使用参数,则采用如下形式:

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

可以在 lambda 表达式中使用多个参数:

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

如果监听的事件返回类型不是 void 的值,则表达式也必须返回相同类型的值。例如,如果要监听长按事件,表达式应返回一个boolean。

如果由于 null 对象而无法对表达式求值,则数据绑定将返回该类型的默认值。例如,引用类型返回 nullint 返回 0boolean 返回 false,等等。

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

如果您需要将表达式与谓词(例如,三元运算符)结合使用,则可以使用 void 作为符号。

android:onClick="@{(v) -> v.isVisible() ? doSomething() : void}"

要避免使用复杂的监听器, 使得布局难以阅读和维护。复杂的逻辑放到后台去做。

 

导入、变量和包含

通过导入,就可以在布局文件中使用导入类。

可以在 data 元素使用多个 import 元素,也可以不使用。以下代码示例将 View 类导入到布局文件中:

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

以下示例展示了如何引用 View 类的 VISIBLE 和 GONE 常量:

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

类型别名

当类名有冲突时,其中一个类可使用别名重命名。以下示例将 com.example.view软件包中的 View 类重命名为 Vista

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

导入其他类

导入的类型可用作变量和表达式中的类型引用。以下示例显示了用作变量类型的 User 和 List, 不能直接<import type="java.util.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>"/>
</data>
    

导入的类型后,可以进行类型转换。以下示例将 connection 属性强制转换为类型 User

<TextView
       android:text="@{((User)(user.connection)).lastName}"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>

在表达式中引用静态字段和方法时,也可以使用导入的类型。以下代码会导入 MyStringUtils 类,并引用其 capitalize 方法:

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

变量

可以在 data 元素中使用多个 variable 元素。每个 variable 都是绑定的对象实例。以下示例声明了 userimage 和 note 变量:

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

在生成的绑定类中,每个描述的变量都有一个对应的 setter 和 getter。在调用 setter 之前,这些变量一直采用默认的托管代码值,例如引用类型采用 nullint 采用 0boolean 采用 false,等等。

支持<include/>

变量可以从包含的布局传递到被包含布局的绑定。以下示例展示了来自 name.xml 和 contact.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>
    

数据绑定不支持 include 作为 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><!-- Doesn't work -->
           <include layout="@layout/name"
               bind:user="@{user}"/>
           <include layout="@layout/contact"
               bind:user="@{user}"/>
       </merge>
    </layout>
    

contact.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:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/txtSize"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="@{user.firstName}" />
    </LinearLayout>
</layout>

 

4. 可观察对象(observable data objects)

目的是对象数据值发生改变的时候,界面也会自动更新。

任何普通对象都可用于数据绑定,但修改对象不会自动使界面更新。需要做出可观察对象才行。

可观察类有三种不同类型:对象、字段和集合。

 

可观察字段,集合

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("Alan");
int age = user.age.get();

可观察对象

当开发者需要自己决定何时发送通知时。那就使用可观察对象。在notifyPropertyChanged()之前还可以再做一些事情。

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

    

数据绑定在模块包中生成一个名为 BR 的类,该类包含用于数据绑定的资源的 ID。

在编译期间,Bindable注释会在 BR 类文件中生成一个条目。

如果数据类的基类无法更改,Observable 接口可以使用 PropertyChangeRegistry对象实现,以便有效地注册和通知监听器。 啥意思??????

 

5.绑定适配器(Binding adapters)

用于解决三个问题:

1. 弥补当初设计Android UI Framework时候的疏忽。因为用于Data Binding的属性都必须有严格的get/set方法, 但是Android Views只有少数属性有严格的get/set方法

2. 自定义依赖属性。

3. 值转换。比如,android:background接受的是ColorDrawable,但是传的是String  (“#FFFFFF”),或者 int (R.color.red)。那么需要转换成ColorDrawable。

 

@BindingMethods/@BindingMethod

BindingMethods包含若干个BindingMethod,BindingMethod是BindingMethods的子集。

它们俩可以重定义依赖属性的Setter方法。 

Views类本来就有满足功能的setter方法,只是名字不对那么用BindingMethod映射一下就弥补好了。

@Target({ElementType.TYPE})
public @interface BindingMethods {
    BindingMethod[] value();
}

TextViewBindingAdapter 类是AndroidX的系统类,用于绑定适配TextView的。

@BindingMethods({
        @BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"),
        @BindingMethod(type = TextView.class, attribute = "android:drawablePadding", method = "setCompoundDrawablePadding"),
        @BindingMethod(type = TextView.class, attribute = "android:editorExtras", method = "setInputExtras"),
        @BindingMethod(type = TextView.class, attribute = "android:inputType", method = "setRawInputType"),
        @BindingMethod(type = TextView.class, attribute = "android:scrollHorizontally", method = "setHorizontallyScrolling"),
        @BindingMethod(type = TextView.class, attribute = "android:textAllCaps", method = "setAllCaps"),
        @BindingMethod(type = TextView.class, attribute = "android:textColorHighlight", method = "setHighlightColor"),
        @BindingMethod(type = TextView.class, attribute = "android:textColorHint", method = "setHintTextColor"),
        @BindingMethod(type = TextView.class, attribute = "android:textColorLink", method = "setLinkTextColor"),
        @BindingMethod(type = TextView.class, attribute = "android:onEditorAction", method = "setOnEditorActionListener"),
})
public class TextViewBindingAdapter {
}

比如, android:inputType,绑定后会执行到setRawInputType里面去。

 

@BindingAdapter

如果Views的一些属性没有get/set方法, 而且也没有满足功能但名字不对的方法。那么就用@BindingAdapter 来弥补一下

比如 TextView 的android:phoneNumber,在TextView里面没有setPhoneNumber/getPhoneNumber, 也没有功能一样的方法。那就只能用@BindingAdapter 解决一下。

 不用绑定是这样的流程:

<TextView
   android:id="@+id/txtname"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:phoneNumber="true"/>
在TextView的构造方法里,取出phoneNumber值true 或 false。
case com.android.internal.R.styleable.TextView_inputType:
    inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
    break;

取出phoneNumber值以后,在构造方法里同样完成赋值:

        } else if (phone) {
            createEditorIfNeeded();
            mEditor.mKeyListener = DialerKeyListener.getInstance();
            mEditor.mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;

以上是不用data binding的情况。

那么用了data binding, 又是什么样的流程呢?

我们现在xml里配置好data binding

    <data>
        <variable
            name="isPhoneNumber"
            type="boolean" />
    </data>
   
     <TextView
            android:id="@+id/txtname"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:phoneNumber="@{isPhoneNumber}"
            android:text="@{@string/name(user.firstName, user.lastName, user.age)}" />

运行起来后是跳到TextViewBindingAdapter 这个类的setPhoneNumber里面去了。再细究的话,功能都是一样的。

public class TextViewBindingAdapter { 

.... 
  @BindingAdapter({"android:phoneNumber"})
    public static void setPhoneNumber(TextView view, boolean phoneNumber) {
        if (phoneNumber) {
            view.setKeyListener(DialerKeyListener.getInstance());
        } else if (view.getKeyListener() instanceof DialerKeyListener) {
            view.setKeyListener(null);
        }
    }
}

@BindingAdapter 也可以用来新定义依赖属性!

用在ViewModel类, 自定义View, 或者像系统的TextViewBindingAdapter一样,另外写一个类。无论写在哪里,android studio编译的时候通过注解处理器都会找过来。

接受单个属性参数的定义。

    @BindingAdapter({"imageUrl", "error"})
    public static void loadImage(ImageView view, String url) {
      Picasso.get().load(url).into(view);
    }

也可以定义多个属性,并且在同一个方法里面:
    @BindingAdapter({"imageUrl", "error"})
    public static void loadImage(ImageView view, String url, Drawable error) {
      Picasso.get().load(url).error(error).into(view);
    }
<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />

可选 requireAll 标志设置为 false,代表两个属性不需要都绑定。

    @BindingAdapter(value={"imageUrl", "placeholder"}, requireAll=false)
    public static void setImageUrl(ImageView imageView, String url, Drawable placeHolder) {
      if (url == null) {
        imageView.setImageDrawable(placeholder);
      } else {
        MyImageLoader.loadInto(imageView, url, placeholder);
      }
    }

使用新值和旧值

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

事件也可以用新值和旧值,但是必须是interface或者抽象类接口

    @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有两个方法该怎么做呢?也是一样的,就是多了用系统的ListenerUtil追究老值。另外需要你再定义两个接口:

    @TargetApi(VERSION_CODES.HONEYCOMB_MR1)//加这个是因为databinding从3.1开始支持。可以不要加。因为现在都21了吧.
    public interface OnViewDetachedFromWindow {
      void onViewDetachedFromWindow(View v);
    }

    @TargetApi(VERSION_CODES.HONEYCOMB_MR1)//加这个是因为databinding从3.1开始支持。可以不要加。因为现在都21了吧.
    public interface OnViewAttachedToWindow {
      void onViewAttachedToWindow(View v);
    }
    
    @BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"}, requireAll=false)
    public static void setListener(View view, OnViewDetachedFromWindow detach, OnViewAttachedToWindow attach) {
        if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
            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);
                        }
                    }
                };
            }

            OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view, newListener,
                    R.id.onAttachStateChangeListener);
            if (oldListener != null) {
                view.removeOnAttachStateChangeListener(oldListener);
            }
            if (newListener != null) {
                view.addOnAttachStateChangeListener(newListener);
            }
        }
    }

    

Object 类型转换

当绑定表达式返回Object 时,系统会选择用于设置属性值的方法。Object 会转换为所选方法的参数类型。

 

@BindingConversion

android:background接受的是ColorDrawable,但是传的是String  (“#FFFFFF”),或者 int (R.color.red)。那么需要转换成ColorDrawable。

<View
       android:background="@{isError ? @color/red : @color/white}"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"/>
    @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"/>

6. 双向数据绑定(Two-way data binding)

在之前单向绑定的基础上,UI view 的人为输入变化,直接反馈到绑定的对象上。这就是双向绑定。

UI View  《------------》java object。

在xml上的使用方式:

在原来@{}的基础上加上=。 变成@={}

在java上有两种方式:

1. 用@Bindable+ BaseObservable, 并且实现setter方法,在方法里面调用notifyPropertyChanged();

    public class LoginViewModel extends BaseObservable {
        private boolean rememberMe ;

        @Bindable
        public Boolean getRememberMe() {
            return rememberMe;
        }

        public void setRememberMe(Boolean value) {
            // Avoids infinite loops.
            if (rememberMe != value) {
                rememberMe = value;

                // React to the change.
                saveData();

                // Notify observers of a new value.
                notifyPropertyChanged(BR.remember_me);
            }
        }
    }

2. 自定义依赖属性

在setter方法上,注解@InverseBindingAdapter

    @BindingAdapter("time")
    public static void setTime(MyView view, Time newValue) {
        // Important to break potential infinite loops.
        if (view.time != newValue) {
            view.time = newValue;
        }
    }


 @InverseBindingAdapter("time")
    public static Time getTime(MyView view) {
        return view.getTime();
    }

 

@InverseMethod

类型,格式转换的功能

<EditText
        android:id="@+id/birth_date"
        android:text="@={Converter.dateToString(viewmodel.birthDate)}"
    />
    public class Converter {
        @InverseMethod("stringToDate")
        public static String dateToString(EditText view, long oldValue,
                long value) {
            // Converts long to String.
        }

        public static long stringToDate(EditText view, String oldValue,
                String value) {
            // Converts String to long.
        }
    }

系统支持双向绑定的就这么几个view的属性。

特性 绑定适配器
AdapterView android:selectedItemPosition
android:selection
AdapterViewBindingAdapter
CalendarView android:date CalendarViewBindingAdapter
CompoundButton android:checked CompoundButtonBindingAdapter
DatePicker android:year
android:month
android:day
DatePickerBindingAdapter
NumberPicker android:value NumberPickerBindingAdapter
RadioButton android:checkedButton RadioGroupBindingAdapter
RatingBar android:rating RatingBarBindingAdapter
SeekBar android:progress SeekBarBindingAdapter
TabHost android:currentTab TabHostBindingAdapter
TextView android:text TextViewBindingAdapter
TimePicker android:hour
android:minute
TimePickerBindingAdapter

 

终于写完了!已经是最完整的data binding了。

 

上一篇:详解Oracle临时表的几种用法及意义


下一篇:android开发EditText禁止输入中文密码的解决方法