26
2017
09

菜鸟初体验之----DataBinding

构建环境

1.首先,确保能使用Data Binding,需要下载最新的 Support repository。否则可能报错

2.在模块的build.gradle文件中添加dataBinding配置

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "com.android.ebeijia.databindinglearndemo"
        minSdkVersion 15
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

    dataBinding{
        enabled=true
    }
}

注意:如果app依赖了一个使用 Data Binding 的库,那么app module 的 build.gradle 也必须配置 Data Binding

Data Binding布局文件

Data binding 的布局文件与传统布局文件有一点不同。它以一个 layout 标签作为根节点,里面是 data 标签与 view 标签。view 标签的内容就是不使用 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.android.ebeijia.databindinglibrary.bean.User" />
    </data>


    <LinearLayout  android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">

        <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.secondName}"/>

    </LinearLayout>
</layout>

数据对象

假设你有一个 plain-old Java object(POJO) 的 User 对象:

public class User {

    private String firstName;
    private String secondName;

    public User(String firstName,String secondName) {

        this.firstName=firstName;

        this.secondName=secondName;

    }

}

或者是JavaBean对象:

public class User {

    private String firstName;
    private String secondName;

    public User(String firstName,String secondName) {

        this.firstName=firstName;

        this.secondName=secondName;

    }


    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setSecondName(String secondName) {
        this.secondName = secondName;
    }

    public String getSecondName() {
        return secondName;
    }
}

从 Data Binding 的角度看,这两个类是一样的。用于 TextView 的 android:text 属性的表达式@{user.firstName},会读取 POJO 对象的 firstName 字段以及 JavaBeans 对象的 getFirstName()方法。

数据绑定

在默认情况下,会基于布局文件生成一个继承于 ViewDataBinding 的 Binding 类,将它转换成帕斯卡命名并在名字后面接上Binding。例如,布局文件叫 main_activity.xml,所以会生成一个 MainActivityBinding 类。这个类包含了布局文件中所有的绑定关系,会根据绑定表达式给布局文件赋值。在 inflate 的时候创建 binding 的方法如下:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //ActivityMainBinding是自动生成的
        ActivityMainBinding binding=DataBindingUtil.setContentView(this,R.layout.activity_main);
        User user=new User("John","Cena");
        //所有的set方法都是根据布局中variable名称生成的
        binding.setUser(user);
    }

或者你可以通过下面的方式获取到View:

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

如果在ListView或RecyclerView适配器中使用数据绑定项,则可能更喜欢使用:

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

事件处理

数据绑定允许您编写表达式处理从视图分派的事件(例如onClick)。事件属性名称由侦听器方法的名称控制,但有一些例外。例如,View.OnLongClickListener有一个方法onLongClick(),所以这个事件的属性是android:onLongClick。处理事件有两种方法。

Method References 您可以在表达式中引用符合侦听器方法签名的方法。当表达式计算为方法引用时,数据绑定将方法引用和所有者对象包装在侦听器中,并在目标视图上设置该侦听器。如果表达式求值为空,则数据绑定不会创建侦听器,而是设置一个null侦听器。

Listener Bindings 这些是当事件发生时评估的lambda表达式。数据绑定总是创建一个监听器,它在视图上设置。调度事件时,侦听器会计算lambda表达式。

Method References

事件可以直接绑定到处理程序方法,类似于android:onClick可以分配给一个Activity中的一个方法。与View#onClick属性相比,一个主要优点是表达式在编译时被处理,因此如果该方法不存在或其签名不正确,则会收到编译时错误。

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

要将事件分配给其处理程序,请使用正常的绑定表达式,其值为要调用的方法名称。例如,如果您的数据对象有两种方法

 public class MyHandles
    {
        private Context mContext;


        public MyHandles(Context context)
        {
            mContext=context;
        }

        public void onClickFriend(View view)
        {
            Toast.makeText(mContext,"Method References",Toast.LENGTH_LONG).show();
        }
    }

