Android之DataBinding的使用

一、DataBinding介绍
2015年谷歌I/O大会上介绍了一个框架DataBinding,DataBinding是一个数据绑定框架,以前我们在Activity里写很多的findViewById,很多人不想写这个于是用了一些注解框架,可是注解框架无论性能多好,效率总是要低于findViewById的,现在如果我们使用DataBinding,就可以抛弃findViewById。DataBinding主要解决了两个问题:

反射,注解(butterKnife)、DataBinding

  • 需要多次使用findViewById,损害了应用性能且令人厌烦
  • 更新UI数据需切换至UI线程,将数据分解映射到各个view比较麻烦

就让我们具体来看看怎么使用它吧。

二、DataBinding的基本使用

(1)DataBinding的导入

在应用的build.gradle文件中添加如下代码:

android {
    ...

    //导入dataBinding支持
    dataBinding{
        enabled true
    }



    ...
}

注:如果出现找不到databinding的library的话是因为跟gradle的版本不兼容,改成2.0.0

classpath 'com.android.tools.build:gradle:2.0.0'

(2)DataBinding基本使用包括以下内容:

● 单纯的摆脱findviewbyid
● 绑定基本数据类型及String
● 绑定Model数据
● 绑定事件
● 通过静态方法转换数据类型
● 通过运算符操作数据
● 自定义Binding的类名
● 绑定相同Model的操作
● model变量改变自动更新数据
● 绑定List/Map等集合数据
● Observable自动更新
● Databinding与include标签的结合
● DataBinding与RecyclerView的结合
● DataBinding和Fragment的使用
● DataBinding在Activity或Fragment中加载其他布局
● DataBinding实现自定义DataBindingAdapter

(3)功能使用

3.1单纯的摆脱findviewbyid

布局文件

<!--布局以layout作为根布局-->
<layout>
    <!--我们需要展示的布局-->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context="www.zhang.com.databinding.MainActivity">

        <Button
            android:id="@+id/main_tv1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="单纯的摆脱findviewbyid" />
    </LinearLayout>
</layout>

布局通过DataBindingUtils.setContentView()加载到代码中,而且会生成对应一个Binding对象,对象名是布局文件文称加上Binding后缀

MainActivity中代码

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding binding;//这个类是自动生成的

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //通过DataBInding加载布局都会对应的生成Binding对象,如ActivityMainBinding,对象名在布局文件名称后加上了一个后缀Binding
        binding = DataBindingUtil.setContentView(MainActivity.this, R.layout.activity_main);
        //通过binfding.id名称---就相当于获取了改控件对象了
        binding.mainTv1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                binding.mainTv1.setText("没有findviewbyid了");
            }
        });
    }
}

通过Binding对象.id名称,就相当于拿到了指定的布局中的id的控件了,使用起来和findviewbyid获取的控件是一样的

3.2绑定基本数据和String
布局文件

<?xml version="1.0" encoding="utf-8"?><!--布局以layout作为根布局-->
<layout>

    <data>
        <!--绑定基本数据类型及String-->
        <!--name:   和java代码中的对象是类似的,名字自定义-->
        <!--type:   和java代码中的类型是一致的-->
        <variable
            name="content"
            type="String" />

        <variable
            name="enabled"
            type="boolean" />
    </data>
    <!--我们需要展示的布局-->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <!--绑定基本数据类型及String的使用是通过   @{数据类型的对象}  通过对应数据类型的控制显示-->
        <Button
            android:id="@+id/main_tv2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:clickable="@{enabled}"
            android:text="@{content}" />
    </LinearLayout>
</layout>

在布局中是通过@{}来绑定数据的,{}中是布局中该控件属性对应的数据类型数据,同时还可以支持运算符运算和静态方法调用和转换,这个后面会介绍

