Android Databinding技术由浅入深(2)------Databinding的简单使用

本文参照Data Binding Library - Android Developers 谷歌官方


这篇文章主要讲述我自己使用过程中遇到的一些问题和自己的理解,后面也列出许多知识点。

先介绍一下使用这个功能我们需要改变的一些思维:

1.layout变化:起始根标签是layout,接下来一个data元素以及一个view的根元素。layout是在原来布局的基础上包了一层。

2.起始根标签是layout的布局Android Studio会生成一个类,类名可以指定。

3.layout中用的语法为POJO。

Databinding的知识点我感觉比较多,但好多我感觉不必细看,粗略看一下就可以,用的频率最多的就那么几个,在实际使用中会感受到。


1)兼容性配置

你可以在所有的Android平台最低能到Android 2.1(API等级7+)上使用它。

需求:Android Plugin for Gradle1.5.0-alpha1更高版本

2)Android Studio内配置构建环境

要开始使用Data Binding,首先需要在Android SDK Manager的支持库里下载该库。

你的app要使用Data Binding,需要添加Data Binding到gradle构建文件里,如下:

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

Data Binding插件将会在你的项目内添加必需提供的以及编译配置依赖。

3)Data Binding Layout文件

a)Data Binding表达式

Data Binding layout文件有点不同的是:起始根标签是layout,接下来一个data元素以及一个view的根元素。这个view元素就是你没有使用Data Binding的layout文件的根元素。举例说明如下:

data内描述了一个名为user的变量属性,使其可以在这个layout中使用:

在layout的属性表达式写作@{},下面是一个TextView的text设置为user的firstName属性:

b)Data对象

假设你有一个user的plain-old Java Object(POJO):

publicclassUser{publicfinalStringfirstName;publicfinalStringlastName;publicUser(StringfirstName,StringlastName) {this.firstName = firstName;this.lastName = lastName;  }}

这个类型的对象拥有从不改变的数据。在app中它是常见的,可以读取一次并且之后从不改变。当然也可以使用JavaBeans对象:

publicclassUser{privatefinalString firstName;privatefinalString lastName;publicUser(String firstName, String lastName) {this.firstName = firstName;this.lastName = lastName;  }publicString getFirstName() {returnthis.firstName;  }publicString getLastName() {returnthis.lastName;  }}

从Data Binding的角度来看,这两个类是等价的。用于TextView中的android:text属性的表达式@{user.firstName}将访问前者POJO对象中的firstName和后者JavaBeans对象中的getFirstName()方法。

c)Binding数据

默认情况下,一个Binding类会基于layout文件的名称而产生,将其转换为Pascal case(译注:首字母大写的命名规范)并且添加“Binding”后缀。上述的layout文件是main_activity.xml,因此生成的类名是MainActivityBinding。此类包含从layout属性到layout的Views中所有的bindings(例如user变量),并且它还知道如何给Binding表达式分配数值。创建bindings的最简单的方式是在inflating(译注:layout文件与Activity/Fragment的“链接”)期间如下:

@OverrideprotectedvoidonCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);  MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);  User user =newUser("Test","User");  binding.setUser(user);}

就是这样,运行app后,你将会看到Test User。或者你可以通过如下获取View:

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

如果你在ListView或者RecyclerViewadapter使用Data Binding时,你可能会使用:

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

d)事件处理

数据绑定允许你编写表达式来处理view分派的事件。事件属性名字取决于监听器方法名字。例如View.OnLongClickListeneronLongClick()的方法,因此这个事件的属性是android:onLongClick。处理事件有两种方法:

Method References

Listener Bindings

4)深入Layout文件

a)Import

零个或多个import元素可能在data元素中使用。这些只用在你的layout文件中添加引用,就像在Java中:

现在,View可以使用你的Binding表达式:

当类名有冲突时,其中一个类名可以重命名为alias::

这样,在该layout文件中Vista对应com.example.real.estate.View,而View对应android.view.View。导入的类型可以在Variable和表达式中使用作为引用来使用:

                "/>

注意:Android Studio还没有处理imports,所以自动导入Variable在你的IDE不能使用。您的app仍会正常编译,你可以在您的Variable定义中使用完全符合规定的名称来解决该IDE问题。

导入的类型还可以在表达式中使用static属性和方法:

就像在Java中,java.lang。*是自动导入的。

b)Variables

在data中可以使用任意数量的variable元素。每一个variable元素描述了一个用于layout文件中Binding表达式的属性。


该Variable类型在编译时检查,因此如果一个Variable实现了Observableobservable collection,这应该反映在类型中。(译注:需要查找资料来理解)如果variable是一个没有实现Observable接口的基本类或者接口,Variables不会被observed!

当对于多种配置有不同的layout文件时(如,横向或纵向),Variables会被合并。这些layout文件之间必须不能有冲突的Variable定义。

