Android之Databinding学习笔记

来源:互联网 发布:遗传算法简单实例 编辑:程序博客网 时间:2024/05/18 03:50

Android之DataBinding学习笔记

简介

Data binding 在2015年7月发布的Android Studio v1.3.0 版本上引入,在2016年4月Android Studio v2.0.0 上正式支持。目前为止,Data Binding 已经支持双向绑定了。

Databinding 是一个实现数据和UI绑定的框架,是一个实现 MVVM 模式的工具,有了 Data Binding,在Android中也可以很方便的实现MVVM开发模式。

Data Binding 是一个support库,最低支持到Android 2.1(API Level 7+)。

Data Binding 之前,我们不可避免地要编写大量的重复的代码,如 findViewById()、setText(),setVisibility(),setEnabled() 或 setOnClickListener() 等,通过 Data Binding , 我们可以通过声明式布局以精简的代码来绑定应用程序逻辑和布局,这样就不用编写大量的重复的代码了。

studio环境构建

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

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

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

Data Binding 布局文件 - (View)

Data binding 的布局文件与传统布局文件有一点不同。它以一个 layout 标签作为根节点,里面是 data 标签与 view 标签。view 标签的内容就是不使用 Data Binding 时的普通布局文件内容。以下是一个例子:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools">    <data>        <variable            name="entry"            type="cn.itrealman.databindingdemo.Entry"/>    </data>    <RelativeLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        tools:context="cn.itrealman.databindingdemo.MainActivity">        <TextView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_centerInParent="true"            android:textSize="20sp"            android:text="@{entry.text}"            android:textColor="@{entry.color}"/>    </RelativeLayout></layout>

在上面的布局中,对于data里面的variable nama=”entry”表示的是一个变量名entry,type表示你所对应实体的包名路径,在TextView中,我们采用@{}的语法来引用实体类Entry中的属性。

数据对象 - (Model)

Entry.java

public class Entry {    private String text;    private int color;    public String getText() {        return text;    }    public void setText(String text) {        this.text = text;    }    public int getColor() {        return color;    }    public void setColor(int color) {        this.color = color;    }}

上面就是我们所说的实体类Entry,用于TextView的android:text属性的表达式@{entry.text}android:textColor属性表达式@{entry.color}

绑定数据 - (ViewModel)

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

MainActivity.java

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main);        Entry entry = new Entry();        entry.setText("文本数据1");        entry.setColor(0xff0000ff);        binding.setEntry(entry);    }}

事件处理

类似于 android:onClick 可以指定 Activity 中的函数,Data Binding 也允许处理从视图中发送的事件。

有两种实现方式:

  • 方法调用

  • 监听绑定

二者主要区别在于方法调用在编译时处理,而监听绑定于事件发生时处理。

方法调用

相较于 android:onClick ,它的优势在于表达式会在编译时处理,如果函数不存在或者函数签名不对,编译将会报错。

以下是个例子:

Entry.java