MainActivity文件中

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //通过DataBInding加载布局都会对应的生成一个对象,如ActivityMainBinding,对象名在布局文件名称后加上了一个后缀Binding
        binding = DataBindingUtil.setContentView(MainActivity.this, R.layout.activity_main);

        //2.绑定基本数据类型及String
        binding.setContent("对String类型数据的绑定");//setContent就是给布局文件text属性绑定的content设置值
        binding.setEnabled(false);//设置enabled值为false
        //给控件设置点击事件,发现其实点击无效,因为在布局文件中给cilckable属性绑定了enabled,在代码中设置enabled值为false,所以点击事件无效
        binding.mainTv2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "我被点击了", Toast.LENGTH_SHORT).show();
            }
        });

    }
}

3.3绑定model数据

Model数据类型

public class User {
    private String text;

    public User(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}

后面会对model数据做更详细的讲解,这里先让大家知道一下用法
布局文件

<?xml version="1.0" encoding="utf-8"?><!--布局以layout作为根布局-->
<layout>

    <data>
        <!--绑定Model数据2中形式,一中是导入该类型的,一种指定类型的全称,和java一样-->
        <!--  方式一 -->
        <variable
            name="user"
            type="www.zhang.com.databinding.User" />
        <!--  方式二 -->
        <!--<import type="www.zhang.com.databinding.User" />-->
        <!--<variable-->
            <!--name="user"-->
            <!--type="User" />-->

    </data>
    <!--我们需要展示的布局-->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <Button
            android:id="@+id/main_btn3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{user.text}" /><!--这里user.text相当于user.getText()-->
    </LinearLayout>
</layout>

MainActivity中代码:

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //通过DataBInding加载布局都会对应的生成一个对象,如ActivityMainBinding,对象名在布局文件名称后加上了一个后缀Binding
        binding = DataBindingUtil.setContentView(MainActivity.this, R.layout.activity_main);

        //3.绑定model对象数据
        User user = new User("绑定Model数据类型");
        binding.setUser(user);//或者 binding.setVariable(BR.user,user);
    }
}


binding设置数据有2中方式:
1.binding.setUser(user)
2.binding.setVariable(BR.user,user)–采用BR指定

3.4事件的绑定
布局文件

<?xml version="1.0" encoding="utf-8"?><!--布局以layout作为根布局-->
<layout>

    <data>
        <variable
            name="event"
            type="www.zhang.com.databinding.model.EventListener" />

        <variable
            name="title1"
            type="String" />

        <variable
            name="title2"
            type="String" />

        <variable
            name="title3"
            type="String" />

        <variable
            name="title4"
            type="String" />

    </data>
    <!--我们需要展示的布局-->
        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
            <Button
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="@{event.click1}"
                android:text="@{title1}" />

            <Button
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="@{event::click2}"
                android:text="@{title2}" />
            <!--android:onClick="@{event::click2}"  编译报错没关系,可以运行的-->
            <Button
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:onClick="@{()->event.cilck3(title4)}"
                android:text="@{title3}" />

        </LinearLayout>
</layout>

事件有多种写法

1.android:onClick="@{event.click1}"
2.android:onClick="@{event::click2}"
3.android:onClick="@{()->event.cilck3(title4)}"

[注]:()->event.cilck3(title4)是lambda表达式写法,
也可以写成:(view)->event.cilck3(title4),前面(view)表示onClick方法的传递的参数,
如果event.click3()方法中不需要用到view参数,可以将view省略。

当然event.click1也可以写成(view)->event.click1(view),将onClick(View view)的view参数传递给event.click1(view)方法。
大概就这意思,以下是伪代码

onclick(View view){
    event.click1(view)
}

EventListener接口

public interface EventListener{
    public void click1(View v);
    public void click2(View v);
    public void cilck3(String s);
}

MainActivity
public class MainActivity extends AppCompatActivity{

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //通过DataBInding加载布局都会对应的生成一个对象,如ActivityMainBinding,对象名在布局文件名称后加上了一个后缀Binding
        binding = DataBindingUtil.setContentView(MainActivity.this, R.layout.activity_main);

