android引入MVVM框架时间还不长,眼下还非常少有应用到app中的。但它是比較新的技术,使用它来搭建项目能省非常多代码,并且能使用代码架构比較清晰。本篇文章是我在学习MVVM时翻译的。篇幅比較长。先翻译前半部分。
这篇文档解析怎样使用数据绑定库来写响应式布局并降低用来绑定应用程序和布局之间冗余代码,使用逻辑层和布局分离。
数据绑定库提供了即灵活又全面的兼容性——它的支持库.so能够用在android2.1平台(API level 7+)。
使用MVVM须要Gradle1.5.0-alphal或更高版本号的插件。
一、測试版
请注意,数据绑定库是一个測试版。尽管数据绑定是处于測试阶段,开发者应该注意下面事项:
*眼下它仅仅是一个測试版。可能不适合你的用例。我们须要你的反馈。
*数据绑定库測试版有重大的改变,包含那些没有源码与应用程序不兼容,也就是说,可能以后须要进行更改。
*开发者应该随时公布应用程序构建与数据绑定库測试版,它与Android SDK和谷歌的服务条款适用,建议常常採用新库或工具来彻底測试自己的应用程序。
二、搭建好开发环境
首先你须要在Android SDK manager中下载支持库。
配置您的应用程序。在你的module中的build.gradle文件里加入dataBinding元素。
使用以下的代码片段来配置数据绑定:
android { .... dataBinding { enabled = true } }
假设你的app使用到的库使用到数据绑定,那你的app也须要在build.gradle文件里进行配置。
另外,确保您正在使用一个兼容的版本号的Android工作室。Android Studio 1.3或更新的版本号支持数据绑定。
数据绑定的布局文件
编写你的第一个数据绑定布局
数据绑定布局文件略有不同,它以layout作为布局的起点,,后跟一个data标签和一个view元素。
这个view元素是普通不使用数据绑定布局的根元素。一个演示样例文件是这种:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="user" type="com.example.User"/> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.lastName}"/> </LinearLayout> </layout>
data中使用的variable表示了一个可能会在这个布局中作用的属性。
<variable name="user" type="com.example.User"/>
布局属性的设置使用“@ { }”语法,这里TextView的文字属性就设置为user中的firstName属性。
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}"/>
对象
让我们如果如今有一个User的普通java对象(POJO):
public class User { public final String firstName; public final String lastName; public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }
这样的类型的对象数据不会改变。
通常在应用程序的数据读取一次,永远不会改变。它还能够改为javabean对象:
public class User { private final String firstName; private final String lastName; public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return this.firstName; } public String getLastName() { return this.lastName; } }
从数据绑定的角度来看,这两个类是等价的。用于设置TextView的android:文本的表达式@{user.firstName}将訪问第一个类中的firstName字段和后一个类的getFirstName()方法。另外,假设firstName()方法存在。它还将訪问firstName()方法。
绑定数据
默认情况下,绑定类的名称是基于布局文件的名称起的,它是将布局文件名称开头大写并加上“Binding”而成。上述布局文件名称称为main_activity.xml。那它的绑定类名为MainActivityBinding。这个类拥有全部从属性(比如用户变量)到布局的绑定关系并知道怎样赋值绑定表达式。最简单的方法创建绑定的方法就是通过反射:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity); User user = new User("Test", "User"); binding.setUser(user); }
完毕了!执行应用程序,你会看到在UI上看到“Test User”。另外,你也能够通过下面代码来获取view:
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());假设你想在 ListView或者RecyclerView 使用数据绑定。你应该这样使用:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false); //or ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
绑定事件
事件能够直接绑定到处理程序方法,类似于android:onClick能够分配给一个Activity的方法。事件属性名称是listener方法的名称有一部分。
比如,View.OnLongClickListener有一个onLongClick()方法,所以这个事件的属性名应写为android:onLongClick。
分配一个事件给handler。用法名称来作为正常绑定表达式的变量,比如,假设您的数据对象有两个方法:
public class MyHandlers { public void onClickFriend(View view) { ... } public void onClickEnemy(View view) { ... } }绑定表达式会为View 分配一个click监听事件。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="handlers" type="com.example.Handlers"/> <variable name="user" type="com.example.User"/> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}" android:onClick="@{user.isFriend ?三、布局细节handlers.onClickFriend : handlers.onClickEnemy}"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.lastName}" android:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/> </LinearLayout> </layout>
Imports
零个或多个导入元素能够使用内部数据元素。
这些同意简单引用类内部布局文件,就像在Java。
<data> <import type="android.view.View"/> </data>
如今,view能够使用在你的绑定表达式:
<TextView android:text="@{user.lastName}" android:layout_width="wrap_content" android:layout_height="wrap_content" android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
当有类名称冲突,当中一个类可能用alias进行重命名:
<import type="android.view.View"/> <import type="com.example.real.estate.View" alias="Vista"/>
如今,Vista能够使用 com.example.real.estate.view中的引用而且View可用于代表android.view.view。导入类型能够在变量和表达式中作为类型引用:
<data> <import type="com.example.User"/> <import type="java.util.List"/> <variable name="user" type="User"/> <variable name="userList" type="List<User>"/> </data>
注意:Android studio还没处理好import的自己主动导入,因此你的IDE可能无法自己主动导入变量,你能够在变量定义使用全然限定名称。这样你的应用程序仍能够正常编译。
<TextView android:text="@{((User)(user.connection)).lastName}" android:layout_width="wrap_content" android:layout_height="wrap_content"/>导入类型时也能够使用在引用静态字段和方法中:
<data> <import type="com.example.MyStringUtils"/> <variable name="user" type="com.example.User"/> </data> … <TextView android:text="@{MyStringUtils.capitalize(user.lastName)}" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
就像在java中,java.lang.*会自己主动导入一样。
变量
数据元素能够使用随意数量的变量元素。
每一个变量元素描写叙述一个属性,它都必须在layout中通过绑定表达式来设置值。
<data> <import type="android.graphics.drawable.Drawable"/> <variable name="user" type="com.example.User"/> <variable name="image" type="Drawable"/> <variable name="note" type="String"/> </data>
变量在编译时进行类型检查,假设一个变量实现了Observable接口或者它是一个observable集合,那应该反射得到。假设变量是一个没有实现Observable的基类或接口。,那变量将无法被观察到!
当不同的布局文件里的变量配置不同样(如landscape或者protrait),这些变量将被组合起来。
在这些布局文件之间定义变量不会有冲突。
生成的绑定类中,相应每一个变量都有一个setter和getter方法。变量将设置为默认的Java值直到setter被调用,如引用类型默觉得null,int默认值是0,boolean默觉得false等。
Custom Binding Class Names
默认情况下,生成绑定类名称是基于布局文件的名称的,把布局文件名开头用大写,删除下划线(_),最后加上”Binding”。
这个类将被放置在module的一个databinding包模块下。比如。contact_item.xml将产生一个类名ContactItemBinding的类。假设module包名为com.example.my。那么它将会被放置在com.example.my.app.databinding这个包下。
通过调整数据元素的class属性能够将绑定类重命名或放置在不同的包。比如:
<data class="ContactItem"> ... </data>
这个生成绑定类ContactItem将会存在module包下的databinding包中。假设想让生成的类存放在module中的还有一个包下,它增加前缀”.”:
<data class=".ContactItem"> ... </data>
这样的情况下。ContactItem将被直接放置在module包下。假设想放在其他包能够使用包名的全称:
<data class="com.example.ContactItem"> ... </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="user" type="com.example.User"/> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <include layout="@layout/name" bind:user="@{user}"/> <include layout="@layout/contact" bind:user="@{user}"/> </LinearLayout> </layout>
在这里。name.xml和contact.xml中都必需要有user变量。
<pre name="code" class="html"><span style="font-weight: normal;"><?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="user" type="com.example.User"/> </data> <merge> <include layout="@layout/name" bind:user="@{user}"/> <include layout="@layout/contact" bind:user="@{user}"/> </merge> </layout></span>
表达式语法
共同特性
表达式语法跟java语法非常像,以下的一样的语法:
· 数学计算 + - / * %
· 字符串连接 +
· 逻辑运算符&& ||
· 位运算& | ^
· 一元运算 + - ! ~
· 位移>> >>> <<
· 比較== > < >= <=
· instanceof
· Grouping ()
· 文字 - character, String, numeric, null
· Cast
· 方法调用
· 字段訪问
· 数组訪问 [ ]
· 三元运算符?
:
比如:
android:text="@{String.valueOf(index + 1)}" android:visibility="@{age < 13 ?View.GONE : View.VISIBLE}" android:transitionName='@{"image_" + id}'
不具备的方法
有一些操作仅仅能在Java表达式中使用。
· this
· super
· new
· Explicit generic invocation
空联合操作
联合操作符(?
?)不为空的话选择左边操作,为空的话选择右边操作。
android:text="@{user.displayName ?? user.lastName}"
它等同于:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
避免指针异常
生成数据绑定代码会自己主动检查null,避免空指针异常。比如,在表达式@ { user.name },假设用户是null,user.name分配其默认值(null)。
假设是@ { user.age }。
age是int,那么它将默认值为0。
Collections
集合共同点:数组,list,sparse list和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]}"
文字字符串
使用单引號包裹住属性值,这样就非常easy在表达式中使用双引號:
android:text='@{map["firstName"]}'
还能够使用双引號包围的属性值。当这样做时,字符串应该使用"或引號(`)。
android:text="@{map[`firstName`}" android:text="@{map["firstName"]}"
资源文件
能够使用正常的訪问资源的表达式语法:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"格式字符串和复数能够通过提供參数来定义:
android:text="@{@string/nameFormat(firstName, lastName)}" android:text="@{@plurals/banana(bananaCount)}"当一个复数多个參数。全部參数都应该通过:
Have an orange Have %d oranges android:text="@{@plurals/orange(orangeCount, orangeCount)}"一些资源须要显式类型的评估:.
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 |