DataBinding 是google 出的另一种开发模式,还有一个叫做黄油刀的,黄油刀在adapter 里面会自动的生成ViewHolder。
一个是在运行时赋值,一个是在编译时完成。
黄油刀 Databinding
如果报错:
Source folders generated at incorrect location
Warning:Folder C:\AdroidProjects\RecyclerViewDataBinding\app\build\intermediates\dataBindingInfo\debug
Warning:Folder C:\AdroidProjects\RecyclerViewDataBinding\app\build\intermediates\dataBindingInfo\androidTest\debug
Information:3rd-party Gradle plug-ins may be the cause
很有可能是下面这句话没有写好。
dataBinding {
enabled = true
}
现在开始讲Data Binding:
使用DataBinding 为了不使用findViewById,因为findViewById效率低
然后点击右上角的sync now,现在同步
然后添加:
2:adapter 里面可以使用这种方法。可以不使用findViewById了,也就可以不使用ViewHolder,对的。因为ViewHolder 就是为了减少findViewById的次数。
原来的set属性的后面的属性,都可以通过DataBinding 来直接指定。模型-视图-控制
View->Controller->Model->View
3:数据对象,Data Object
Let’s assume for now that you have a plain-old Java object (POJO) for User:
public class User { public final String firstName; public final String lastName; public User(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }
This type of object has data that never changes. It is common in applications to have data that is read once and never changes thereafter. It is also possible to use a JavaBeans objects:
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; } }
From the perspective of data binding, these two classes are equivalent. The expression @{user.firstName}
used for the TextView’s android:text
attribute will access the firstName
field in the former class and the getFirstName()
method in the latter class. Alternatively, it will also be resolved to firstName()
if that method exists.
从data binding 来看,这两种class 是一样的,在android:text 里面的表达式@{user.firstName}
将会访问firstName的属性在第一个class里面,
如果是第二个class的话,就会访问getFirstName的方法了。
同时,如果有firstName的方法的话,将会直接访问这个方法。
4:Data Binding,数据绑定
默认的情况下,一个绑定的类会自动的产生,这个是基于layout的文件,同时还会将这个将这个class转换为Pascal语言的case格式,同时将Binding后缀添加到这个class;
上面的布局文件是main_activity.xml.所以产生的类文件是MainAcitivityBinding,这个类将从布局文件中获得的所有绑定属性(例如user的变量)放到布局(layout)的视图上面
并且这个MainAcitivityClass 知道如何分配值给绑定的表达式。
最简单的创建绑定的方法是在进行inflating的时候做。
应该是,官网上面有问题了
ActivityMainBinding,而不是 MainActivityBinding
By default, a Binding class will be generated based on the name of the layout file, converting it to Pascal case and suffixing “Binding” to it. The above layout file was main_activity.xml
so the generate class was MainActivityBinding
. This class holds all the bindings from the layout properties (e.g. the user
variable) to the layout’s Views and knows how to assign values for the binding expressions.The easiest means for creating the bindings is to do it while inflating:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity); User user = new User("Test", "User"); binding.setUser(user); }
You’re done! Run the application and you’ll see Test User in the UI. Alternatively, you can get the view via:
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
If you are using data binding items inside a ListView or RecyclerView adapter, you may prefer to use:
ListItemBinding binding = ListItemBinding.inflate(layoutInflater, viewGroup, false);
//or
ListItemBinding binding = DataBindingUtil.inflate(layoutInflater, R.layout.list_item, viewGroup, false);
事件可能可以直接绑定到handler的方法,类似与android:onClick的方法可以分配给Activity的方法。
事件属性名字被有exceptions的监听(listener)的方法名字管理。例如,View.onLongClickListener 有一个方法叫做onLongClick()的方法,
所以呢,这个event的的属性名字就是android:onLongClick。
其实就是如果有set***Listener的就有一个这样的***属性
Events may be bound to handler methods directly, similar to the way android:onClick
can be assigned to a method in the Activity. Event attribute names are governed by the name of the listener method with a few exceptions. For example,View.OnLongClickListener
has a method onLongClick()
, so the attribute for this event is android:onLongClick
.
给一个handler分配event,需要用到绑定的表达式,这个表达式里面有一个值,这个值是要调用的方法名字,例如你的数据对象有两个方法:
To assign an event to its handler, use a normal binding expression, with the value being the method name to call. For example, if your data object has two methods:
注意这里面一定要写View view
public class MyHandlers { public void onClickFriend(View view) { ... } public void onClickEnemy(View view) { ... } }
The binding expression can assign the click listener for a View:
绑定的表达式可以将它发布
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="handlers" type="com.example.Handlers"/> <variable name="user" type="com.example.User"/> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}" android:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.lastName}" android:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/> </LinearLayout> </layout>
零个或多个import
元素可能在data
元素中使用。这些只用在你的layout文件中添加引用,就像在Java中:
<data>
<import type="android.view.View"/>
</data>
现在,View可以使用你的Binding表达式:
<TextView
android:text="@{user.lastName}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{user.isAdult ? View.VISIBLE : View.GONE}"/>
导入的类型还可以在表达式中使用static属性和方法:
<data> <import type="com.example.MyStringUtils"/> <variable name="user" type="com.example.User"/> </data> … <TextView android:text="@{MyStringUtils.capitalize(user.lastName)}" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
就像在Java中,java.lang。*
是自动导入的。
在data
中可以使用任意数量的variable
元素。每一个variable
元素描述了一个用于layout文件中Binding表达式的属性。
<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>
该Variable
类型在编译时检查,因此如果一个Variable实现了Observable或observable collection,这应该反映在类型中。(译注:需要查找资料来理解)如果variable是一个没有实现Observable接口的基本类或者接口,Variables不会被observed!
当对于多种配置有不同的layout文件时(如,横向或纵向),Variables会被合并。这些layout文件之间必须不能有冲突的Variable定义。
产生的Binding类对于每一个描述的Variables都会有setter和getter。这些Variables会使用默认的Java值 - null(引用类型)、0(int)、false(boolean)等等,直到调用setter时。
默认情况下,Binding类的命名是基于所述layout文件的名称,用大写开头,除去下划线()以及()后的第一个字母大写,然后添加“Binding”后缀。这个类将被放置在一个模块封装包里的databinding
封装包下。例如,所述layout文件contact_item.xml
将生成ContactItemBinding
。如果模块包是com.example.my.app
,那么它将被放置在com.example.my.app.databinding
。
Binding类可通过调整data
元素中的class
属性来重命名或放置在不同的包中。例如:
其实直接使用第一种就可以。
<data class="ContactItem">
...
</data>
在模块封装包的databinding包中会生成名为ContactItem
的Binding类。如果要想让该类生成在不同的包种,你需要添加前缀.
,如下:
<data class=".ContactItem">
...
</data>
在这个情况下,ContactItem
类直接在模块包种生成。或者你可以提供整个包名:
<data class="com.example.ContactItem">
...
</data>
通过使用application namespace以及在属性中的Variable名字从容器layout中传递Variables到一个被包含的layout:
<?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>
注意:在name.xml
以及contact.xml
两个layout文件中必需要有user
variable
常用表达式跟Java表达式很像,以下这些是一样的:
+
-
/
*
%
+
&&
||
&
|
^
+
-
!
~
>>
>>>
<<
==
>
<
>=
<=
instanceof
()
null
Cast
[]
?:
示例:
android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age < 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
缺少的操作:
this
super
new
??
- 左边的对象如果它不是null,选择左边的对象;或者如果它是null,选择右边的对象:
android:text="@{user.displayName ?? user.lastName}"
函数上的写法如下:
android:text="@{user.displayName != null ? user.displayName : user.lastName}"
第一个已经在前边提到了a)Data Binding表达式
:JavaBean引用的简短格式。
当一个表达式引用一个类的属性,它仍使用同样的格式对于字段、getters以及ObservableFields。
android:text="@{user.lastName}"
Data Binding代码生成时自动检查是否为nulls来避免出现null pointer exceptions错误。例如,在表达式@{user.name}
中,如果user
是null,user.name
会赋予它的默认值(null)。如果你引用user.age
,age是int
类型,那么它的默认值是0。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
常用的集合:arrays、lists、sparse lists以及maps,为了简便都可以使用[]
来访问。
<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]}"
当使用单引号包含属性值时,在表达式中使用双引号很容易:
android:text='@{map["firstName"]}'
使用双引号来包含属性值也是可以的。字符串前后需要使用"`":
android:text="@{map[`firstName`]}"
android:text="@{map["firstName"]}"
使用正常的表达式来访问resources也是可行的:
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
格式化字符串和复数可以通过提供参数来判断
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
当复数需要多个参数时,所有的参数都会通过:
Have an orange
Have %d oranges
android:text="@{@plurals/orange(orangeCount, orangeCount)}"
一些资源需要显式类型判断:
类型 | 正常引用 | 表达式引用 |
---|---|---|
String[] | @array | @stringArray |
int[] | @array | @intArray |
TypedArray | @array | @typedArray |
Animator | @animator | @animator |
StateListAnimator | @animator | @stateListAnimator |
color int | @color | @color |
ColorStateList | @color | @colorStateList |
任何Plain old Java object(POJO)可用于Data Binding,但修改POJO不会导致UI更新。Data Binding的真正能力是当数据变化时,可以通知给你的Data对象。有三种不同的数据变化通知机制:Observable
对象、ObservableFields
以及observable collections
。
当这些可观察Data对象绑定到UI,Data对象属性的更改后,UI也将自动更新。
实现android.databinding.Observable
接口的类可以允许附加一个监听器到Bound对象以便监听对象上的所有属性的变化。
Observable
接口有一个机制来添加和删除监听器,但通知与否由开发人员管理。为了使开发更容易,一个BaseObservable
的基类为实现监听器注册机制而创建。Data实现类依然负责通知当属性改变时。这是通过指定一个Bindable
注解给getter以及setter内通知来完成的。
private static class User extends BaseObservable {
private String firstName;
private String lastName;
@Bindable
public String getFirstName() {
return this.firstName;
}
@Bindable
public String getFirstName() {
return this.lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}
在编译期间,Bindable
注解在BR类文件中生成一个Entry。BR类文件会在模块包内生成。如果用于Data类的基类不能改变,Observable
接口通过方便的PropertyChangeRegistry
来实现用于储存和有效地通知监听器。
一些小工作会涉及到创建Observable类,因此那些想要节省时间或者几乎没有几个属性的开发者可以使用ObservableFields
。ObservableFields
是自包含具有单个字段的observable对象。它有所有基本类型和一个是引用类型。要使用它需要在data对象中创建public final字段:
private static class User extends BaseObservable {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
就是这样,要访问该值,使用set和get方法:
user.firstName.set("Google");
int age = user.age.get();
一些app使用更多的动态结构来保存数据。Observable集合允许键控访问这些data对象。ObservableArrayMap
用于键是引用类型,如String
。
ObservableArrayMap<String, Object> user = new ObservableArrayMap<>();
user.put("firstName", "Google");
user.put("lastName", "Inc.");
user.put("age", 17);
在layout文件中,通过String键可以访问map:
<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"/>
ObservableArrayList
用于键是整数:
ObservableArrayList<Object> user = new ObservableArrayList<>();
user.add("Google");
user.add("Inc.");
user.add(17);
在layout文件中,通过索引可以访问list:
<data>
<import type="android.databinding.ObservableList"/>
<import type="com.example.my.app.Fields"/>
<variable name="user" type="ObservableList<Object>"/>
</data>
…
<TextView android:text='@{user[Fields.LAST_NAME]}' android:layout_width="wrap_content" android:layout_height="wrap_content"/>
<TextView android:text='@{String.valueOf(1 + (Integer)user[Fields.AGE])}' android:layout_width="wrap_content" android:layout_height="wrap_content"/>
Binding类的生成链接了layout中variables与Views。如前面所讨论的,Binding的名称和包名可以定制。所生成的Binding类都扩展了android.databinding.ViewDataBinding
。
Binding应在inflation之后就立马创建,以确保View层次结构不在之前打扰layout中的binding到views上的表达式。有几个方法可以绑定到一个layout。最常见的是在Binding类上使用静态方法.inflate
方法载入View的层次结构并且绑定到它只需这一步。还有一个更简单的版本,只需要LayoutInflater
还有一个是采用ViewGroup
:
MyLayoutBinding binding = MyLayoutBinding.inflate(layoutInflater);
MyLayoutBinding binding = MyLayoutBinding.inflate(LayoutInflater, viewGroup, false);
如果使用不同的机制载入layout,他可一分开绑定:
MyLayoutBinding binding = MyLayoutBinding.bind(viewRoot);
有时Binding不能提前知道,对于这种情况,可以使用DataBindingUtil
类来创建Binding:
ViewDataBinding binding = DataBindingUtil.inflate(LayoutInflater, layoutId,
parent, attachToParent);
ViewDataBinding binding = DataBindingUtil.bindTo(viewRoot, layoutId);
在layout中对于每个带ID的View会生成一个public final字段。Binding在View层次结构上做单一的传递,提取带ID的Views。这种机制比起某些Views使用findViewById
还要快。例如:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}" android:id="@+id/firstName"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.lastName}" android:id="@+id/lastName"/>
</LinearLayout>
</layout>
它会生成如下的Binding类:
public final TextView firstName;
public final TextView lastName;
IDs不像没有Data Bindings那样几乎没有必要,但是仍然会有一些实例需要从代码中访问Views。
每个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>
它会在Binding中生成setters和getters:
public abstract com.example.User getUser();
public abstract void setUser(com.example.User user);
public abstract Drawable getImage();
public abstract void setImage(Drawable image);
public abstract String getNote();
public abstract void setNote(String note);
ViewStubs跟正常的Views略有不同。他们开始时是不可见的,当他们要么设置为可见或被明确告知要载入时,它们通过载入另外一个layout取代了自己。
由于ViewStub基本上从View的层次结构上消失,在Binding对象的View也必须消失来允许被收集。因为Views是最后的,一个ViewStubProxy
对象取带ViewStub,给开发者获得了ViewStub,当它存在以及还可以访问载入的View层次结构时当ViewStub已被载入时。
当载入另一个layout,为新的布局必需创建一个Binding。因此,ViewStubProxy
必需监听ViewStub
的OnInflateListener
监听器并在那个时候建立Binding。因为只有一个可以存在,ViewStubProxy
允许开发者在其上设置一个OnInflateListener
它会在建立Binding后调用。
有时,不知道具体的Binding类,例如,一个RecyclerView
适配器对layouts任意操作并不知道具体的Binding类。它仍然必需在onBindViewHolder
期间赋值给Binding。
在这个例子中,该RecyclerView
绑定的所有layouts有一个“item”的Variable。该BindingHolder
有一个getBinding
方法返回ViewDataBinding
。
public void onBindViewHolder(BindingHolder holder, int position) {
final T item = mItems.get(position);
holder.getBinding().setVariable(BR.item, item);
holder.getBinding().executePendingBindings();
}
当一个variable或observable变化时,binding会在下一帧之前被计划要改变。有很多次,但是在Binding时必须立即执行。要强制执行,使用executePendingBindings()
方法。
只要它不是一个集合,你可以在后台线程中改变你的数据模型。在判断是否要避免任何并发问题时,Data Binding会对每个Varialbe/field本地化。
每当绑定值的变化,生成的Binding类必须调用setter方法。Data Binding框架有可以自定义赋值的方法。
对于一个属性,Data Binding试图找到setAttribute
方法。与该属性的namespace并不什么关系,仅仅与属性本身名称有关。
例如,有关TextView的android:text
属性的表达式会寻找一个setText(String)
的方法。如果表达式返回一个int
,Data Binding会搜索的setText(int)
方法。注意:要表达式返回正确的类型,如果需要的话使用casting
。Data Binding仍会工作即使没有给定名称的属性存在。然后,您可以通过Data Binding轻松地为任何setter“创造”属性。例如,DrawerLayout
没有任何属性,但大量的setters。您可以使用自动setters来使用其中的一个。
<android.support.v4.widget.DrawerLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:scrimColor="@{@color/scrim}"
app:drawerListener="@{fragment.drawerListener}"/>
一些有setters的属性按名称并不匹配。对于这些方法,属性可以通过BindingMethods
注解相关联。这必须与一个包含BindingMethod
注解的类相关联,每一个用于一个重命名的方法。例如,android:tint
属性与setImageTintList
相关联,而不与setTint
相关。
@BindingMethods({
@BindingMethod(type = "android.widget.ImageView",
attribute = "android:tint",
method = "setImageTintList"),
})
以上例子,开发者需要重命名setters是不太可能了,android架构属性已经实现了。
有些属性需要自定义绑定逻辑。例如,对于android:paddingLeft
属性并没有相关setter。相反,setPadding(left, top, right, bottom)
是存在在。一个带有BindingAdapter
注解的静态绑定适配器方法允许开发者自定义setter如何对于一个属性的调用。
Android的属性已经创造了BindingAdapters
。举例来说,对于paddingLeft
:
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
}
Binding适配器对其他定制类型非常有用。例如,自定义loader可以用来异步载入图像。
当有冲突时,开发人员创建的Binding适配器将覆盖Data Binding默认适配器。
您也可以创建可以接收多个参数的适配器。
@BindingAdapter({"bind:imageUrl", "bind:error"})
public static void loadImage(ImageView view, String url, Drawable error) {
Picasso.with(view.getContext()).load(url).error(error).into(view);
}
<ImageView app:imageUrl=“@{venue.imageUrl}” app:error=“@{@drawable/venueError}”/>
如果对于一个ImageView
imageUrl和error都被使用并且imageUrl
是一个string类型以及error是一个drawable时,该适配器会被调用。
当从Binding表达式返回一个对象,一个setter会从自动、重命名以及自定义的setters中选择。该对象将被转换为所选择的setter的参数类型。
这是为了方便那些使用ObservableMaps
来保存数据。例如:
<TextView android:text='@{userMap["lastName"]}' android:layout_width="wrap_content" android:layout_height="wrap_content"/>
在userMap
返回一个对象并且该对象将自动转换为setText(CharSequence)
的参数类型。当有关参数类型可能混乱时,开发人员需要在表达式中转换。
有时候转换应该是自动的在特定类型之间。例如,设置背景的时候:
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
这里,背景需要Drawable
对象,但颜色是一个整数。不管何时有Drawable
并且返回值是一个整数,那么整数类型会被转换为ColorDrawable
。这个转换是通过使用带有BindingConversion
注解的静态方法完成的:
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
注意:转换仅仅发生在setter级别,因此它是不允许以下混合类型:
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
---------------------<完>-----------------------
(如有翻译有误或者不理解的地方,请指正)