        binding.setTitle1("事件绑定1");
        binding.setTitle2("事件绑定2");
        binding.setTitle3("事件绑定3");
        binding.setTitle4("change ok");

        binding.setEvent(new EventListener() {
            @Override
            public void click1(View v) {
                binding.setTitle1("事件1方法调用");
            }

            @Override
            public void click2(View v) {
                binding.setTitle2("事件2方法调用");
            }

            @Override
            public void cilck3(String s) {
                binding.setTitle3(s);
            }
        });
    }
}

3.5通过静态方法转化数据类型
布局文件

<?xml version="1.0" encoding="utf-8"?><!--布局以layout作为根布局-->
<layout>

    <data>
     <variable
            name="user"
            type="www.zhang.com.databinding.User" />

        <!--调用静态方法,需要先导入需要的包    当然java中的lang包可以不用导包-->
        <import type="www.zhang.com.databinding.Utils" />

    </data>
    <!--我们需要展示的布局-->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context="www.zhang.com.databinding.MainActivity">

        <Button
            android:id="@+id/main_btn5"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{Utils.getName(user)}" /><!--就和java中写代码一样,只要符合数据类型就好-->
    </LinearLayout>
</layout>

静态方法的类(当然也可以使用Java自带API)

public class Utils {
    public static String getName(Object o) {
        return o.getClass().getName();
    }
}

3.6通过运算符操作数据
布局文件

<?xml version="1.0" encoding="utf-8"?><!--布局以layout作为根布局-->
<layout>

    <data>

        <variable
            name="user2"
            type="www.zhang.com.databinding.model.User" />

    </data>
    <!--我们需要展示的布局-->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context="www.zhang.com.databinding.MainActivity">

        <Button
            android:id="@+id/main_btn5"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:paddingLeft="@{user2.state ? @dimen/largepadding : (int)@dimen/smallpadding}"
            android:text="@{user2.content ?? @string/app_name}" />
        <!-- android:text="@{user2.content ?? @string/app_name}"
         等价于
         android:text="@{user2.content!=null? user2.content : @string/app_name}"-->

        <Button
            android:id="@+id/main_btn6"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{`Hello World`+ @string/app_name}" /><!-- ``字符包裹的表示字符串,可用作拼接字符串 -->

    </LinearLayout>
</layout>
public class User {
    private String content;
    private boolean state;

    public User(String content, boolean state) {
        this.content = content;
        this.state = state;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public boolean isState() {
        return state;
    }

    public void setState(boolean state) {
        this.state = state;
    }

}

MainActivity中

package www.zhang.com.databinding;

import android.databinding.DataBindingUtil;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import www.zhang.com.databinding.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //通过DataBInding加载布局都会对应的生成一个对象,如ActivityMainBinding,对象名在布局文件名称后加上了一个后缀Binding
        binding = DataBindingUtil.setContentView(MainActivity.this, R.layout.activity_main);

        //运算符操作
        www.zhang.com.databinding.model.User user2 = new www.zhang.com.databinding.model.User("通过运算符操作数据", true);
//        user2 = new www.zhang.com.databinding.model.User(null, false);    改变数据作为测试DataBinding是否有效
        binding.setUser2(user2);

    }
}

DataBinding支持的表达式有:
数学表达式: + - / * %
字符串拼接 +
逻辑表达式 && ||
位操作符 & | ^
一元操作符 + - ! ~
位移操作符 >> >>> <<
比较操作符 == > < >= <=
instanceof
分组操作符 ()
字面量 - character, String, numeric, null
强转、方法调用
字段访问
数组访问 []
三元操作符 ?:
聚合判断(Null Coalescing Operator)语法 ‘??’

3.7自定义Binding的类名
data标签有个class属性,它可以用来对Binding对象重新命名(一般是以布局文件名加上Binding后缀作为该Binding类名)
文件目录如下

自定义类名有3中方式
1. 通过指定全类名的方式

<data class="www.zhang.com.databinding.activity.Item">
    ...
