Android Data-Binding简记

从我的CSDN博客http://blog.csdn.net/dahaohan 迁移的第一篇博文。

What's Data-Binding?

看过我之前转发的博文Android App的设计架构:MVC,MVP,MVVM经验谈
可以了解到移动端App开发架构从传统MVC-->MVP-->MVVM的一些进展和演化,而目前发展成的MVVM架构则需要使用Data-Binding机制来完成View和ViewModel之间的通信。
2015年google I/0开发者大会发布的Data-binding库,使得开发者可以更加简洁优雅的编写代码实现复杂的业务逻辑,而不必去关注数据变更后UI View的更新问题。View的变化会直接影响ViewModel,ViewModel的变化或者内容也会直接体现在View上,这就是Data Binding Library默默帮您完成的工作。
Android官方Data Binding介绍:https://developer.android.com/topic/libraries/data-binding/index.html

Data Binding Requirements

The Data Binding Library offers both flexibility and broad compatibility — it's a support library, so you can use it with all Android platform versions back to Android 2.1 (API level 7+).
To use data binding, Android Plugin for Gradle 1.5.0-alpha1 or higher is required.
Also, make sure you are using a compatible version of Android Studio. Android Studio 1.3 and later provides support for data binding.

Data binding 是一个类似support-v4/v7的支持库,API 7+都可以使用;
Android Studio1.3+
Android Gradle插件 1.5.0 +

How To Use Data Binding?

要使用Data binding功能需要在你app module 的build.gradle中开启。

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

<h3>Data binding Layout

使用Data binding核心是将viewModel数据嵌入到布局layout xml文件,故而layout xml文件与之前的纯view布局文件的构成略有不同。

//以下为官方给出的最简单的一个示例:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
            name="user"
            type="test.example.com.databindingtest.User">
        </variable>
    </data>

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:id="@+id/firstname_text"
            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>

public class User {
   public final String firstName;
   public final String lastName;
   public User(String firstName, String lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
}

Data binding的布局文件的根节点为layout标签,由一个数据data标签以及一个根视图标签构成。
data标签声明了该View使用的数据模型viewModel为变量名为user的User类对象,然后在Root View标签下通过
@{“变量名" + "." + ”属性名/函数名“}来给指定的view设置值。

PS:

//对于User类的另一种写法
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;
   }
}

关于Data binding框架识别语句 @{user.firstName} 的一个机制是对于第一种写法访问User类的 firstName属性,而第二种写法则是访问User类的getFirstName()方法,或者也有可能访问firstName()方法如果存在的话。

<h3>Binding Data

编写完成data binding layout之后,类似IDE自动编译资源文件产生资源ID R文件,默认将以layout xml文件的名字为基础生成一个继承自ViewDataBinding的类,此例xml文件为activity_main.xml故而生成ActivityMainBinding.java
生成的ViewDataBinding类默认命名规则为,布局xml文件名去掉"_"以驼峰式写法+“Binding”,当然也支持自定义命名,后续提及

对于Android Studio IDE可以在
app/build/intermediates/classes/debug/+"对应包目录"+databinding文件夹下查看该生成的ViewDataBinding类


这里写图片描述

PS:注意这里生成的ActivityMainBinding内的以下几个属性和函数

public class ActivityMainBinding extends android.databinding.ViewDataBinding  {
    ..........
    .....
    // views
    //在layout xml内定义了id的 android:id="@+id/firstname_text"
    //Viewdatabinding将会以驼峰命名生成对应:
    //public final android.widget.TextView firstnameText;
    //通过获得的binding类以及view的id名称可以直接获取该view的引用,而不需要再findViewById
    public final android.widget.TextView firstnameText;

    //未设置id的view则只会定义为private,外部将不可访问
    private final android.widget.LinearLayout mboundView0;
    private final android.widget.TextView mboundView2;

