Data Binding使用详解(一)

本文介绍了Data Binding的基本用法,包括一些基本概念、事件的处理及观察者模式的简单应用。

简介

Data Binding有很好的灵活性和兼容性,向后兼容至Android 2.1(API级别7+)。

为了使用Data Binding,需要使用Gradle 1.5.0-alpha1+及Android Studio 1.3+。

构建环境

首先需要在app module下的build.gradle文件下添加data binding的支持:

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

注意:如果你的app module依赖的类库使用了data binding,那你也得在app module中配置data binding。

基础功能

为了使用Data Binding,首先需要修改布局文件,布局文件的根元素需要使用元素:




    
        
    

    

        

        

    

当布局文件的根元素使用元素后,Android Studio就会根据布局文件名自动生成一个Binding类,否则不会作处理。默认情况下生成的Binding类名是根据布局文件名称生成的,大写字母开头,移除下划线并大写后面的字母最后加上“Binding”后缀。这个类会放在module包下的databinding包下。例如,布局文件contact_item.xml会生成ContactItemBinding。如果module包名为 com.example.my.app那它会放在com.example.my.app.databinding包下。这个类控制着布局文件中的所有binding,从布局属性(如:variable变量)到布局View及设置绑定表达式的值。

对于布局中每个设置ID的View会在Binding类中生成对应的public final域,生成规则为View的ID名首字母小写,移除下划线并大写后面的字母。例如,View ID tv_hello会生成tvHello。

对于每个被描述的变量生成的binding类会对应有setter和getter。变量会使用Java默认值直到setter被调用——引用类型为null,int为0,boolean为false,等等。当为了不同的配置(例:横屏或竖屏)有不同的布局文件,这些变量会被合并。这些布局文件中定义的变量不能有冲突。

我们可以通过data元素class属性修改Binding类名或放在不同包下。例如:

 // 
    ...

写在布局文件属性中的表达式使用“@{}” 语法。TextView的文本被设置为了user中的name属性:

 

然后来看一下User类:

public class User {
    public final String name;
    public final String age;

    public User(String name, String age) {
        this.name = name;
        this.age = age;
    }
}

这个对象的类型数据从不会被改变。在应用程序中这是很常见的,数据只读一次之后不会被改变。也可以使用一个JavaBeans对象:

public class User {
    public final String name;
    public final String age;