</data>
import www.zhang.com.databinding.activity.Item;

Item binding = DataBindingUtil.setContentView(FiveActivity.this, R.layout.activity_five);
  1. 直接生成在项目的包目录下
<data class=".Item">
    ...
</data>
import www.zhang.com.databinding.Item;

Item binding = DataBindingUtil.setContentView(FiveActivity.this, R.layout.activity_five);
  1. 如果FiveActivity直接在包下与方式二相同,如果FiveActivity在包的子目录下,则Binding生成的目录如下
<data class="Item">
    ...
</data>
import www.zhang.com.databinding.databinding.Item;

Item binding =DataBindingUtil.setContentView(FiveActivity.this, R.layout.activity_five);

3.8绑定相同model的操作
绑定相同model我的理解有2种,一种是同类的2个对象,另一种是不同类的2个类,但类名相同,具体看代码:
● 第一种,同一个类的2个对象,只需对象名不同就可以
布局文件

<?xml version="1.0" encoding="utf-8"?><!--布局以layout作为根布局-->
<layout>

    <data>

        <import type="www.zhang.com.databinding.User" />

        <variable
        name="user3"
        type="User" />

        <variable
        name="user4"
        type="User" />

    </data>
    <!--我们需要展示的布局-->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context="www.zhang.com.databinding.MainActivity">

        <Button
            android:id="@+id/main_btn7"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{user3.text}" />

        <Button
            android:id="@+id/main_btn8"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{user4.text}" />

    </LinearLayout>
</layout>

User类

public class User {
    private String text;

    public User(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}

MainActivity中

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //通过DataBInding加载布局都会对应的生成一个对象,如ActivityMainBinding,对象名在布局文件名称后加上了一个后缀Binding
        binding = DataBindingUtil.setContentView(MainActivity.this, R.layout.activity_main);


        User user3  = new User("相同类,不同对象1");
        binding.setUser3(user3);

        User user4 = new User("相同类,不同对象2");
        binding.setUser4(user4);

    }
}

● 第二种不同的2个类,对象名相同
布局文件


<?xml version="1.0" encoding="utf-8"?><!--布局以layout作为根布局-->
<layout>

    <data>

        <import type="www.zhang.com.databinding.User" />

        <variable
            name="user4"
            type="User" />

<!--因为type="User"都为User类,会导致不知道到那个包,所以可以通过alias属性重命名type的类型,但实际上alias被指定的那个类型(如:www.zhang.com.databinding.model.User)-->
        <import type="www.zhang.com.databinding.model.User" alias="Model"/>

        <variable
            name="user5"
            type="Model"  />

    </data>
    <!--我们需要展示的布局-->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context="www.zhang.com.databinding.MainActivity">

        <Button
            android:id="@+id/main_btn8"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{user4.text}" />

        <Button
            android:id="@+id/main_btn9"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{user5.content}" />

    </LinearLayout>
</layout>

因为2个地方都有type=”User”都为User类,会导致不知道导入哪个包,所以可以通过alias属性重命名type的类型,但实际上是alias被指定的那个类型(如:www.zhang.com.databinding.model.User)
2个不同的User类

package www.zhang.com.databinding;

public class User {
    private String text;

    public User(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}
package www.zhang.com.databinding.model;

public class User {
    private String content;
    private boolean state;

    public User(String content, boolean state) {
        this.content = content;
        this.state = state;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public boolean isState() {
        return state;
    }

    public void setState(boolean state) {
        this.state = state;
    }

}

MainActivity

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //通过DataBInding加载布局都会对应的生成一个对象,如ActivityMainBinding,对象名在布局文件名称后加上了一个后缀Binding
        binding = DataBindingUtil.setContentView(MainActivity.this, R.layout.activity_main);

        User user4 = new User("相同类,不同对象2");
        binding.setUser4(user4);

        www.zhang.com.databinding.model.User user5 = new www.zhang.com.databinding.model.User("类名相同,但不是相同的2个类",false);
        binding.setUser5(user5);

    }
}