    //layout内声明的变量,对应成员变量以及默认以驼峰命名生成set/get函数setUser
    //用于给dataBinding设置数据viewModel
    // variables
    private test.example.com.databindingtest.User mUser;
    ........
    public void setUser(test.example.com.databindingtest.User user) {
        this.mUser = user;
        synchronized(this) {
            mDirtyFlags |= 0x1L;
        }
        notifyPropertyChanged(BR.user);
        super.requestRebind();
    }
    public test.example.com.databindingtest.User getUser() {
        return mUser;
    }
    ........
    ..................
  }

略微查看默认生成的ActivityMainBinding类,可以看出binding类就是管理并维护View与View Model的关系的核心。包括给将user数据对应值赋值给对应的view视图;当user内值变化时更新视图;视图交互事件调用viewmodel的业务逻辑等。

ViewDataBinding相当于一个联系对应layout中View与对应ViewModel(data标签下的数据)的框架,我们使用时需要将对应的View和ViewModel实例对象传递给该ViewDataBinding框架,Data Binding库已经提供了多种方法来给实现:

@Override
protected void onCreate(Bundle savedInstanceState) {

   //获取ViewDataBinding实例对象的同时已经通过传递对应的View对象或者layout文件参数,
   //将对应的View实例对象传递给该ViewDataBinding框架
   
   //Activity最常用的取代之前的setContentView方法
   MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);
   //可直接通过id名称访问对应View变量
   dataBinding.firstnameText.setText("hello");
   
   //使用默认生成的ActivityMainBinding直接bind对应View
   dataBinding = ActivityMainBinding.bind(viewRoot);
   //或者如下
   dataBinding = ActivityMainBinding.inflate(getLayoutInflater());
   
   ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
    parent, attachToParent);
    
   //给DataBinding框架设置viewModel数据源User
   User user = new User("Test", "User");
   //set方法根据layout中声明的数据类型自动生成
   binding.setUser(user);
}

上述步骤完成运行即可发现,user各属性数据自动绑定到了各自的View上,但是这只体现了dataBinding单方面从ViewModel到View的数据绑定;如何实现user数据更新然后DataBinding自动更新UiView? View的交互事件如何绑定到ViewModel的业务逻辑?

Data Binding View Event Handling##

类似data binding提供的View的数据绑定,在layout的view标签内同样允许使用表达式直接引用相应的函数处理分发的事件。使用函数引用的View 属性由对应的事件listener的函数方法决定:

    //View一般有View.OnLongClickListener/ View.OnClickListener
    //对应的两个方法为onLongClick/onClick故而data binding在view标签下有如下属性:
    android:onClick="@{user.onLastNameClick}"
    android:onLongClick="@{user.onLastNameLongClick}"

若在User类加入以下函数,则在View内完整引用监听click事件写法如下:

public class User {
    ....
    ........
    public void onLastNameClick(View v){
        Log.d("Test"," onLastNameClick ");
    }
    public boolean onLastNameLongClick(View v){
        Log.d("Test"," onLastNameLongClick ");
        return false;
    }
    ....
    .......
}

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable
            name="user"
            type="test.example.com.databindingtest.User">
        </variable>
    </data>
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:id="@+id/firstname_text"
            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}"
            android:onClick="@{user.onLastNameClick}"
            android:onLongClick="@{user.onLastNameClick}"/>
    </LinearLayout>
</layout>

进行编译后运行即可发现点击交互事件可以顺利触发和处理。

Android官方对于View标签引用的事件处理函数的要求说明:

In your expressions, you can reference methods that conform to the signature of the listener method. When an expression evaluates to a method reference, Data Binding wraps the method reference and owner object in a listener, and sets that listener on the target view. If the expression evaluates to null, Data Binding does not create a listener and sets a null listener instead.

官方说明View处理事件引用的方法的签名要与对应的clickListener的方法签名相符,而方法的签名侧重的是方法名和方法参数的顺序、类型、个数;这里测试之后其实需要的是与对应的clickListener的方法的参数以及返回值一致。

