Android Data-Binding简记
来源:互联网 发布:程序员刷题的中文网站 编辑:程序博客网 时间:2024/06/05 06:27
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 }}
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()方法如果存在的话。
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库已经提供了多种方法来给实现:
@Overrideprotected 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显示,如何使用这个核心功能呢?
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框架属性的更新即可。
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框架做出一些更新操作。
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
- Android Data-Binding简记
- Android Data Binding 技术
- Android Data Binding学习
- Android Data Binding
- Android Data Binding
- android data binding
- Android Data Binding 技术
- Android Data Binding
- 精通 Android Data Binding
- Android Data Binding 技术
- Android Data Binding 用户指南
- Data Binding 用户指南(Android)
- Android Data Binding
- Android 双向Data Binding
- Android Data Binding Guide
- Android Data Binding实战
- Android Data Binding 使用
- Android Data Binding
- 调试JS 出现 ;window.onerror=function(){return!0};
- All host(s) tried for query failed (tried: / ip :9042
- unity 5.3.1播放视频文件
- 腾讯云+校园计划
- 数据结构实验之栈一:进制转换
- Android Data-Binding简记
- error:(NSError **)error
- 安卓ToolBar遮住内容
- OpenResty学习笔记(七) lua高阶之元表
- 移动端H5页面之iphone6的适配
- 循环链表的学习
- 利用 python 对文件夹下图片数据进行批量改名
- kernel starting 内核引导失败常见解决办法
- Spring 定时任务