3.9model变量改变自动更新数据

前面讲了DataBinding对基本数据类型及Model的使用以及运算符等操作,但这些基本是静态的去设置数据,现在我们来看看 如何在数据改变时,不手动设置,让其自动改变。

布局文件

<?xml version="1.0" encoding="utf-8"?><!--布局以layout作为根布局-->
<layout>

    <data>

        <import type="www.zhang.com.databinding.model.Person" />

        <variable
            name="person"
            type="Person" />
    </data>
    <!--我们需要展示的布局-->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:text="@{`firstName:`+person.firstName}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:text="@{`lastName:`+person.lastName}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:text="@{`age:`+person.age}" />

        <Button
            android:id="@+id/second_btn1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="改变数据" />

    </LinearLayout>
</layout>

Person类

package www.zhang.com.databinding.model;

import android.databinding.BaseObservable;
import android.databinding.Bindable;

import www.zhang.com.databinding.BR;

public class Person extends BaseObservable {
    private String firstName;
    private String lastName;
    private int age;

    public Person(String firstName, String lastName, int age) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }

    @Bindable
    public String getFirstName() {
        return firstName;
    }

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

    @Bindable
    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
        notifyPropertyChanged(BR.lastName);
    }

    @Bindable
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
        notifyPropertyChanged(BR.age);
    }
}

Model类继承BaseObservable,BaseObservable实现Android.databinding.Observable接口,Observable接口可以允许附加一个监听器到model对象以便监听对象上的所有属性的变化。
Observable接口有一个机制来添加和删除监听器,但通知与否由开发人员管理。为了使开发更容易,BaseObservable实现了监听器注册机制。DataBinding实现类依然负责通知当属性改变时。这是通过指定一个Bindable注解给getter以及setter内通知来完成的。
notifyPropertyChanged(BR.参数名)通知更新这一个参数,需要与@Bindable注解配合使用。notifyChange()通知更新所有参数,可以不用和@Bindable注解配合使用
SecondActivity

public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivitySecondBinding binding = DataBindingUtil.setContentView(SecondActivity.this, R.layout.activity_second);

        final Person person = new Person("zhang","san",38);
        binding.setPerson(person);

        binding.secondBtn1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                person.setFirstName("li");
                person.setLastName("si");
                person.setAge(40);
            }
        });
    }
}

3.10绑定List/Map等集合数据
布局文件

<?xml version="1.0" encoding="utf-8"?><!--布局以layout作为根布局-->
<layout>

    <data>

        <import type="java.util.ArrayList" />

        <import type="java.lang.String" />

        <variable
            name="list"
            type="ArrayList&lt;String>" />
        <!--泛型的支持会在编译时期报红线,但是是可以直接运行的
        但是需要通过转义字符才行,如:&lt;数据类型> 或者&lt;数据类型&gt;-->

        <import type="java.util.Map" />

        <variable
            name="map"
            type="Map&lt;String,String&gt;" />

        <variable
            name="arrays"
            type="String[]" />
    </data>
    <!--我们需要展示的布局-->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:text="@{list[0]}" />
        <!--List集合既可以和数组一样通过索引获取值list[index]方式,也可以调用API-->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:text="@{list.get(1)}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:text="@{map[`name`]}" />
        <!--Map集合既可以通过map[key]的方式,也可以通过调用API-->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:text="@{map.get(`age`)}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:text="@{arrays[0]}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:text="@{arrays[1]}" />

    </LinearLayout>
</layout>

泛型的支持会在编译时期报红线,是可以直接运行的,但是需要通过转义字符才行,如:\<数据类型> 或者\<数据类型>
ThirdActivity

public class ThirdActivity extends AppCompatActivity {

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

        ActivityThirdBinding binding = DataBindingUtil.setContentView(ThirdActivity.this, R.layout.activity_third);
        ArrayList<String> list = new ArrayList<>();
        list.add("first");
        list.add("second");
        binding.setList(list);