编译期间将对View#onClick attribute的表达式引用的方法进行合法性检查,若是方法参数/返回值对应不上,则会出现编译错误:

Error:(26, 36) Listener class android.view.View.OnLongClickListener with method onLongClick did not match signature of any method user.onLastNameLongClick 

若编译正确,实质其实还是将该方法包装进一个对应的listener然后给view设置对应监听接口。

Data Binding listening data/properties changes

Data binding真正的好处提现在data(user)数据变化时,能够自动更新对应的UI显示,如何使用这个核心功能呢?
<h3>Observable 对象

private static class User extends BaseObservable {
   private String firstName;
   private String lastName;
    
   //Bindable注解告诉data binding框架需要侦听该值的变化
   //编译期间生成的类似R文件的一个BR文件将会有该属性的一个public资源Id
   @Bindable
   public String getFirstName() {
       return this.firstName;
   }
   @Bindable
   public String getLastName() {
       return this.lastName;
   }
   public void setFirstName(String firstName) {
       this.firstName = firstName;
       //通过对应属性的资源ID告诉data binding框架该属性发生变化需要更新ui什么的
       notifyPropertyChanged(BR.firstName);
   }
   public void setLastName(String lastName) {
       this.lastName = lastName;
       notifyPropertyChanged(BR.lastName);
   }
}

限定了修改属性数据的方法在对应的set方法内,所以在set方法更新属性后通知data binding框架属性的更新即可。

<h3>ObservableFields

其实与上述的BaseObservable对象是类似的原理,只是将整个类的范围缩小到个别需要侦听的属性上,提供了ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, and ObservableParcelable.

private static class User {
   public final ObservableField<String> firstName =
       new ObservableField<>();
   public final ObservableField<String> lastName =
       new ObservableField<>();
   public final ObservableInt age = new ObservableInt();
}

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

事实上每一个ObservableFields都继承自BaseObservable包含了单独一个属性值,内部默认封装了set/get方法,将上述BaseObservable的方法封装好了,也是在set方法之后通知data-binding框架做出一些更新操作。

<h3>Observable Collections

对于一些需要使用到list/map等集合数据类型来说有: ObservableArrayMap,ObservableArrayList等

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

<data>
    <import type="android.databinding.ObservableMap"/>
    <variable name="user" type="ObservableMap<String, Object>"/>
</data>
…
<TextView
   android:text='@{user["lastName"]}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text='@{String.valueOf(1 + (Integer)user["age"])}'
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

PS: Android Studio IDE目前并不能很完美的支持到data标签的一些变量的import或者是类型声明;但是并不会影响编译运行。


这里写图片描述
ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);

<data>
    <import type="android.databinding.ObservableList"/>
    <variable name="user" type="ObservableList<Object>"/>
</data>
…
<TextView
   android:text="@{user[0]}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>
<TextView
   android:text="@{String.valueOf(1 + (Integer)user[2])}"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"/>

Some Details

关于data标签支持import类似java的import,以及其对于一些类似map/list/array等集合类型的数据的语法简要介绍:

//下面的 < 为符号'<'字符实体
<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]}"

//集合类型数据都是类似数组使用的'[]'访问特定的数据,对于数组[]内是对应的下标,对于map则是对应的key值

PS: Map的key为一个String时,可能遇到引号内要使用引号包括字符串的冲突,这时使用:

//单引号在外包括,内部使用双引号标示字符串
android:text='@{map["firstName"]}'
//或者外部使用双引号,内部用back quote反引号标示字符串(反引号即'~'按键)
android:text="@{map[`firstName`}"

//或者使用"即双引号的java字符实体来替代双引号
android:text="@{map["firstName"]}"

About More Details

关于data binding的细节知识推荐阅读:
官方介绍文档:https://developer.android.com/topic/libraries/data-binding/index.html
比较全面的官方文档的翻译档:http://www.jianshu.com/p/b1df61a4df77
结合实例的介绍:https://github.com/LyndonChin/MasteringAndroidDataBinding

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

推荐阅读更多精彩内容