public class Entry {    private String text;    private int color;    public String getText() {        return text;    }    public void setText(String text) {        this.text = text;    }    public int getColor() {        return color;    }    public void setColor(int color) {        this.color = color;    }    //在这个实体中添加一个点击时间的方法    public void onClick(View view){        Toast.makeText(view.getContext(),"已点击",Toast.LENGTH_SHORT).show();    }}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools">    <data>        <variable            name="entry"            type="cn.itrealman.databindingdemo.Entry"/>    </data>    <RelativeLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        tools:context="cn.itrealman.databindingdemo.MainActivity">        <TextView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_centerInParent="true"            android:textSize="20sp"            android:text="@{entry.text}"            android:textColor="@{entry.color}"            android:onClick="@{entry::onClick}"/>    </RelativeLayout></layout>

通过在上面的TextView中添加一个android:onClick属性表达式为@{entry.onClick}这样就能调用点击事件 了,(注意:对应的方法名和监听器对象必须对应) 如果该方法不存在,则在编译的时候就不会通过了。除了上面的表达式可以表示之外,我们还可以使用以下的表达式方式进行设置:

android:onClick="@{entry.onClick}"(不过这种方式已经过时了,因为这样的表示会和entry.text这样的属性引用难以区分)

监听绑定

监听绑定在事件发生时调用,可以使用任意表达式

此功能在 Android Gradle Plugin version 2.0 或更新版本上可用.

在方法引用中,方法的参数必须与监听器对象的参数相匹配。在监听绑定中,只要返回值与监听器对象的预期返回值相匹配即可。

Entry.java

public class Entry {    private String text;    private int color;    public String getText() {        return text;    }    public void setText(String text) {        this.text = text;    }    public int getColor() {        return color;    }    public void setColor(int color) {        this.color = color;    }    //将点击监听事件中添加了一个参数    public void onClick(View view,String str){        Toast.makeText(view.getContext(),"已点击,产生了" + str,Toast.LENGTH_SHORT).show();    }}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools">    <data>        <variable            name="entry"            type="cn.itrealman.databindingdemo.Entry"/>        <variable            name="str"            type="String"/>    </data>    <RelativeLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        tools:context="cn.itrealman.databindingdemo.MainActivity">        <TextView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_centerInParent="true"            android:textSize="20sp"            android:text="@{entry.text}"            android:textColor="@{entry.color}"            android:onClick="@{(v) -> entry.onClick(v,str)}"/>    </RelativeLayout></layout>

MainAcitivty.java

public class MainActivity extends AppCompatActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        ActivityMainBinding binding = DataBindingUtil.setContentView(this,R.layout.activity_main,new MyComponent(this));        Entry entry = new Entry();        entry.setText("文本数据1");        entry.setColor(0xff0000ff);        //设置测试字符串        binding.setStr("我是监听绑定的数据测试");        binding.setEntry(entry);    }}

当一个回调函数在表达式中使用时,数据绑定会自动为事件创建必要的监听器并注册监听。关于android:onClick="@{(v) -> entry.onClick(v,str)}"这里采用的是java1.8中的lambda表达式的形式,因为这样我们可以传递相应的参数进去。

导入(Imports)

1.data 标签内可以有多个 import 标签。你可以在布局文件中像使用 Java 一样导入引用

activity_main.xml

<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools">    <data>        <import type="android.view.View"/>        <variable            name="show"            type="boolean"/>    </data>    <RelativeLayout        android:layout_width="match_parent"        android:layout_height="match_parent"        tools:context="cn.itrealman.databindingdemo.MainActivity">        <TextView            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_centerInParent="true"            android:visibility="@{show ? View.VISIBLE : View.GONE}"            android:textSize="20sp"/>    </RelativeLayout></layout>

2.当类名发生冲突时,可以使用 alias

<import type="android.view.View"/><import type="cn.itrealman.databindingdemo.utils.View" alias="UtilsView"/>

3.导入的类型也可以用于变量的类型引用和表达式中

<data>    <import type="cn.itrealman.databindingdemo.Entry"/>    <import type="java.util.List"/>    <variable name="entry" type="Entry"/>    <variable name="entryList" type="List<Entry>"/></data>

注意:Android Studio 还没有对导入提供自动补全的支持。你的应用还是可以被正常编译,要解决这个问题,你可以在变量定义中使用完整的包名。

4.导入也可以用于在表达式中使用静态方法

StringUtils.java

public class StringUtils {    public static String show(String string){        string = string.toUpperCase();        return string;    }}

activity_main.xml