        Map<String, String> map = new HashMap<>();
        map.put("name", "zhangsan");
        map.put("age", "40");
        binding.setMap(map);

        String[] arrays = {"lisi", "laowang"};
        binding.setArrays(arrays);
    }
}

3.11 Observable数据改变自动更新
Observable是一个接口,它的子类有BaseObservable,ObservableField,ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable,ObservableArrayList,ObservableArrayMap

<?xml version="1.0" encoding="utf-8"?><!--布局以layout作为根布局-->
<layout>

    <data>
        <import type="www.zhang.com.databinding.model.Animal"/>
        <variable
            name="animal"
            type="Animal"/>
        <variable
            name="list"
            type="android.databinding.ObservableArrayList&lt;String&gt;"/>

        <variable
            name="map"
            type="android.databinding.ObservableArrayMap&lt;String,String&gt;"/>
    </data>
    <!--我们需要展示的布局-->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:text="@{animal.field}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:text="@{String.valueOf(animal.age)}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:text="@{list[0]}" />
        <!--Map集合既可以通过map[key]的方式,也可以通过调用API-->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:text="@{list[1]}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:text="@{map[`name`]}" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="56dp"
            android:text="@{map[`age`]}" />

        <Button
            android:id="@+id/four_btn"
            android:layout_width="match_parent"
            android:text="改变数据"
            android:layout_height="wrap_content" />

    </LinearLayout>
</layout>

Animal类
public class Animal {
public final ObservableField field = new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
FourActivity
package www.zhang.com.databinding;

public class FourActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityFourBinding binding = DataBindingUtil.setContentView(FourActivity.this, R.layout.activity_four);
final Animal animal = new Animal();

    animal.field.set("cat");
    animal.age.set(2);
    binding.setAnimal(animal);

    final ObservableArrayList<String> list = new ObservableArrayList<>();
    list.add("dog");
    list.add("mouse");
    binding.setList(list);

    final ObservableArrayMap<String, String> map = new ObservableArrayMap<>();
    map.put("name","Tom");
    map.put("age","4");
    binding.setMap(map);

    binding.fourBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            animal.field.set("dog");
            animal.age.set(4);
            list.set(0,"cat");
            list.set(1,"dog");
            map.put("name","Sam");
            map.put("age","5");
        }
    });

}

当Animal属性数据改变,list/map集合数据改变,会自动更新数据,我们不需要自己手动设置,省去了一个操作,让我们更专注于业务逻辑。

3.12 Databinding与include标签的结合
布局文件
activity_five.xml

<?xml version="1.0" encoding="utf-8"?><!--布局以layout作为根布局-->
<layout>

    <data >
        <import type="www.zhang.com.databinding.model.Content"/>
        <variable
            name="con"
            type="Content"/>
    </data>
    <!--我们需要展示的布局-->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <include
            android:id="@+id/toolbar"
            layout="@layout/toolbar"
            android:layout_height="56dp"
            android:layout_width="match_parent"
            bind:content="@{con}" />
 <!--通过命名空间将写有toolbar的xml文件中定义的content对象作为属性绑定con对象,这2个对象是同一个类-->
            <TextView
                android:text="@string/app_name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
    </LinearLayout>
</layout>

toolbar.xml

<?xml version="1.0" encoding="utf-8"?>
<layout >
    <data>
        <import type="www.zhang.com.databinding.model.Content"/>
      <variable
          name="content"
          type="Content"/>
    </data>

<android.support.v7.widget.Toolbar
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/toolbar"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_height="56dp"
    android:layout_width="match_parent"
    app:title="@{content.title}"
    app:subtitle="@{content.subTitle}"
    android:background="@color/colorPrimary"
    app:titleTextColor="@android:color/white"
    app:subtitleTextColor="@android:color/white" />
</layout>

在activity_five.xml中的include属性中定义了一个id,同时又在toolbar.xml中的Toolbar标签中又定义了一个id,其作用是通过binding.toolbar.toolbar等同于Toolbar控件,可以方便做一些操作等
FiveActivity中

