Android之Databinding学习笔记

本文详细介绍了Android的DataBinding库,包括其引入背景、环境构建、布局文件、数据对象、事件处理、方法调用等方面,旨在帮助开发者理解如何利用DataBinding实现MVVM模式,减少重复代码,提高开发效率。
摘要由CSDN通过智能技术生成

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)}"
TypeNormal ReferenceExpression 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]}'/>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值