    public User(String name, String age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public String getAge() {
        return age;
    }
}

这两个类对Data Binding来说是相等的。TextView中的text属性user.name值会访问前面类的name字段和后面类的getName方法。另外如果有name()方法也会访问这个。

逻辑缜密的同学已经考虑到了直接使用user.name若user为null会不会报空指针异常,生成的Binding类会检查null值并且会避免空指针异常。例如,在@{user.name}表达式中,如果user为null,user.name会分配它的默认值(null)。如果引用的是user.age,age是一个int值,那它的默认值为0。

在Activity的onCreate中,我们需要将setContentView换为DataBinding的方式,并且我们需要调用Binding类的setter为元素下的每个变量设置相应的对象:

ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setUser(new User("我是姓名","我是年龄"));

没错,就是这么简单,快来看看效果吧:

Data Binding使用详解(一)_第1张图片
example.png

你也可以使用这种方法得到view:

MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
View view = binding.getRoot();//获取对应的View

如果你的ListView或RecyclerView adapter的item使用了data binding,你会更喜欢这种方法:

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

前面提到Binding类可以控制所有布局View,我们试着给TextView加一个ID:android:id="@+id/tv_hello",然后在Activity中直接操作TextView,省去了findViewById操作:

binding.tvHello.setText("Hi,I'm from DataBinding.");

当我们使用元素时,例如:


view_toolbar.xml


    


我们可以使用类似方式访问view:

setSupportActionBar(binding.includeToolbar.toolbar);

事件处理

Data Binding允许你写表达式处理view分发的事件(如:onClick)。事件属性名称是由监听方法的名称所管理。例如, View.OnLongClickListener 有一个 onLongClick()方法,因此这个事件的属性是android:onLongClick(这时AS会提示“Unknown attribute android:onLongClick more”,然而这并不影响运行)。有两种方法可以处理一个事件:

方法引用(Method References)

事件可以直接绑定到一个处理的方法,就像android:onClick可以在Activity中指定一个方法。它和View#onClick属性比较最主要的优势在于表达式在编译的时候处理,因此如果方法不存在或它的签名不正确,编译会出现错误。

方法引用和监听绑定之间最主要的区别是真正的监听实现是当数据绑定时创建,不是当事件触发时。如果你想要当事件发生时计算表达式,那你应该使用监听绑定。

这里我们实现点击事件的处理,首先需要一个方法的处理类,包含一个点击事件的处理方法,如下:

public class MainClickHandlers {
    public void onClickName(View v) {
    }
}

然后我们需要在布局文件中引入这个类,并为TextView指定点击监听:



   
       
       
   
   
       
   

可以看到我们指定点击事件使用的是"@{handlers::onClickName}"语法,当然你也可以使用"@{handlers.onClickName}"。

注意表达式中方法的签名必须和监听对象中的方法签名完全匹配,否则会编译失败。

最后我们同样需要使用Binding类的setter为每个变量设置相应对象:

binding.setHandlers(new MainClickHandlers());

这样我们就完成了使用方法引用的方式实现了View的点击事件处理。

监听绑定(Listener Bindings)

这个是当事件发生时运行lambda表达式。这个和方法引用类似,但这个可以运行任意的data binding表达式。这个特性需要Gradle 2.0+的支持。

在方法引用中,事件监听的参数和方法的参数必须匹配。在监听绑定中,只需要你的返回值匹配监听期望返回的值即可(除非返回值为void)。

首先和方法引用一样,我们需要在一个类中添加一个方法:

public class MainPresenter implements MainContract.Presenter {
    public void onSaveClick(User user) {//save the user
    }
}

然后在布局文件中引用这个类并调用其中的方法:


  
      
          
          
      
      
          
      
  

最后我们同样需要使用Binding类的setter为每个变量设置相应对象:

binding.setPresenter(new MainPresenter());

这样我们就完成了使用监听绑定的方式实现了View的点击事件的处理。使用lambda表达式代表的监听只能作为表达式的顶级元素。当表达式中使用了回调,Data Binding会自动创建必要的监听并且会为事件进行注册。

表达式

在上面的例子中我们没有定义view参数传递到 onClick(android.view.View)。监听绑定为监听参数提供了两个选择:你可以忽略方法的所有参数或方法名称。如果你更喜欢加上参数名称,上面的表达式也可以这样写:

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

如果你想要使用参数名称,你也可以这样写:

public class MainPresenter implements MainContract.Presenter {
    public void onClick(View view) {
    }
}
android:onClick="@{(view) -> presenter.onClick(view)}"

lambda表达式也支持使用多个参数,例如:

public class Presenter {
    public void onCompletedChanged(User user, boolean completed){}
}

如果监听的事件返回的是非void值,那么你的表达式也必须返回相同的类型。例如,如果你想监听long click事件,你的表达式应该返回boolean。

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

如果由于对象为null不能计算表达式,Data Binding会返回Java类型的默认值。例如,引用类型为null,int为0,boolean为false,等等。

表达式也支持三元运算符,例如:

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

导入

在data元素中可以使用零个或多个import元素。这允许你在布局文件中可以很方便的引用类,就像在Java中。


    

现在,View可以用在绑定表达式中,我们可以根据boolean值变量的值决定View是否显示:


当导入的类名有冲突时,可以使用“alias”设置别名,这样我们就可以使用Vista代表我们自己定义的类了:



变量和表达式可以引用导入的类型:


    
    
    
    




注意:数组和泛型,比如Observable类,当没有错误的时候可能会显示错误。

在表达式中也可以使用导入的类型的静态域和静态方法:


    
    

变量

在data元素中可以使用零个或多个variable元素。在binding表达式中可以使用生成的特殊的变量名为“context”。“context”的值是从根View's getContext()获取的Context。“context”变量会被声明的同名变量重写。在编译的时候会检查变量类型,因此如果一个变量实现了Observable或 observable collection,应在类型中得到反映。如果变量是一个基类或没有实现Observable的接口,那么变量不会被观察。

因此,当我们需要使用Context时,上面的例子也可以改成这样:

public class Presenter {
    public void onCompletedChanged(Context ct,boolean completed){
      //use context show toast
    }
}

databinding包中给我们提供了很多实现Observable接口的类,使用下面的类型时,当数据更新时会自动更新UI内容:

ObservableArrayList
ObservableArrayMap
ObservableBoolean
ObservableByte
ObservableChar
ObservableDouble
ObservableField
ObservableFloat
ObservableInt
ObservableLong
ObservableParcelable
ObservableShort

我们来定义一个ObservableBoolean类型的变量:

public class User {
    public final String name;
    public final String age;
    public ObservableBoolean isComplete;

    public User(String name, String age, boolean isComplete) {
        this.name = name;
        this.age = age;
        this.isComplete = new ObservableBoolean(isComplete);
    }
}

我们根据CheckBox的事件中修改变量的值:

public class Presenter {
    public void onCompletedChanged(User user, boolean completed){
      user.isComplete.set(completed);
    }
}

在布局文件中通过这个变量的值来确定View是否显示:


运行Demo,我们可以看到View随着变量值的改变显示或隐藏(竟然不支持gif):

Data Binding使用详解(一)_第2张图片
example.png

避免复杂的监听

监听表达式是非常强大的并且可以让你的代码变得很易于阅读。从另一方面来说,包含复杂表达式的布局会变得很难阅读和维护。这些表达式应该尽可能简单地传递数据从UI到回调方法。你应该在回调方法中实现业务逻辑从监听表达式调用。
存在一些专门点击事件的处理者并且为了避免冲突它们需要一个和android:onClick不一样的属性。为了避免这样冲突创建了下列属性:

Class Listener Setter Attribute
SearchView setOnSearchClickListener(View.OnClickListener) android:onSearchClick
ZoomControls setOnZoomInClickListener(View.OnClickListener) android:onZoomIn
ZoomControls setOnZoomOutClickListener(View.OnClickListener) android:onZoomOut

Includes

通过使用应用的命名空间和变量名,变量可以从containing的布局传递到included的布局binding中:



   
       
   
   
       
       
   

Data binding不支持include作为merge的直接子元素。例如,下面的布局是不支持的:



   
       
   
   
       
       
   

完整示例代码可以从这里找到:https://github.com/yuweiguocn/DataBindingDemo

参考链接:https://developer.android.com/topic/libraries/data-binding/index.html

你可能感兴趣的:(Data Binding使用详解(一))