    <data>        <variable            name="entry"            type="cn.itrealman.databindingdemo.Entry"/>        <import type="cn.itrealman.databindingdemo.utils.StringUtils"/>    </data>    ......    <TextView         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:layout_centerInParent="true"         android:textSize="20sp"         android:text="@{StringUtils.show(entry.text)}"         android:textColor="@{entry.color}"/></layout>

5.java.lang.* 包中的类会被自动导入,可以直接使用,例如, 要定义一个 String 类型的变量

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

变量 Variables

1.data 标签中可以有任意数量的 variable 标签。每个 variable 标签描述了会在 binding 表达式中使用的属性。

<data>    <import type="android.graphics.drawable.Drawable"/>    <variable name="entry"  type="cn.itrealman.databindingdemo.Entry"/>    <variable name="image" type="Drawable"/>    <variable name="str"  type="String"/></data>

2.可以在表达式中直接引用带 id 的 view,引用时采用驼峰命名法。

<TextView    android:id="@+id/m_entry"    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:text="@={entry.text}" /><TextView    android:text="@{user.entry}"    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:visibility="@{mEntry.getVisibility() == View.GONE ? View.GONE : View.VISIBLE}" />    <!-- 这里TextView直接引用第一个TextView,mEntry为m_entry id的驼峰命名,默认会去掉下划线 -->

3.binding 类会生成一个命名为 context 的特殊变量(其实就是 rootView 的 getContext() ) 的返回值),这个变量可用于表达式中。 如果有名为 context 的变量存在,那么生成的这个 context 特殊变量将被覆盖。

StringUtils.java

public class StringUtils {    public static String show(Context context,String string){        Toast.makeText(context, string,Toast.LENGTH_SHORT).show();        string = string.toUpperCase();        return string;    }}
<data>        <variable            name="entry"            type="cn.itrealman.databindingdemo.Entry"/>        <import type="cn.itrealman.databindingdemo.utils.StringUtils"/></data>......<TextView         android:layout_width="wrap_content"         android:layout_height="wrap_content"         android:layout_centerInParent="true"         android:textSize="20sp"         android:text="@{StringUtils.show(context,entry.text)}"         android:textColor="@{entry.color}"/>

自定义绑定类名

默认情况下,binding 类的名称取决于布局文件的命名,以大写字母开头,移除下划线,后续字母大写并追加 “Binding” 结尾。这个类会被放置在 databinding 包中。举个例子,布局文件 activity_main.xml 会生成 ActivityMainBinding 类。如果 module 包名为 com.example.my.app ,binding 类会被放在 com.example.my.app.databinding 中。

通过修改 data 标签中的 class 属性,可以修改 Binding 类的命名与位置。举个例子:

<data class="CustomBinding">    ...</data>

以上会在 databinding 包中生成名为 CustomBinding 的 binding 类。如果需要放置在不同的包下,可以在前面加 “.”:

<data class=".CustomBinding">    ...</data>

这样的话, CustomBinding 会直接生成在 module 包下。如果提供完整的包名,binding 类可以放置在任何包名中:

<data class="com.example.CustomBinding">    ...</data>

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="entry"            type="cn.itrealman.databindingdemo.Entry"/>   </data>   <RelativeLayout       android:orientation="vertical"       android:layout_width="match_parent"       android:layout_height="match_parent">       <include layout="@layout/include"            app:entry="@{entry}"/>   </RelativeLayout></layout>

include.xml

<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android">    <data>        <variable            name="entry"            type="cn.itrealman.databindingdemo.Entry"/>    </data>    <TextView        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="@{entry.text}"        android:textColor="@{entry.color}">    </TextView></layout>

需要注意, activity_main.xml 与 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="entry"            type="cn.itrealman.databindingdemo.Entry"/>   </data>   <merge>       <include layout="@layout/include"            app:entry="@{entry}"/>   </merge></layout>

表达式语言

表达式语言与 Java 表达式有很多相似之处。下面是相同之处:

  • 数学计算 + - / * %
  • 字符串连接 +
  • 逻辑 && ||
  • 二进制 & | ^
  • 一元 + - ! ~
  • 位移 >> >>> <<
  • 比较 == > < >= <=
  • instanceof
  • 组 ()
  • 字面量 - 字符,字符串,数字, null
  • 类型转换
  • 函数调用
  • 字段存取
  • 数组存取 []
  • 三元运算符 ?:
<!-- 内部使用字符串 & 字符拼接--><TextView    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:text="@{`num:` + String.valueOf(entry.num)}"/><!-- 三目运算--><TextView    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:visibility="@{show ? View.VISIBLE : View.GONE}"/>

在xml中转义是不可避免的,如 : 使用“&&”是编译不通过的,需要使用转义字符 “&&”

附:常用的转义字符

描述 转义字符 十进制 显示结果 空格 &nbsp; &#160;   小于号 &lt; &#60; < 大于号 &gt; &#62; > 与号 &amp; &#38; & 引号 &quot; &#34; " 撇号 &apos; &#39; ' 乘号 &times; &#215; × 除号 &divide; &#247; ÷

不支持的操作符

一些 Java 中的操作符在表达式语法中不能使用。

