Android中的MVVM DataBinding指南(一)

databinding.jpg

背景

2015年的Google I/O上提出了Data Binding的库,意味着在Android开发中,也支持MVVM的开发模式。首先简单介绍一下MVVM,这种模式是由微软公司的提出的,并且在MVP上演变而来的一种架构模式。熟悉MVP的人应该知道ViewModel通过Presenter来完成交互,现在比较流行positive View,也就是View完全被动的展示数据,逻辑完全交给Presenter。而在MVVM中,ViewModel就是一个强化版的Presenter,因为Data Binding Library能够帮ViewModel生成Binding大量的代码。下图为MVP和MVVM示意图

mvp-passiveview.png

MVVM.png

本期对Data Binding的用法做个介绍。

兼容性

这是很多人比较关心的地方,毕竟版本兼容一直Android中的重要话题,Data Binding Library 兼具灵活性和广泛的兼容性,它是一个Support Library(类似于v4和v7包)可以向下兼容到Android2.1(API level 7+)。

构建环境

Data Binding 要求Android Studio版本在1.3以上(好处是IDEA支持语法高亮提示等,详情请见 Android Studio Support for Data Binding)gradle的版本要在1.5.0-alpha1以上,而且需要在Android SDK manager中下载Support repository,随后在工程的build.gradle中加入dataBinding元素如下

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

如果你的app module依赖于一个使用了Data Binding library,那么app modulebuild.gradle 文件也需要加入此元素。

先从Layout文件入手

Data Bindinglayout文件和以往我们的形式有所不同,<Primary Root Element>需要变成<layout>,在<layout>中添加了<data>节点来定义需要用到的变量和需要的引用,<Primary Root Element>则和<data>平行于<layout>之内,可以理解为<data>负责数据部分,<Primary Root Element>负责View的展示。以下是个简单地例子

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable name="user" type="com.winter.huang.databinding.viewmodel.UserViewModel"/>
    </data>

    <RelativeLayout
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".view.MainActivity">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{user.phone}" />

    </RelativeLayout>

</layout>

<data>中的<variable>声明了一个可能在layout中使用的变量,包括nametype。如果使用<import>之后type就不需要写明类的全路径了

<data>
  <import type="com.winter.huang.databinding.viewmodel.UserViewModel"/>
  <variable name="user" type="UserViewModel"/>
</data>

<import>可以引用你需要的任何类就和普通的Java import关键字一样,例如

<import type="android.view.View"/>

引用之后这个类的方法就可以直接使用了,例如

<TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:visibility="@{user.isRegister ? View.VISIBLE : View.INVISIBLE}">

但是这里可能会有两个名字重复的类,例如

<data>
  <import type="com.winter.huang.databinding.viewmodel.UserViewModel"/>
  <import type="com.winter.huang.databinding.data.UserViewModel"/>
  <variable name="user" type="UserViewModel"/>
</data>

这样看起来让人很困惑,因为对应关系看起来一团糟,可以在<import>中加入alias,就可以轻松的区分同名类了

<data>
  <import type="com.winter.huang.databinding.viewmodel.UserViewModel" alias="User"/>
  <import type="com.winter.huang.databinding.data.UserViewModel" alias="DataUser"/>
  <variable name="user" type="User"/>
</data>

之后就可以在layout中需要属性值的地方加入由"@{}"语法包裹的表达式了,给TextView android:text的赋值方法

<TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="@{user.phone}"/>

支持的操作符

数学运算符 + - / * %

字符串拼接 +

逻辑运算 && ||

二进制运算 & | ^

一元运算符 + - ! ~

位运算符 >> >>> <<

比较运算符 == > < >= <=

instanceof

Grouping ()

文字 - character, String, numeric, null

类型转换 cast

方法调用 methods call

字段使用 field access

数组使用 [] Arrary access

三元运算符 ? :

缺失的操作符

this

super

new

显示类型调用 关于显示类型调用可以看这里

此外还支持null的合并操作符??,不同于三元运算符,??代表如果前者不为null则直接使用前者的值,否则使用后者,例如

<TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.phone ?? @string/empty}">

如果user.phone不为null显示user.phone的值,为空则将在TextView中显示一个空字符串。

空指针安全

