Android中的MVVM DataBinding指南(一)

作者: winter1991 | 来源:发表于2016-05-05 11:46 被阅读2018次
    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的事件绑定。

    相关文章

      网友评论

      • Y_KANG:赞一个
      • 一个loser:牛人都是怎么炼成的!真佩服你们
      • eed8ce203d04:现在支持双向绑定了,不妨加进去?
        eed8ce203d04:@winter1991 soga,不过没有看到类似@={}这类的用法
        winter1991:@黄炤宇 已经是双向绑定了,View的交互能直接更新ViewModel,ViewModel改变就直接体现到View之上,可以看下第二期的解释 :smile:
      • android121:不错,学习了
        winter1991:@android121 谢谢 :smile:

      本文标题:Android中的MVVM DataBinding指南(一)

      本文链接:https://www.haomeiwen.com/subject/xqdhrttx.html