绑定表达式可以为视图分配点击监听器

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable  name="user" type="com.android.ebeijia.databindinglibrary.bean.User" />

        <variable  name="handles" type="com.android.ebeijia.databindinglibrary.MainActivity.MyHandles" />
    </data>


    <LinearLayout  android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">

        <TextView  android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}" android:padding="10dp" android:onClick="@{handles::onClickFriend}"/>

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



    </LinearLayout>
</layout>

Listener Bindings

侦听器绑定是在事件发生时运行的绑定表达式。它们与方法引用类似,但它们允许您运行任意数据绑定表达式。此功能适用于Gradle版本2.0及更高版本的Android Gradle Plugin。

在方法引用中,方法的参数必须与事件侦听器的参数相匹配。在侦听器绑定中,只有您的返回值必须与侦听器的预期返回值相匹配(除非它是期望的void)。例如,您可以有一个演示者类具有以下方法:

public class Presenter{

        public void onSaveClick(Task task)
        {
            task.run();
        }
    }
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable  name="user" type="com.android.ebeijia.databindinglibrary.bean.User" />

        <variable  name="handles" type="com.android.ebeijia.databindinglibrary.MainActivity.MyHandles" />
        <variable  name="presenter" type="com.android.ebeijia.databindinglibrary.MainActivity.Presenter" />

        <variable  name="task" type="com.android.ebeijia.databindinglibrary.bean.Task" />
    </data>


    <LinearLayout  android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">

        <TextView  android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}" android:padding="10dp" android:onClick="@{handles::onClickFriend}"/>

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

        <Button  android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{()->presenter.onSaveClick(task)}" />



    </LinearLayout>
</layout>

侦听器由只能作为表达式的根元素允许的lambda表达式表示。当表达式中使用回调时,数据绑定会自动为事件创建必要的侦听器和注册。当视图触发事件时,数据绑定会计算给定的表达式。与正常的绑定表达式一样,您仍然会收到数据绑定的null和线程安全性,同时正在评估这些侦听器表达式。

请注意,在上面的示例中,我们尚未定义传入onClick(android.view.View)的view参数。侦听器绑定为侦听器参数提供了两个选择:您可以忽略该方法的所有参数或全部名称。如果您喜欢命名参数,可以在表达式中使用它们。例如,上面的表达式可以写成:

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

如果您正在侦听的事件返回类型不为空的值,则表达式也必须返回相同类型的值。例如,如果要监听长时间点击事件,则表达式应返回布尔值:

如果由于空对象无法评估表达式,则数据绑定返回该类型的默认Java值。例如,对于引用类型为null,对于int为0,对于布尔值为false

如果您需要使用带有谓词(例如三元)的表达式,则可以使用void作为符号

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

监听器表达式非常强大,可以使您的代码非常容易阅读。另一方面,包含复杂表达式的监听器使您的布局难以阅读和不可维护。这些表达式应该像将UI中的可用数据传递给您的回调方法一样简单。您应该在从监听器表达式调用的回调方法中实现任何业务逻辑。
一些专门的点击事件处理程序存在,他们需要除android之外的属性:onClick以避免冲突。已创建以下属性以避免此类冲突:
这里写图片描述

布局详情

导入

数据元素内可以使用零个或多个导入元素。这些可以方便地引用布局文件中的类,就像在Java中一样。

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

在绑定表达式中使用View类:

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

根据user的isAdult字段判断TextView是否要显示。

当有类名冲突时,其中一个类可能被重命名为“别名”

<import type="android.view.View" />
        <import type="com.android.ebeijia.databindinglibrary.bean.View"
            alias="own_view1"
            />

现在,own_view1可能被用来引用com.example.real.estate.View和View可以用来引用layout.view.View中的layout文件。导入的类型可以用作变量和表达式中的类型引用。

<data>
        <import type="com.android.ebeijia.databindinglibrary.bean.User"/>
        <import type="java.util.List" />
        <variable
            name="user"
            type="User"
            />

        <variable
            name="userList"
            type="List&lt;User&gt;"
            />

        <import type="android.view.View" />
        <import type="com.android.ebeijia.databindinglibrary.bean.View"
            alias="own_view1"
            />
    </data>