  • this
  • super
  • new
  • 显示泛型调用<T>

Null合并运算符

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

android:text="@{entry.text ?? `Default Text`}"

等同于

android:text="@{entry.text != null ? entry.text : `Default Text`}"

容器类

通用的容器类:数组,List ,SparseArray ,和 Map,可以用 [] 操作符来存取

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

字符串常量

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

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

也可以用双引号将属性包起来。这样的话,字符串常量就可以用 ” 或者反引号 ( ` ) 来调用

android:text="@{map[`text`}"android:text="@{map[&quot;text&quot;]}"

资源

也可以在表达式中使用普通的语法来引用资源:

android:text="@{@string/text(entry.text)"

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

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

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

android:text="@{@plurals/numbers(num, num)}"
Type Normal Reference Expression Reference String[] @array @stringArray int[] @array @intArray TypedArray @array @typedArray Animator @animator @animator StateListAnimator @animator @stateListAnimator color int @color @color ColorStateList @color @colorStateList

数据对象 (Data Objects)

任何 POJO 对象都能用在 Data Binding 中,但是更改 POJO 并不会同步更新 UI。Data Binding 的强大之处就在于它可以让你的数据拥有更新通知的能力。

  • Observable 对象

  • Observable 字段

  • Observable 容器类

当以上的 observable 对象绑定在 UI 上,数据发生变化时,UI 就会同步更新。

Observable 对象

Observable 接口有一个添加/移除 listener 的机制,但通知取决于开发者。为了简化开发,Android 原生提供了一个基类 BaseObservable 来实现 listener 注册机制。这个类也实现了字段变动的通知,只需要在 getter 上使用 Bindable 注解,并在 setter 中通知更新即可。

public class Entry extends BaseObservable{    private String text;    private int color;    @Bindable    public String getText() {        return text;    }    public void setText(String text) {        this.text = text;        notifyPropertyChanged(BR.text);    }    @Bindable    public int getColor() {        return color;    }    public void setColor(int color) {        this.color = color;        notifyPropertyChanged(BR.color);    }    public void onClick(View view,String str){        Toast.makeText(view.getContext(),"已点击,产生了" + str,Toast.LENGTH_SHORT).show();        setText(str);        setColor(0xffff0000);    }}

BR 是编译阶段生成的一个类,功能与 R.java 类似,用 @Bindable 标记过 getter 方法会在 BR 中生成一个 entry。
当点击 事件使数据发生变化时需要调用 notifyPropertyChanged(BR.text) 通知系统 BR.text 这个 entry 的数据已经发生变化以更新UI。

ObservableFields

创建 Observable 类还是需要花费一点时间的,如果想要省时,或者数据类的字段很少的话,可以使用 ObservableField 以及它的派生 ObservableBoolean、ObservableByte 、ObservableChar、ObservableShort、ObservableInt、ObservableLong、ObservableFloat、ObservableDouble、
ObservableParcelable 。

ObservableFields 是包含 observable 对象的单一字段。原始版本避免了在存取过程中做打包/解包操作。要使用它,在数据类中创建一个 public final 字段:

public class EntryField{    public ObservableField<String> mText = new ObservableField<>();    public ObservableField<Integer> mColor = new ObservableField<>();    public EntryField(String text, int color) {        mText.set(text);        mColor.set(color);    }}

要存取数据,只需要使用 get() / set() 方法:

mEntryField.mText.set("text");mEntryField.mColor.set(0xffff0000);String text = mEntryField.mText.get();String color = mEntryField.mColor.get();

Observable Collections 容器类

一些应用会使用更加灵活的结构来保持数据。Observable 容器类允许使用 key 来获取这类数据。当 key 是类似 String 的一类引用类型时,使用 ObservableArrayMap 会非常方便。

ObservableArrayMap<String, Object> mEntry = new ObservableArrayMap<>();mEntry .put("text", "it_real_man");mEntry .put("color", 0xffff0000);binding.setEntry(mEntry);

在布局中,可以用 String key 来获取 map 中的数据:

<data>    <import type="android.databinding.ObservableMap"/>    <variable name="entry" type="ObservableMap&lt;String, String&gt;"/></data><TextView    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:text='@{entry["text"]}'/><TextView    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:textColor='@{entry["color"]}'/>

当 key 是整数类型时,可以使用 ObservableArrayList :

ObservableArrayList<String> sEntry= new ObservableArrayList<>();sEntry.add("entryData");sEntry.add("text");sEntry.add("color");binding.setSEntry(sEntry);

在布局文件中,使用下标获取列表数据:

<data>    <import type = "android.databinding.ObservableList"/>        <variable            name="sEntry"            type="ObservableList&lt;String&gt;"/></data><TextView    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:text='@{sEntry[0]}'/><TextView    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:text='@{sEntry[1]}'/><TextView    android:layout_width="wrap_content"    android:layout_height="wrap_content"    android:text='@{sEntry[2]}'/>
1 0