自动生成的 DataBinding 代码会检查null,避免出现NullPointerException。例如在表达式中@{user.phone}如果user == null 那么会为user.phone设置默认值null而不会导致程序崩溃(基本类型将赋予默认值如int为0,引用类型都会赋值null

集合

支持 arrays, lists, sparse lists, maps,为了方便在获取item时可以使用[]

<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]}"

需要注意一点,在xml中<需要用转义字符<来代替。

Data Object

接下来看一下如何定义一个 POJO(plain-old Java object) 类,这个类就是ViewModel

public class UserViewModel {
    public final String phone;
    public final boolean hasRegister;

    public UserViewModel(boolean hasRegister, String phone) {
        this.hasRegister = hasRegister;
        this.phone = phone;
    }
}

由于字段都是final的,那么这个类的数据在读取它的数值之后永远不会在程序运行时改变,当然你也可以用一个 JavaBean object

public class UserViewModel {
    private final String phone;
    private final boolean hasRegister;

    public UserViewModel(boolean hasRegister, String phone) {
        this.hasRegister = hasRegister;
        this.phone = phone;
    }

    public String getPhone() {
        return phone;
    }

    public boolean isHasRegister() {
        return hasRegister;
    }
}

以上两种定义方式对于 Data Binding 而言是没有区别的,在给TextView android:text赋值@{user.phone}时,前一种会使用phone后一种会使用getPhone()

绑定数据

默认情况下,BindingClass会以layout文件名来驼峰命名,并且以“Binding”结尾,例如layout文件为main_activity.xml那么自动生成的Binding类的名字就是MainActivityBinding,你也可以自定义这个BindingClass的名字,只需要在 <data>加上class

<data class="MainBinding">

这样你的Binding类名字就变成了MainBinding。由于在<data>中的<variable>声明了一个user变量,Data Binding自动生成了setUser(viewModel)方法,这样就可以把ViewModelxml关联起来了。
那么接下来在MainActivity中只需要几行代码就能实现Data Binding

  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        UserViewModel viewModel = new UserViewModel(false, "18588419291");
        binding.setUser(viewModel);
    }

或者你也可以在使用以下方法获取view

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

作为列表展示数据可以使用以下方法:

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

现在运行程序,就能看到Userphone属性已经神奇的展示在TextView的位置了。

关于MainBinding.java

xml中配置好之后,Data Binding Library 会帮你生成一个对应的.class文件,经过IDEA的编译我们可以看到自动生成的代码,在AndroidStudio中切换到Project视图app-build-intermediates-classes-debug-Your PackageName下会有一个databinding文件夹,这里面就有对应的MainBinding.class我们可以大概看一下

public class MainBinding extends android.databinding.ViewDataBinding {
  private static final android.databinding.ViewDataBinding.IncludedLayouts sIncludes;
  private static final android.util.SparseIntArray sViewsWithIds;
  static {
    sIncludes = null;sViewsWithIds = null;
  }
  // views
  private final android.widget.RelativeLayout mboundView0;
  public final android.widget.TextView textView;// variables
  private com.winter.huang.databinding.viewmodel.UserViewModel mUser;
  // values
  // listeners
  // Inverse Binding Event Handlers
  public MainBinding(android.databinding.DataBindingComponent bindingComponent, View root) {
    super(bindingComponent, root, 0);
    final Object[] bindings = mapBindings(bindingComponent, root, 2, sIncludes,sViewsWithIds);
    this.mboundView0 = (android.widget.RelativeLayout) bindings[0];
    this.mboundView0.setTag(null);
    this.textView = (android.widget.TextView) bindings[1];
    this.textView.setTag(null);
    setRootTag(root);
    // listeners
    invalidateAll();}
    ...
}

可以看到每个xml中的View在自动生成的类中都会有对应的一个final的变量与之对应,那么在MainActivity中就不需要再使用findViewById,可以直接访问该变量

mainBinding.textView.setText(user.phone);

同时在xml中也可以直接使用,例如使用checkBox.checked属性做判断

  <CheckBox
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:id="@+id/checkBox"/>

       <TextView
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:id="@+id/textView"
           android:visibility="@{checkBox.checked ? View.VISIBLE : View.GONE}"/>

这个类会随着xml中一些修改而在编译时重新生成,这就是Data Binding帮我们做的一个非常重要的桥梁,这期就先到这里,下期继续介绍Data Binding的事件绑定。

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

推荐阅读更多精彩内容