注:Android Studio尚未处理导入,因此导入的变量的自动完成可能无法在IDE中运行。您的应用程序仍然可以编译好,您可以通过在变量定义中使用完全限定名称来解决IDE问题

在引用表达式中的静态字段和方法时也可以使用导入的类型。

public class MyStringUtils {

    public static String capitalize(final String name)
    {
        return String.valueOf(name.charAt(0)).toUpperCase() + name.substring(1);
    }
}
<TextView  android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{MyStringUtils.capitalize(user.firstName)}"/>

该静态方法的作用就是将firstName的 首字母变成大写。

数据元素内可以使用任意数量的可变元素。每个变量元素描述可以在布局中设置的属性,用于在布局文件中绑定表达式。

 <import type="android.graphics.drawable.Drawable"
            />
        <variable
            name="image"
            type="Drawable"
            />
        <variable
            name="note"
            type="String"
            />
        <variable
            name="user1"
            type="User"
            />

变量类型在编译时进行检查,因此如果一个变量实现了Observable,或者是一个可观察的集合,那么应该反映在类型中。如果变量是不实现Observable *接口的基类或接口,则不会观察到变量!

当各种配置(例如横向或纵向)有不同的布局文件时,将组合变量。这些布局文件之间不能有冲突的变量定义。

生成的绑定类将为每个描述的变量设置一个setter和getter。这些变量将使用默认的Java值,直到setter被调用为null为引用类型,0为int,false为布尔等。

生成一个名为context的特殊变量,用于根据需要在绑定表达式中使用。上下文的值是根视图的getContext()中的Context。上下文变量将被具有该名称的显式变量声明覆盖。

自定义绑定类名

默认情况下,根据布局文件的名称生成一个Binding类,以大写字母开始,删除下划线(_),并使用以下字母大小写,然后后缀为“Binding”。该类将被放置在模块包下的数据绑定包中。例如,布局文件contact_item.xml将生成ContactItemBinding。如果模块包是com.example.my.app,那么它将被放在com.example.my.app.databinding中。

可以通过调整数据元素的类属性将绑定类重命名或放置在不同的包中。例如:

<data class="TestBinding">
        <variable
            name="user"
            type="com.android.ebeijia.databindinglibrary.bean.User"
            />
    </data>
import com.android.ebeijia.databindinglibrary.databinding.TestBinding;

这将在模块包中的数据绑定包中生成绑定类作为ContactItem。如果该类应该在模块包中的不同包中生成,则可以以“.”为前缀:

<data class=".TestBinding">
        <variable
            name="user"
            type="com.android.ebeijia.databindinglibrary.bean.User"
            />
    </data>

这里写图片描述

在这种情况下,MyBinding是直接在模块包中生成的。如果提供完整的包,可以使用任何包:

<data class="com.android.ebeijia.databindinglibrary.binding.TestBinding">
        <variable
            name="user"
            type="com.android.ebeijia.databindinglibrary.bean.User"
            />
    </data>

Includes

变量可以通过使用应用程序命名空间和属性中的变量名称从包含的布局传递到包含的布局的绑定。例:
include.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

   <data>
       <variable  name="user" type="com.android.ebeijia.databindinglibrary.bean.User" />
   </data>

    <LinearLayout  android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">

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

        <TextView  android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.secondName}" android:padding="10dp"/>

    </LinearLayout>
</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 class="com.android.ebeijia.databindinglibrary.binding.TestBinding">
        <variable  name="user" type="com.android.ebeijia.databindinglibrary.bean.User" />
    </data>

    <LinearLayout  android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">

        <include layout="@layout/include" bind:user="@{user}" />

    </LinearLayout>
</layout>

这边需要注意的是,include.xml中必须声明一个user变量。

Data binding 不支持直接包含 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.connorlin.databinding.model.User"/>
   </data>
   <merge>
       <include layout="@layout/include" app:user="@{user}"/>
   </merge>
</layout>

表达式语言

通用特性
表达式语言和Java语言有很多相似之处,如下:
数学计算 + - / * %
字符串连接 +
逻辑 && ||
二进制 & | ^
一元 + - ! ~
位移 >> >>> <<
比较 == > < >= <=
instanceof
组 ()
字面量 - 字符,字符串,数字, null
类型转换
函数调用
字段存取
数组存取 []
三元运算符 ?:

例子:这里以字符串拼接和三目运算符为例

<TextView
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:text="@{`First Name:`+String.valueOf(user.firstName)}"
 android:padding="10dp"/>
        <TextView
 android:layout_width="wrap_content"
 android:layout_height="wrap_content"
 android:padding="10dp"
 android:text="@{10>5?`10大`:`5`}"/>

不支持的操作符

一些Java操作符在表达式语法中无法使用。
this
super
new
显式泛型调用

Null合并运算符

Null合并运算符 ?? 会在非 null 的时候选择左边的操作,反之选择右边。

<TextView  android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName??user.secondName}" android:padding="10dp"/>

其实等同于:

<TextView  android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName!=null?user.firstName:user.secondName}" android:padding="10dp"/>

Property Reference

第一个已经在上面写的第一个数据绑定表达式中讨论过:简单的JavaBean引用。当表达式引用类上的属性时,它对于fields,getter和ObservableFields使用相同的格式。

避免空指针
生成的数据绑定代码自动检查null并避免空指针异常。例如,在表达式@ {user.name}中,如果用户为空,则user.name将被分配其默认值(null)。如果您引用user.age,其中age是int,那么它将默认为0(→_→666)

集合

常见的集合:数组,列表,稀疏列表和映射可以使用[]运算符方便地访问。

<?xml version="1.0" encoding="utf-8"?>
<layout  xmlns:android="http://schemas.android.com/apk/res/android" >

    <data>
        <import type="android.util.SparseArray"/>
        <import type="java.util.List"/>
        <import type="java.util.Map"/>
        <import type="int" />
        <import type="java.lang.String" />

        <variable  name="sparseArray" type="SparseArray&lt;String&gt;" />
        <variable  name="list" type="List&lt;String&gt;" />
        <variable  name="map" type="Map&lt;String,String&gt;" />
        <variable  name="number" type="int" />
        <variable  name="str" type="String" />
    </data>

    <LinearLayout  android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">

        <TextView  android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="10dp" android:text="@{sparseArray[number]}"/>

        <TextView  android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="10dp" android:text="@{list[number]}"/>

        <TextView  android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="10dp" android:text="@{map[str]}"/>

        <TextView  android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="10dp" android:text="@{str}"/>

    </LinearLayout>

</layout>
 ActivityFourthBinding binding= DataBindingUtil.setContentView(this, R.layout.activity_fourth);

        SparseArray<String> sparseArray=new SparseArray<>();
        sparseArray.put(0,"QiDong");
        sparseArray.put(1,"LiuZhiPeng");

        List<String> list=new ArrayList<>();
        list.add("AAA");
        list.add("BBB");

        Map<String,String> map=new HashMap<>();
        map.put("A","GGG");
        map.put("B","VVV");

        binding.setList(list);
        binding.setMap(map);
        binding.setSparseArray(sparseArray);
        binding.setNumber(1);
        binding.setStr("A");

字符串常量

使用单引号把属性包起来,就可以很简单地在表达式中使用双引号:

<TextView  android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="10dp" android:text='@{map["A"]}'/>

也可以使用双引号来包围属性值。当这样做时,字符串文字应该使用’或返回引号(`)。

<TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{map[`B`]}"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{map['A']}"/>
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:padding="10dp"
            android:text="@{map[&quot;B&quot;]}"/>

事实证明,第二种方式无效。

资源

使用正常语法可以访问资源作为表达式的一部分

 <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="@{large?@dimen/large_dimen:@dimen/small_dimen}"
            android:text="@{map[`A`]}"/>

字符串格式化和复数形式可以这样实现:

<TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{@plurals/sample_plurals(number)}"
            />

看不懂→_→

当复数形式有多个参数时,应该这样写:

android:text="@{@plurals/numbers(num, num)}"

一些资源需要显示类型调用。
这里写图片描述

待续。。。。

上一篇:android 连按两次Back键退出 (单Activity多Fragment,保留根Fragment) 下一篇:Android基于高德地图实现搜索框的自动输入提示功能