产生的Binding类对于每一个描述的Variables都会有setter和getter。这些Variables会使用默认的Java值 - null(引用类型)、0(int)、false(boolean)等等,直到调用setter时。

c)自定义Binding类名称

默认情况下,Binding类的命名是基于所述layout文件的名称,用大写开头,除去下划线()以及()后的第一个字母大写,然后添加“Binding”后缀。这个类将被放置在一个模块封装包里的databinding封装包下。例如,所述layout文件contact_item.xml将生成ContactItemBinding。如果模块包是com.example.my.app,那么它将被放置在com.example.my.app.databinding。

Binding类可通过调整data元素中的class属性来重命名或放置在不同的包中。例如:

...

在模块封装包的databinding包中会生成名为ContactItem的Binding类。如果要想让该类生成在不同的包种,你需要添加前缀.,如下:

...

在这个情况下,ContactItem类直接在模块包种生成。或者你可以提供整个包名:

...

d)Includes

通过使用application namespace以及在属性中的Variable名字从容器layout中传递Variables到一个被包含的layout:

注意:在name.xml以及contact.xml两个layout文件中必需要有uservariable

e)表达式

常用表达式跟Java表达式很像,以下这些是一样的:

数学+-/*%

字符串连接+

逻辑&&||

二进制&|^

一元运算+-!~

移位>>>>><<

比较==><>=<=

instanceof

分组()

null

Cast

方法调用

数据访问[]

三元运算?:

示例:

android:text="@{String.valueOf(index + 1)}"android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"android:transitionName='@{"image_" + id}'

缺少的操作:

this

super

new

显式泛型调用

Null合并操作

??- 左边的对象如果它不是null,选择左边的对象;或者如果它是null,选择右边的对象:

android:text="@{user.displayName ?? user.lastName}"

函数上的写法如下:

android:text="@{user.displayName!=null?user.displayName:user.lastName}"

属性引用

第一个已经在前边提到了a)Data Binding表达式:JavaBean引用的简短格式。

当一个表达式引用一个类的属性,它仍使用同样的格式对于字段、getters以及ObservableFields。

android:text="@{user.lastName}"

避免 NullPointerException

Data Binding代码生成时自动检查是否为nulls来避免出现null pointer exceptions错误。例如,在表达式@{user.name}中,如果user是null,user.name会赋予它的默认值(null)。如果你引用user.age,age是int类型,那么它的默认值是0

集合

常用的集合:arrays、lists、sparse lists以及maps,为了简便都可以使用[]来访问。

        ">  ">  ">    …android:text="@{list[index]}"…android:text="@{sparse[index]}"…android:text="@{map[key]}"

字符串

当使用单引号包含属性值时,在表达式中使用双引号很容易:

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

使用双引号来包含属性值也是可以的。字符串前后需要使用"`":

android:text="@{map[`firstName`]}"android:text="@{map["firstName"]}"

Resources

使用正常的表达式来访问resources也是可行的:

android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"

格式化字符串和复数可以通过提供参数来判断

android:text="@{@string/nameFormat(firstName, lastName)}"android:text="@{@plurals/banana(bananaCount)}"

当复数需要多个参数时,所有的参数都会通过:

Have an orangeHave %d orangesandroid:text="@{@plurals/orange(orangeCount, orangeCount)}"

一些资源需要显式类型判断:

类型正常引用表达式引用

String[]@array@stringArray

int[]@array@intArray

TypedArray@array@typedArray

Animator@animator@animator

StateListAnimator@animator@stateListAnimator

color int@color@color

ColorStateList@color@colorStateList

5)Data 对象

任何Plain old Java object(PO​​JO)可用于Data Binding,但修改POJO不会导致UI更新。Data Binding的真正能力是当数据变化时,可以通知给你的Data对象。有三种不同的数据变化通知机制:Observable对象、ObservableFields以及observable  collections。

当这些可观察Data对象​​绑定到UI,Data对象属性的更改后,UI也将自动更新。

a)Observable 对象

实现android.databinding.Observable接口的类可以允许附加一个监听器到Bound对象以便监听对象上的所有属性的变化。

Observable接口有一个机制来添加和删除监听器,但通知与否由开发人员管理。为了使开发更容易,一个BaseObservable的基类为实现监听器注册机制而创建。Data实现类依然负责通知当属性改变时。这是通过指定一个Bindable注解给getter以及setter内通知来完成的。

privatestaticclassUserextendsBaseObservable{privateString firstName;privateString lastName;@BindablepublicStringgetFirstName(){returnthis.firstName;  }@BindablepublicStringgetFirstName(){returnthis.lastName;  }publicvoidsetFirstName(String firstName){this.firstName = firstName;      notifyPropertyChanged(BR.firstName);  }publicvoidsetLastName(String lastName){this.lastName = lastName;      notifyPropertyChanged(BR.lastName);  }}

在编译期间,Bindable注解在BR类文件中生成一个Entry。BR类文件会在模块包内生成。如果用于Data类的基类不能改变,Observable接口通过方便的PropertyChangeRegistry来实现用于储存和有效地通知监听器。

b)Observable 字段

一些小工作会涉及到创建Observable类,因此那些想要节省时间或者几乎没有几个属性的开发者可以使用ObservableFields。ObservableFields是自包含具有单个字段的observable对象。它有所有基本类型和一个是引用类型。要使用它需要在data对象中创建public final字段:

privatestaticclassUser{publicfinal ObservableField firstName =newObservableField<>();publicfinal ObservableField lastName =newObservableField<>();publicfinal ObservableInt age =newObservableInt();}

就是这样,要访问该值,使用set和get方法:

user.firstName.set("Google");int age = user.age.get();

c)Observable 集合

一些app使用更多的动态结构来保存数据。Observable集合允许键控访问这些data对象。ObservableArrayMap用于键是引用类型,如String。

ObservableArrayMapuser= new ObservableArrayMap<>();user.put("firstName","Google");user.put("lastName","Inc.");user.put("age", 17);

在layout文件中,通过String键可以访问map:

"/>…

ObservableArrayList用于键是整数:

ObservableArrayListuser= new ObservableArrayList<>();user.add("Google");user.add("Inc.");user.add(17);

在layout文件中,通过索引可以访问list:

"/>…

6)Binding生成

Binding类的生成链接了layout中variables与Views。如前面所讨论的,Binding的名称和包名可以定制。所生成的Binding类都扩展了android.databinding.ViewDataBinding。

a)创建

Binding应在inflation之后就立马创建,以确保View层次结构不在之前打扰layout中的binding到views上的表达式。有几个方法可以绑定到一个layout。最常见的是在Binding类上使用静态方法.inflate方法载入View的层次结构并且绑定到它只需这一步。还有一个更简单的版本,只需要LayoutInflater还有一个是采用ViewGroup:

MyLayoutBinding binding= MyLayoutBinding.inflate(layoutInflater);MyLayoutBinding binding= MyLayoutBinding.inflate(LayoutInflater, viewGroup, false);

如果使用不同的机制载入layout,他可一分开绑定:

MyLayoutBinding binding= MyLayoutBinding.bind(viewRoot);

有时Binding不能提前知道,对于这种情况,可以使用DataBindingUtil类来创建Binding:

ViewDataBindingbinding= DataBindingUtil.inflate(LayoutInflater, layoutId,    parent, attachToParent);ViewDataBindingbinding= DataBindingUtil.bindTo(viewRoot,layoutId);

b)带ID的Views

在layout中对于每个带ID的View会生成一个public final字段。Binding在View层次结构上做单一的传递,提取带ID的Views。这种机制比起某些Views使用findViewById还要快。例如:

它会生成如下的Binding类:

publicfinalTextView firstName;publicfinalTextView lastName;

IDs不像没有Data Bindings那样几乎没有必要,但是仍然会有一些实例需要从代码中访问Views。

c)Variables

每个Variable会有访问方法。


它会在Binding中生成setters和getters:

publicabstractcom.example.UsergetUser();publicabstractvoidsetUser(com.example.User user);publicabstractDrawablegetImage();publicabstractvoidsetImage(Drawable image);publicabstractStringgetNote();publicabstractvoidsetNote(String note);

d)ViewStubs

ViewStubs跟正常的Views略有不同。他们开始时是不可见的,当他们要么设置为可见或被明确告知要载入时,它们通过载入另外一个layout取代了自己。

由于ViewStub基本上从View的层次结构上消失,在Binding对象的View也必须消失来允许被收集。因为Views是最后的,一个ViewStubProxy对象取带ViewStub,给开发者获得了ViewStub,当它存在以及还可以访问载入的View层次结构时当ViewStub已被载入时。

当载入另一个layout,为新的布局必需创建一个Binding。因此,ViewStubProxy必需监听ViewStub的OnInflateListener监听器并在那个时候建立Binding。因为只有一个可以存在,ViewStubProxy允许开发者在其上设置一个OnInflateListener它会在建立Binding后调用。

e)Binding进阶

动态Variables

有时,不知道具体的Binding类,例如,一个RecyclerView适配器对layouts任意操作并不知道具体的Binding类。它仍然必需在onBindViewHolder期间赋值给Binding。

在这个例子中,该RecyclerView绑定的所有layouts有一个“item”的Variable。该BindingHolder有一个getBinding方法返回ViewDataBinding。

publicvoidonBindViewHolder(BindingHolder holder,intposition) {  final T item = mItems.get(position);  holder.getBinding().setVariable(BR.item, item);  holder.getBinding().executePendingBindings();}

直接Binding

当一个variable或observable变化时,binding会在下一帧之前被计划要改变。有很多次,但是在Binding时必须立即执行。要强制执行,使用executePendingBindings()方法。

后台线程

只要它不是一个集合,你可以在后台线程中改变你的数据模型。在判断是否要避免任何并发问题时,Data Binding会对每个Varialbe/field本地化。

7)属性Setters

每当绑定值的变化,生成的Binding类必须调用setter方法​​。Data Binding框架有可以自定义赋值的方法。

a)自动Setters

对于一个属性,Data Binding试图找到setAttribute方法。与该属性的namespace并不什么关系,仅仅与属性本身名称有关。

例如,有关TextView的android:text属性的表达式会寻找一个setText(String)的方法。如果表达式返回一个int,Data Binding会搜索的setText(int)方法。注意:要表达式返回正确的类型,如果需要的话使用casting。Data Binding仍会工作即使没有给定名称的属性存在。然后,您可以通过Data Binding轻松地为任何setter“创造”属性。例如,DrawerLayout没有任何属性,但大量的setters。您可以使用自动setters来使用其中的一个。

b)重命名的Setters

一些有setters的属性按名称并不匹配。对于这些方法,属性可以通过BindingMethods注解相关联。这必须与一个包含BindingMethod注解的类相关联,每一个用于一个重命名的方法。例如,android:tint属性与setImageTintList相关联,而不与setTint相关。

@BindingMethods({@BindingMethod(type ="android.widget.ImageView",                      attribute ="android:tint",                      method ="setImageTintList"),})

以上例子,开发者需要重命名setters是不太可能了,android架构属性已经实现了。

c)自定义Setters

有些属性需要自定义绑定逻辑。例如,对于android:paddingLeft属性并没有相关setter。相反,setPadding(left, top, right, bottom)是存在在。一个带有BindingAdapter注解的静态绑定适配器方法允许开发者自定义setter如何对于一个属性的调用。

Android的属性已经创造了BindingAdapters。举例来说,对于paddingLeft:

@BindingAdapter("android:paddingLeft")publicstaticvoidsetPaddingLeft(View view,intpadding){  view.setPadding(padding,                  view.getPaddingTop(),                  view.getPaddingRight(),                  view.getPaddingBottom());}

Binding适配器对其他定制类型非常有用。例如,自定义loader可以用来异步载入图像。

当有冲突时,开发人员创建的Binding适配器将覆盖Data Binding默认适配器。

您也可以创建可以接收多个参数的适配器。

@BindingAdapter({"bind:imageUrl","bind:error"})publicstaticvoidloadImage(ImageView view, String url, Drawableerror){  Picasso.with(view.getContext()).load(url).error(error).into(view);}

如果对于一个ImageViewimageUrlerror都被使用并且imageUrl是一个string类型以及error是一个drawable时,该适配器会被调用。

匹配的过程中自定义namespaces将被忽略。

你也可以为Android namespaces写适配器。

8)转换

a)对象转换

当从Binding表达式返回一个对象,一个setter会从自动、重命名以及自定义的setters中选择。该对象将被转换为所选择的setter的参数类型。

这是为了方便那些使用ObservableMaps来保存数据。例如:

在userMap返回一个对象并且该对象将自动转换为setText(CharSequence)的参数类型。当有关参数类型可能混乱时,开发人员需要在表达式中转换。

b)自定义转换

有时候转换应该是自动的在特定类型之间。例如,设置背景的时候:

这里,背景需要Drawable对象,但颜色是一个整数。不管何时有Drawable并且返回值是一个整数,那么整数类型会被转换为ColorDrawable。这个转换是通过使用带有BindingConversion注解的静态方法完成的:

@BindingConversionpublicstaticColorDrawableconvertColorToDrawable(intcolor){returnnewColorDrawable(color);}

注意:转换仅仅发生在setter级别,因此它是不允许以下混合类型:

9)Android Studio支持

Android Studio为数据绑定支持许多的代码编辑。例如,它支持以下功能:

语法高亮

标记表达式的语法错误

XML代码补全

引用,包括navigation(如导航到声明处)以及快速文档查询

注意: 数组以及通用类型,比如说Observable类,可能会显示错误事实上并没有错误。


做技术的,不要畏首畏尾,要乐于尝试新的技术。

学习目录

1.Android Databinding技术由浅入深(1)------Databinding的介绍

2.Android Databinding技术由浅入深(2)------Databinding的简单使用

3.Android Databinding技术由浅入深(3)------Databinding的源码解析

4.Android Databinding技术由浅入深(4)------Databinding进阶MVVM全框架

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容