public class FiveActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);

        ActivityFiveBinding binding =DataBindingUtil.setContentView(FiveActivity.this, R.layout.activity_five);

        Content con = new Content("Title","SubTitle");
        binding.setCon(con);

//        binding.toolbar.setContent(con);  //这个测试没有效果,不会显示toolbar的title/subTitle
//        binding.toolbar.toolbar.setTitle(""); 
//        binding.toolbar.toolbar.setSubtitle("");

        //下面的代码也可以通过DataBinding绑定数据
        binding.toolbar.toolbar.setNavigationIcon(R.mipmap.ic_launcher);
        setSupportActionBar(binding.toolbar.toolbar);
        binding.toolbar.toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }
}

Content

public class Content extends BaseObservable {
    private String title;
    private String subTitle;

    public Content(String title, String subTitle) {
        this.title = title;
        this.subTitle = subTitle;
    }

    @Bindable public String getSubTitle() {
        return subTitle;
    }

    public void setSubTitle(String subTitle) {
        this.subTitle = subTitle;
        notifyPropertyChanged(BR.subTitle);
    }

    @Bindable public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
        notifyPropertyChanged(BR.title);
    }
}

3.13 DataBinding与RecyclerView的结合
布局文件
activity_five.xml

<?xml version="1.0" encoding="utf-8"?><!--布局以layout作为根布局-->
<layout>

    <data >
        <import type="www.zhang.com.databinding.model.Content"/>
        <variable
            name="con"
            type="Content"/>
    </data>
    <!--我们需要展示的布局-->
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:bind="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <include
            android:id="@+id/toolbar"
            layout="@layout/toolbar"
            android:layout_height="56dp"
            android:layout_width="match_parent"
            bind:content="@{con}" />
        <!--通过命名空间将写有toolbar的xml文件中定义的content对象作为属性绑定con对象,这2个对象是同一个类-->
            <android.support.v7.widget.RecyclerView
                android:id="@+id/recycler"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
    </LinearLayout>
</layout>

recycler_item.xml

<?xml version="1.0" encoding="utf-8"?>
<layout>
    <data>
        <variable
            name="str"
            type="String"/>
    </data>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:orientation="vertical">

        <TextView
            android:text="@{str}"
            android:gravity="center_vertical"
            android:textColor="@android:color/black"
            android:textSize="16sp"
            android:layout_width="match_parent"
            android:layout_height="48dp" />
    </LinearLayout>
</layout>

FiveActivity

public class FiveActivity extends AppCompatActivity {

    private ActivityFiveBinding binding;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);

        binding = DataBindingUtil.setContentView(FiveActivity.this, R.layout.activity_five);

        initToolbar();
        initRecyclerView();
    }

    private void initRecyclerView() {
        LinearLayoutManager manager = new LinearLayoutManager(FiveActivity.this);
        binding.recycler.setLayoutManager(manager);
        binding.recycler.setHasFixedSize(true);
        MyAdapter adapter = new MyAdapter(getApplicationContext());
        binding.recycler.setAdapter(adapter);
    }

    private void initToolbar() {
        Content con = new Content("Title","SubTitle");
        binding.setCon(con);

//        binding.toolbar.setContent(con);  //这个测试没有效果,不会显示toolbar的title/subTitle
//        binding.toolbar.toolbar.setTitle("");
//        binding.toolbar.toolbar.setSubtitle("");

        //下面的代码也可以通过DataBinding绑定数据
        binding.toolbar.toolbar.setNavigationIcon(R.mipmap.ic_launcher);
        setSupportActionBar(binding.toolbar.toolbar);
        binding.toolbar.toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
    }
}

