DataBinding使用教程详解
序
针对MVP和MVC架构的理解还是比较清晰的,但是微软早些提出的MVVM还不是很了解,只知道一个词在耳边很热——“数据驱动”。下面通过对MVVM在Android端的实践DataBinding的深入学习来加深对MVVM模式的理解。
DataBinding介绍
DataBinding——数据绑定,是Google对MVVM在Android上的一种实现,可以直接绑定数据到xml中,通过View层持有Model层的引用来实现单项绑定,达到数据的变化带动控件/页面发生变化。现在还支持双向绑定,控件/页面的变化能及时的带动相关数据发生变化。
import DataBinding Library
确保插件版本——Android Plugin for Gradle 1.5.0-alpha1 或者以上版本;AndroidStudio版本在1.3.0或者以上;
android { .... dataBinding { enabled = true }}
如果是老版本的AS或者是eclipse,可以在Android SDK manager里面下载最新的library然后添加到项目中使用。建议还是更新稳定版本的AS及其相关插件。
如果是较新版本的Android Gradle Plugin 3.1.0 Canary 6 ships 其中对编译进行了优化,需要在gradle.properties文件中加上
android.databinding.enableV2=true
基础用法
变量绑定
在最外层的布局套上一层layout如下:
"http://schemas.android.com/apk/res/android"> ... <原来的布局/>
< data>< /data>中间是对类的引用和变量定义的地方,在剩下的布局中就可以直接使用在这里定义好的变量了。
比如:我们定义一个简单的用户类User
package org.zy.demonew.modelview.model;public class User { public final String firstName; public final String lastName; public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; }}
然后再修改对应的布局文件activity_main.xml,将上面的数据对象用起来,添加几个控件(View)来和这个数据(Model)进行单项绑定。比如创建了一个TextView用来显示User对象的firstName属性,一个EditText用来显示User对象的lastName属性。
<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <import type="org.zy.demonew.modelview.model.User" /> <variable name="user" type="User" /> data> <LinearLayout android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent"> <android.support.constraint.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1.0" tools:context=".modelview.BasicFuncActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="20dp" android:text="@{user.firstName}" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" /> <EditText android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="@{handler.onClickLastName}" android:onTextChanged="@{handler.onTextChanged}" android:padding="20dp" android:text="@{user.lastName}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> android.support.constraint.ConstraintLayout> LinearLayout>layout>
那么接下来我们就要初始化对象,给对象赋值了。
首先在对应的Activity中获取DataBinding对象,在我们重写Activity的onCreate方法中将原来的setContentView去设置布局的方法替换成DataBindingUtil类的一个静态方法setContentView去设置布局:
DataBindingUtil.setContentView(this, R.layout.activity_main);
这时候我们最好build一下项目,项目就会自动生成一个继承 ViewDataBinding的类名为ActivityMainBinding的类,这个类就是我们用来初始化、赋值、设置监听的类。这个类的命名规则很简单:通过xml文件名生成,使用下划线分割大小写后面再加个Binding就好了。如果layout的名字是activity_login.xml那生成的类名就是ActivityLoginBinding。下面我们对上面的User对象进行初始化。
ActivityMainBinding activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);User user = new User("Zhang", "San");activityMainBinding.setUser(user);// 或者直接通过setVariable// activityMainBinding.setVariable(BR.obuser,user);
这样我们跑app的结果就是
对面上面的EditText我们可以通过android:text="@={user.lastName}"
来将EditTex上面的文字的变化实时反应到对应的User对象上去进行双向绑定。
上面只是绑定了一个类的对象,那么在xml里面也可以绑定一些方法、事件。
事件处理
DataBinding允许您编写表达式来处理从视图中分派的事件(例如onClick)。 除少数例外,事件属性名称由监听器方法的名称管理。 例如,View.OnLongClickListener的onLongClick()方法,因此该事件的属性名称是android:onLongClick。 处理事件有两种方法。
- Method References(方法引用):你可以直接引用和listener方法签名一致的方法。当一个表达式被解析为Method References,Data Binding会把该方法包装成一个listener并设置到对应的View。如果表达式被解析为null,Data Binding会设置成对应的listener为null。
- Listener Bindings(监听器绑定): 当事件发生的时候lambda表达式会运行。 DataBinding总是会创建一个监听器来和VIew绑定,一旦事件触发了,这个监听器就会运行lambda表达式。
Method References
比如我定义一个MyHandler来处理监听点击事件和EditText的文本变化的监听:
public class MyHandler { public void onClickLastName(View view) { Log.e("--zy--", "abc"); Toast.makeText(view.getContext(), "click!", Toast.LENGTH_SHORT).show(); view.setBackgroundResource(R.drawable.ic_launcher_background); } public void onTextChanged(CharSequence s, int start, int before, int count) { Log.e("--zy--", "text : " + s); }}
在xml布局文件中< data>< /data>之间添加申明:
"handler" type="org.zy.demonew.modelview.model.MyHandler" />
在之前定义的EditTex的监听器中加上方法引用:
"wrap_content" android:layout_height="wrap_content" android:onClick="@{handler.onClickLastName}" android:onTextChanged="@{handler.onTextChanged}" android:padding="20dp" android:text="@{user.lastName}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" />
然后需要在DataBinding实例中进行绑定:
activityMainBinding.setHandler(new MyHandler());
这样在你点击EditText和输入Text的时候会触发你在MyHandler中写的方法了。
Listener Bindings - lambda expression
我定义了一个类Presenter来处理保存用户数据的操做:
public class Presenter { public void onSaveClick(User user) { Log.e("--zy--", "save name: " + user.firstName + user.lastName); }}
在xml文件中添加引用和绑定调用的位置:
"presenter" type="org.zy.demonew.modelview.model.Presenter" />......"wrap_content" android:layout_height="wrap_content" android:padding="20dp" android:onClick="@{()->presenter.onSaveClick(user)}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" />
然后在DataBinding实例中绑定:
activityMainBinding.setPresenter(new Presenter());
避免监听器混淆
Listener Bindings非常强大,可以让你写出更易读的代码。另一方面,复杂的Listener表达式会让你的布局变得难以读懂、管理。Listener表达式应该尽量简单,只从UI传递有效数据到Callback的方法中。所有的业务逻辑都应该该在Callback的方法中实现。
这里存在一些特有的点击事件,它们的点击事件的属性并不是android:onClick。下面的表格列举了对应的属性:
Class | Listener setter | Attribute |
---|---|---|
SearchView | setOnSearchClickListener(View.OnClickListener) | android:onSearchClick |
ZoomControls | setOnZoomInClickListener(View.OnClickListener) | android:onZoomIn |
ZoomControls | setOnZoomOutClickListener(View.OnClickListener) | android:onZoomOut |
Data Object
任何一个POJO都可以用于Data Binding,但是修改了POJO数据UI并不会自动更新。Data Binding有3种数据改变通知机制:Observable Objects, Observable Fields, Observable Collections。一旦数据对象实现了这三种中的任意一种通知机制并且和UI绑定成功,那么UI就会跟随数据变化而发生变化——数据驱动UI变化。
(顺便提一下,你想更新UI还有个方法就是改变绑定的对象,那么BindingAdapter也会更新对应的UI)比如:User user = new User(“Zhang2”, “Yang!”);activityBasicBinding.setUser(user);
Observable Objects
DataBinding提供了一个基类BaseObservable,我们编写的类要继承它,然后在负责在getter方法加上@Bindable注解并且在setter方法中发出通知。当数据改变时通知UI进行更新。其中setter方法中主要是notifyPropertyChanged和notifyChange两个方法来发出通知,这两个方法的区别是:前者是特定的变量刷新而后者是刷新所有变量。现在我们定义一个BaseObservableUser类:
public class BaseObservableUser extends BaseObservable { public String firstName; private String lastName; public BaseObservableUser(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } @Bindable public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; notifyPropertyChanged(BR.firstName);// notifyChange(); } @Bindable public String getLastName() { return lastName; } public void setLastName(String lastName) { this.lastName = lastName; notifyPropertyChanged(BR.lastName);// notifyChange(); } @Bindable({"firstName", "lastName"}) public String getName() { return firstName + lastName; }}
activity_main2.xml
<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="obuser" type="org.zy.demonew.modelview.model.BaseObservableUser" /> data> <LinearLayout android:layout_width="match_parent" android:orientation="vertical" android:layout_height="match_parent"> <android.support.constraint.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1.0" tools:context=".modelview.BasicFuncActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="20dp" android:text="@{obuser.firstName}" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="20dp" android:text="@{obuser.getFirstName()}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/second_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="i am a button !" android:padding="20dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" /> android.support.constraint.ConstraintLayout> LinearLayout>layout>
BaseObservableObjectActivity.java
public class BaseObservableObjectActivity extends AppCompatActivity { ActivityMain2Binding binding; BaseObservableUser baseObservableUser; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);// setContentView(R.layout.activity_main); binding = DataBindingUtil.setContentView(this, R.layout.activity_main2); baseObservableUser = new BaseObservableUser("Zhang", "San"); binding.setObuser(baseObservableUser); binding.secondBtn.setClickable(true); binding.secondBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(v.getContext(),"!!!",Toast.LENGTH_SHORT).show(); //可以// BaseObservableUser baseObservableUser = new BaseObservableUser("Zhang W", "Yan!!");// binding.setObuser(baseObservableUser); //可以,或者setLastName使用notifyChange()// baseObservableUser.setLastName(new String("Yan!")); //不行,只能配合公开属性使用才行 baseObservableUser.setFirstName(new String("Zhang2")); } }); }}
通过对这个例子的尝试发现一个注意点:数据结构中setLastName用的是notifyChange那我的xml中定义obj.getLastName或者obj.lastName都是能刷新UI的,但是数据结构中setFirstName如果用的是notifyPropertyChanged firstName那我的xml中必须要定义成obj.firstName,如果定义成方法obj.getFirstName是不能刷新UI的。因为我们数据改变了通知的是变量obj.firstName而不会对这个属性(firstName)相关的所有方法都进行重新执行来刷新UI。
Observable Fields
Data Binding还提供了更细粒度、更方便的创建Observable类的方式,封装了一系列的ObservableFields:ObservableField, ObservableBoolean, ObservableByte, ObservableChar, ObservableShort, ObservableInt, ObservableLong, ObservableFloat, ObservableDouble, 和ObservableParcelable。这个使用起来就更简单了就和我们的基础数据类型差不多用就行了。比如定义一个ObservableFieldUser类:
public class ObservableFieldUser { public final ObservableField firstName = new ObservableField<>(); public final ObservableField lastName = new ObservableField<>(); public final ObservableInt age = new ObservableInt();}
activity_main3.xml
<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="user" type="org.zy.demonew.modelview.model.ObservableFieldUser" /> data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.constraint.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1.0"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="20dp" android:text="@{user.firstName}" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="20dp" android:text="@{user.lastName}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="20dp" android:text="@{@string/ageFormat(user.age)}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" /> android.support.constraint.ConstraintLayout> LinearLayout>layout>
strings.xml
<string name="ageFormat">Age: %1$sstring>
ObservableFieldObjectActivity.java
public class ObservableFieldObjectActivity extends AppCompatActivity { ActivityMain3Binding binding; ObservableFieldUser observableFieldUser; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);// setContentView(R.layout.activity_main); binding = DataBindingUtil.setContentView(this, R.layout.activity_main3); observableFieldUser = new ObservableFieldUser(); observableFieldUser.firstName.set("Zhang"); observableFieldUser.lastName.set("San"); observableFieldUser.age.set(26); binding.setUser(observableFieldUser); changeData(); } private void changeData() { new Handler().postDelayed(new Runnable() { @Override public void run() { observableFieldUser.firstName.set("Zhang3"); observableFieldUser.lastName.set("San3"); observableFieldUser.age.set(30); } }, 2000); }}
Observable Collections
对于我们使用的变量,除了一些基础类型那剩下的就是集合了,DataBinding为我们提供了Observable Collections:ObservableArrayMap, ObservableArrayList使用方法类似,直接上(代)码:
activity_main4.xml
<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <import type="android.databinding.ObservableArrayMap" /> <variable name="user" type="ObservableArrayMap<String, Object>" /> <variable name="userList" type="android.databinding.ObservableArrayList<Object>" /> data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <android.support.constraint.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1.0" tools:context=".modelview.BasicFuncActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="20dp" android:text='@{userList[0]}' app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="20dp" android:text='@{userList[1]}' app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="20dp" android:text='@{String.valueOf(userList[2])}' app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" /> android.support.constraint.ConstraintLayout> <android.support.constraint.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1.0" tools:context=".modelview.BasicFuncActivity"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="20dp" android:text='@{user["firstName"]}' app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="20dp" android:text='@{user["lastName"]}' app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="20dp" android:text='@{String.valueOf(user["age"])}' app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" /> android.support.constraint.ConstraintLayout> LinearLayout>layout>
ObservableCollectionActivity.java
public class ObservableCollectionActivity extends AppCompatActivity { ActivityMain4Binding binding; ObservableArrayMap userMap; ObservableArrayList
Layout Details
生成Binding对象/实例的方法
最常见的将对象绑定到布局中的方法是使用绑定类上的静态方法。inflate()方法:
@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MyLayoutBinding binding = MyLayoutBinding.inflate(getLayoutInflater());}
还有一个“备用”的inflate()方法:
MyLayoutBinding binding = MyLayoutBinding.inflate(getLayoutInflater(), viewGroup, false);
也可以:
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
有时候Binding的类型事先不知道,那我们可以用DataBindingUtil类的方法来创建Binging实例:
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId, parent, attachToParent);ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
如果你在Fragment, ListView, 或者是 RecyclerView adapter中使用DataBinding,建议你用这个生成的BInding类或者是DataBindingUtil 类 的 inflate() 方法:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);// orListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
Views With ids
现在我们用id标识的控件都不需要使用findViewById方法来获取控件的引用了,直接在Binding的实例中就有这个控件的成员变量,而且这种方式要比原来的findViewById要快,控件的命名方式也很简单,比如一个TextView的控件点击事件的设置:
id="@+id/second_btn" android:text="i am a button !" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="20dp"/>
activityMainBinding.secondBtn.setOnClickListener(...);
variable-变量
<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>
别名
type="android.view.View"/>type="org.zy.demonew.modelview.model.View" alias="CustomUser"/>
表达式语言
你可以使用下列的操作符和关键词在layout表达式中:
- Mathematical + - / * %
- String concatenation +
- Logical && ||
- Binary & | ^
- Unary + - ! ~
- Shift >> >>> <<
- Comparison == > < >= <=
- instanceof
- Grouping ()
- Literals - character, String, numeric, null
- Cast
- Method calls
- Field access
- Array access []
- Ternary operator ?:
不可以使用下列操作:
- this
- super
- new
- Explicit generic invocation
非空运算(Null Coalescing Operator)
android:text="@{user.displayName ?? user.lastName}"
等于
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
属性引用(Property Reference)
无论Data采用的是那种定义方法,POJO、Java Beans或ObservableFields,在DataBinding的语法中引用都用下面
的方式。
android:text="@{user.lastName}"
**资源文件**Resources
可以使用通常的语法在表达式中访问资源文件:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
格式化的strings 和plurals:
android:text="@{@string/nameFormat(firstName, lastName)}"android:text="@{@plurals/banana(bananaCount)}"
When a plural有多个参数, 参数都得加上:
Have an orange Have %d orangesandroid: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 |
includes
变量能够被传递到一个被databinding包含且其中的变量名一致的布局中去。比如我们include包含变量user的contact.xml和name.xml:
<?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>
但是Data binding不支持直接在merge element里面include child。如下的引用方式是不支持的:
<?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>
ViewStubs
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);// setContentView(R.layout.activity_main); activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); User user = new User("Zhang", "San"); activityMainBinding.setUser(user); activityMainBinding.viewstub.setOnInflateListener(new ViewStub.OnInflateListener() { @Override public void onInflate(ViewStub stub, View inflated) { ListItemBinding binding = DataBindingUtil.bind(inflated); User user = new User("Zhang", "Si"); binding.setUser(user); } }); inflateViewStub(); } private void inflateViewStub() { new Handler().postDelayed(new Runnable() { @Override public void run() { if (!activityMainBinding.viewstub.isInflated()) activityMainBinding.viewstub.getViewStub().inflate(); } }, 2000); }
<ViewStub android:id="@+id/viewstub" android:layout_width="match_parent" android:layout="@layout/list_item" android:layout_height="wrap_content" />
<?xml version="1.0" encoding="utf-8"?><layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="user" type="org.zy.demonew.modelview.model.User" /> data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="10dp" android:text="@{user.firstName}"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="10dp" android:text="@{user.lastName}"/> LinearLayout>layout>
Immediate Binding
当一个variable或者是observable object发生变化了, the binding 实例在下一帧来到时是在队列里的,到UI改变的时候中间有一段时间,如果你想要Binding必须立刻执行变化。可以使用 executePendingBindings() 方法。
类的静态方法
调用和方法引用类似。比如我想显示我名字的昵称。直接上代码了:
public class MyStaticHandler { public static String getNickName(User user){ return "NickName: Xiao "+user.lastName; }}"org.zy.demonew.modelview.model.MyStaticHandler" />... ... android:text="@{MyStaticHandler.getNickName(user)}" />
Advanced Binding
转换器 (Converters)
在xml中为属性赋值时,如果变量的类型与原先的不一致,通过Converters可以进行转换。比如你想在xml中设置背景色但是背景色要新增相关的资源文件才行,你想直接写上颜色的RGB值就完成颜色的赋值,比如:android:background="@{user.firstName!=null ? @string/colorPrimary : @string/colorAccent}
“这时候你可以定义如下的Converters:
public class BindingConverters { @BindingConversion public static ColorDrawable convertColorToDrawable(String color) { return new ColorDrawable(Color.parseColor(color)); }}
Binding adapters
对于每个Layout布局都有对应一个adapter来完成创建要求framework来设置对应的属性或者是监听的请求。比如the binding adapter能够通过调用setText()方法来设置text属性或者是调用setOnClickListener()方法来对点击事件添加一个监听。
DataBinding支持你可以去定义一个方法来设值、设置你自己想绑定的逻辑和设置返回数据的类型。
设置属性的值
一旦绑定的数据发生变化, the generated binding class 必然会调用一个setter方法在这个有binding表达式的view上,你可以让DataBinding自动决定这个,明确定义这个方法或者是提供一个一般的标准的逻辑来选择方法。
比如一个属性叫做example,DataBinding自动去找方法setExample(arg)-参数类型只要兼容就行。 只有这个属性的名字和类型在搜索方法的时候被使用到了,那么这个属性才会被分配命名空间。
比如android:text=”@{user.name}” ,DataBind会先去找一个方法setText(arg)这个方法的参数和user.getName()属性值要能对上,如果你定义的user.getName()是一个String类型的,那么我们的library会去找一个setText()方法是接受String参数的。如果表达式返回的是int类型,那么我们的library会去找一个setText()方法是接受int参数的。这个表达式必须返回正确的类型,实在不行的话你需要想办法转化这个返回值的类型。
如果你属性的名字不存在,Data binding也可以有效。你能通过使用Data binding来为任何的setter创建相关的属性。比如DrawerLayout没有任何的属性但是有很多setters。如下的布局文件会自动的使用方法setScrimColor(int)和setDrawerListener(DrawerListener)方法来作为app:scrimColor和app:drawerListener属性的setter:
.support.v4.widget.DrawerLayout android:layout_width="wrap_content" android:layout_height="wrap_content" app:scrimColor="@{@color/scrim}" app:drawerListener="@{fragment.drawerListener}">
自定义匹配属性名字的方法
有一些情况是你的setter和你的属性名称不匹配,这个时候你就可以使用注解@BindingMethods来完成转化的工作了。在@BindingMethods里面你可以定义很多@BindingMethod方法来完成属性名称不匹配的问题。比如android:tint的属性不用setTint()的setter而是使用setImageTintList(ColorStateList)方法:
@BindingMethods({ @BindingMethod(type = "android.widget.ImageView", attribute = "android:tint", method = "setImageTintList"),})
大多数情况下,您不需要重命名Android框架类中的setter。这些属性已经实现了使用名称约定来自动查找匹配的方法了。
提供一段标准的逻辑
比较普通的binding adapters使用 比如我们使用的android:text属性对应的adapter你可以通过android.databinding.adapters 包来获得。你可以创建一个自定义的binding adapter对控件的左间距进行处理。如下:
@BindingAdapter("android:paddingLeft")public static void setPaddingLeft(View view, int padding) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom());}
当然你也可以定义多个参数的:
@BindingAdapter({"imageUrl", "error"})public static void loadImage(ImageView view, String url, Drawable error) { Picasso.with(view.getContext()).load(url).error(error).into(view);}
我们在xml中
就可以使用这个方法了。
如果不需要所有参数都设置的话你可以使用 requireAll=false,比如图片的地址在对象中还没有被初始化这时候展示初始图片,如下:
@BindingAdapter(value={"imageUrl", "placeholder"}, requireAll=false)public static void setImageUrl(ImageView imageView, String url, Drawable placeHolder) { if (url == null) { imageView.setImageDrawable(placeholder); } else { MyImageLoader.loadInto(imageView, url, placeholder); }}
如果你一个值设置了多次,BindingAdapter的方法会选择性的去取老值(在他们的halders里面)。如果有新值、旧值我们的方法应该取老的值然后再取新的值执行。如下:
@BindingAdapter("android:paddingLeft")public static void setPaddingLeft(View view, int oldPadding, int newPadding) { if (oldPadding != newPadding) { view.setPadding(newPadding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); }}
对于事件处理我们只能使用一个接口或者是一个抽象类的抽象方法,如下:
@BindingAdapter("android:onLayoutChange")public static void setOnLayoutChangeListener(View view, View.OnLayoutChangeListener oldValue, View.OnLayoutChangeListener newValue) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { if (oldValue != null) { view.removeOnLayoutChangeListener(oldValue); } if (newValue != null) { view.addOnLayoutChangeListener(newValue); } }}
我们通过
来使用事件处理。
如果我们定义需要事件处理的listener有多个方法,那么必须要把它拆分成多个listener。比如View.OnAttachStateChangeListener有两个方法: onViewAttachedToWindow(View) 和onViewDetachedFromWindow(View)。你必须定义两个接口来区分他们的attributes和handlers。如下:
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)public interface OnViewDetachedFromWindow { void onViewDetachedFromWindow(View v);}@TargetApi(VERSION_CODES.HONEYCOMB_MR1)public interface OnViewAttachedToWindow { void onViewAttachedToWindow(View v);}
@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"}, requireAll=false)public static void setListener(View view, OnViewDetachedFromWindow detach, OnViewAttachedToWindow attach) { if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) { OnAttachStateChangeListener newListener; if (detach == null && attach == null) { newListener = null; } else { newListener = new OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { if (attach != null) { attach.onViewAttachedToWindow(v); } } @Override public void onViewDetachedFromWindow(View v) { if (detach != null) { detach.onViewDetachedFromWindow(v); } } }; } OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view, newListener, R.id.onAttachStateChangeListener); if (oldListener != null) { view.removeOnAttachStateChangeListener(oldListener); } if (newListener != null) { view.addOnAttachStateChangeListener(newListener); } }}
上面的例子比一般的例子稍微复杂一些,因为the View类使用the addOnAttachStateChangeListener()和removeOnAttachStateChangeListener()方法而不是用OnAttachStateChangeListener的一个setter方法。这个android.databinding.adapters.ListenerUtil类是用来追踪之前的listeners的,以便于他们能够被BindingAdapter回收。
这会让人联想到自定义控件中自定义属性值,我们要使用某个属性我们需要在values/styles里面定义好,然后在控件初始化的时候获取控件的对应属性值然后进行相应的操作,但是我们没有数据驱动来更新UI,我们大多数是将获取的值做一些初始化操作而现在可以对应一个变量值,变量值变化了这个对应的UI也可以改变了,而且你定义的这个Adapter不仅仅是单个控件可以使用,只要你定义的参数合理就能够被很好的复用,从这里能感受到DataBing非常实用和方便。
优点与不足
优点:原本在java文件中的findViewById,@BindView,@ViewInject等方式查找控件以及对其进行setText,setBackground,setVisible,onClickListener等操作,现在都可以放在xml中,java类和Activity的代码会少很多,结构也会清晰很多,运行效率提高很多。首先在MVC的架构下:登录模块大抵由负责页面布局的xml文件activity_login.xml和负责登录信息的Model文件User.java和拿有两者实例的控制层LoginActivity组成。如果有了DataBinding,那么LoginActivity只要负责绑定和解绑的操作,数据层的操作交给Model文件User.java,页面层的操作交给activity_login.xml,他们之间的实时交互交给ViewModel层,大大的降低原本在LoginActivity的各种逻辑处理、操作的耦合性。我们在有DataBinding的情况下编写程序只要搞清楚数据层有哪些基础数据和对应的操作,视图层与数据层的显示和交互关系,Activity负责绑定和解绑的操作就可以了。findViewById一直被诟病的效率低、速度慢现在通过databinding解决了。和MVP架构相比的代码量要少很多,结构清晰,使用更方便。
缺点:编写xml过程中不会提示出错,报错提示较难发现具体报错位置(不能像java文件报错那样告诉你是哪个文件的哪行),大多是给出哪个文件以及错误的原因是MethodNotFound、Could not find accessor类似这种提示;还有就是你的数据结构变化了无论是字段还是移动了文件的位置对应的xml都不会refactor但是如果你是rename包名对应文件都是会更新的,但如果在项目model包的最外面又添了一层包结构,结果是xml中的相关引用要自己手动修改的。。。总结为一点:AndroidStudio关于xml的校验支持还不是非常完美。
跋
既然DataBinding是解决Model层与View交互的问题的工具,加上现在双向绑定的支持也比较成熟了,我就写一个登录的例子来进一步感受一下DataBinding的优势,在Demo中可以下载查看,建议最好是自己编写相关的例子感受会更深刻。
参考文档:https://developer.android.com/topic/libraries/data-binding/index.html;
https://stackoverflow.com/questions/34294916/android-databinding-notifypropertychanged-not-working;
本文原创,转载请注明链接https://blog.csdn.net/zhangwude2301
Demo下载地址,如果网络比较慢,上GitHub样式都不能加载出来的话也可以在CSDN上下载,因为源码的压缩包很小。
更多相关文章
- 针对 CoordinatorLayout 及 Behavior 的一次细节较真
- Android(安卓)ListView理解之BaseAdapter
- 小记Activity生命周期(onCreate)
- unity和Android之间互相调用
- Android(安卓)Service更新UI的方法之AIDL
- android桌面小部件appwidget使用ListView或者StackView如何刷新
- android sharedpreference保存boolean,int,float,long,String和图片
- android Recyclerview实现类似朋友圈点击添加图片的view
- 《Android开发艺术探索》笔记(五)