MyAdapter

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

    private Context mContext;
    String[] datas;

    public MyAdapter(Context context) {
        mContext = context;
        datas = context.getResources().getStringArray(R.array.item_list);
    }

    @Override
    public MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        RecyclerItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(mContext), R.layout.recycler_item, parent, false);
        return new MyViewHolder(binding);
    }

    @Override
    public void onBindViewHolder(MyAdapter.MyViewHolder holder, int position) {
        String name = datas[position];
        holder.getBinding().setVariable(www.zhang.com.databinding.BR.str,name);
        //holder.getBinding().setStr(name); //两者都可以

        //executePendingBindings()方法说明
        // When a variable or observable changes, the binding will be scheduled to change before the next frame. 
        // There are times, however, when binding must be executed immediately. 
        // To force execution, use the executePendingBindings() method.
        holder.getBinding().executePendingBindings();//此方法必须执行在UI线程,当绑定的数据修改时更新视图(不知道翻译的准不准)
    }

    @Override
    public int getItemCount() {
        return datas.length;
    }

    public class MyViewHolder extends RecyclerView.ViewHolder {
        private RecyclerItemBinding binding;

        public MyViewHolder(ViewDataBinding binding) {
            super(binding.getRoot());
            this.binding = (RecyclerItemBinding) binding;
        }

        public RecyclerItemBinding getBinding() {
            return binding;
        }

        public void setBinding(RecyclerItemBinding binding) {
            this.binding = binding;
        }
    }
}

3.14 DataBinding在Fragment中使用
DataBinding在Fragment中使用和Activity中使用大体相同不同的是获取对象要在
DataBindingUtil.inflate(inflater, R.layout.homepage_fragment, container, false);

3.15 DataBinding在Activity或Fragment中加载其他布局

此处以PopupWindow 为例,其他如Dialog等类同;

ItemMapInfoBinding infoBinding = ItemMapInfoBinding.inflate(getLayoutInflater());
View popupView = infoBinding.getRoot();
PopupWindow mPopupWindow = new PopupWindow(mContext);
mPopupWindow.setContentView(popupView);

3.16 重新BindingAdapter来实现动画等非点击事件

安卓上新的binding framework可以轻松实现视图根据model的改变而改变。你只需要让model能被观测,然后framework会做好其余的事情。比如,你可以通过下面的代码来实现一个加载提示的显示和隐藏:

@BindingAdapter("fadeVisible")
public static void setFadeVisible(final View view, boolean visible) {
    view.animate().cancel();

    if (visible) {
        view.setVisibility(View.VISIBLE);
        view.setAlpha(0);
        view.animate().alpha(1).setListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                view.setAlpha(1);
            }
        });
    } else {
        view.animate().alpha(0).setListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                view.setAlpha(1);
                view.setVisibility(View.GONE);
            }
        });
    }
}
<ProgressBar
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    app:fadeVisible="@{model.loading}"/>

但是,这种方式有个问题,每次值发生改变的时候都会产生动画效果,即使是在第一次绑定的时候。比如,你会在旋转屏幕的时候也看到淡入淡出。我们需要一种能判断是否是第一次绑定的办法。
可以利用view的tag不被保持的特点,如果没有tag,则这是第一次绑定,我们设置tag而不运行动画:

@BindingAdapter("fadeVisible")
public static void setFadeVisible(final View view, boolean visible) {
    if (view.getTag() == null) {
        view.setTag(true);
        view.setVisibility(visible ? View.VISIBLE : View.GONE);
    } else {
        view.animate().cancel();

        if (visible) {
            view.setVisibility(View.VISIBLE);            view.setAlpha(0);
            view.animate().alpha(1).setListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    view.setAlpha(1);
                }
            });
        } else {
            view.animate().alpha(0).setListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    view.setAlpha(1);
                    view.setVisibility(View.GONE);
                }
            });
       }
   }
}

嗯,就是这样,加载提示便会很好的根据model的变化正确的显示淡入淡出了。

发布了67 篇原创文章 · 获赞 11 · 访问量 4万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 游动-白 设计师: 上身试试

分享到微信朋友圈

×

扫一扫,手机浏览