1 Converters
1 Object Conversions
官方文档:
When an Object is returned from a binding expression, a setter will be chosen from the automatic, renamed, and custom setters. The Object will be cast to a parameter type of the chosen setter.
This is a convenience for those using ObservableMaps to hold data. for example:
<TextView
android:text='@{userMap["lastName"]}'//返回的Object自动转text属性的数据类型(CharSequence)
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
The userMap returns an Object and that Object will be automatically cast to parameter type found in the setter setText(CharSequence). When there may be confusion about the parameter type, the developer will need to cast in the expression.
Object类型的转换较为简单,它会根据所在属性的数据类型自动转换。
2 Custom Conversions
1 我的例子
本来引用的是官方文档的例子,无奈官方说明太过粗糙,挖了不少坑。啥也不说了,都是泪。
上代码:
XML:
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text='@{1}'/>//注意,此处text的属性本身类型应该问String,可是我却设置了一个int型的1,进去。
这样直接运行报错,信息如下:
android.content.res.Resources$NotFoundException: String resource ID #0x1
字面意思是说,资源没找到,这是什么意思呢?其实在这里引进去的1是作为R文件里面的id的,而不是简单的int型数据1,所以才说资源没找到。要解决问题,也只要把传进去的1转化为R文件里的一个id即可。官方提供了一个办法可以实现:通过一个带 BindingConversion 注解的静态方法实现。
代码如下:
@BindingConversion
public static int convertIntToString(int num) {//返回的必须为int,代表的是R文件里面的一个id
switch (num) {
case 1:
return R.string.red;//你以为能直接返回"红"么?!不行的
case 2:
return R.string.green;
}
return R.string.app_name;
}
附代码:http://download.csdn.net/detail/cherish20151011/9465821
2 官方例子
顺便也上下之前整理的官方文档:
Sometimes conversions should be automatic between specific types. For example, when setting the background:
<View
android:background="@{isError ? @color/red : @color/white}"//貌似不用转
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
Here, the background takes a Drawable, but the color is an integer. Whenever a Drawable is expected and an integer is returned, the int should be converted to a ColorDrawable. This conversion is done using a static method with a BindingConversion annotation:
上面的意思是说有时候需要让其自动转换,例如上面 background 属性数据类型应该为Drawable,而后面的选择返回的则是Integer。这样就需要它们自己进行转换。可是如何实现呢?文档也说了,这种自动转换可以通过一个带 BindingConversion 注解的静态方法实现。代码如下所示:()
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);//这一点和上面所述必须返回R的资源id有冲突,不是很理解
}
Note that conversions only happen at the setter level, so it is not allowed to mix types like this:
这句话是说上面的自动转换只发生在setter级别,所以不能像下面的代码一样混合不同的类型。
<TextView
android:background="@{isError ? @drawable/error : @color/white}"
android:text="@{user.firstName, default=PLACEHOLDER}"//后面那个属性就是设置AS上预览界面显示的默认值
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
2 自定义Binding
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);
3 Attribute Setters
1 Automatic Setters
每当绑定值的变化,生成的 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}"/>
以上是官方文档的解释。通俗一点讲就是,有了 Data Binding,即使属性没有在 declare-styleable 中定义,我们也可以通过 xml 进行赋值操作,只要有 setter 方法就可以这样为属性赋值。
可这到底什么作用呢?在自定义控件中,以往自定义属性是通过TypeArray类获取得到自定属性的在XML中的值,而现在使用DataBinding,只需要提供set方法就可以获取XML中的属性值。
首先看下自定义View:
public class MyView extends TextView {
private String MyName;
//该方法其实作用就是把XML设置的值(name)传递到当前View中的MyName,
public void setName(String name) {
this.MyName = name;
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
}
XML:
<com.example.ethan.autodatabinding.MyView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:name="@{`Test`}"/>//一个点的不是单引号,是“~”键的另一个
通过上面2个,其实就是把XML文件中的Test传递给了MyView中的MyName,妈妈再也不用愁我要去自定义属性里找值了~
附代码地址:http://download.csdn.net/detail/cherish20151011/9465690
2 Renamed Setters
1 意义
先看下官方文档说明:
Some attributes need custom binding logic. For example, there is no associated setter for the android:paddingLeft attribute. Instead, setPadding(left, top, right, bottom) exists. A static binding adapter method with the BindingAdapter annotation allows the developer to customize how a setter for an attribute is called.
以上说明告诉我们为什么要有自定义setter:其实就是有些setter并没有直接的意义,而是设置的值会有其他的一些逻辑来使用。
2 单属性
代码:
@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom());
以上代码的意思就是说当”android:paddingLeft”属性设置后就会通过注解BindingAdapter调用setPaddingLeft方法。
3 多属性
代码:
@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);
}
XML:
<ImageView
app:imageUrl=“@{venue.imageUrl}”
app:error=“@{@drawable/venueError}”/>
需要注意的地方官方也给了:
This adapter will be called if both imageUrl and error are used for an ImageView and imageUrl is a string and error is a drawable.
上面是一个典型的多属性例子。代码很明显。通过设置ImageView的属性,调用loadImage方法来加载图片。
在匹配适配器的时候, 会忽略自定义的命名空间你也可以为 android 命名空间的属性自定义适配器
另外需要注意的有:
1、 若绑定适配器的方法需要用到就的数据,则绑定的方法中需要带新、旧数据的参数,而且旧数据必须放在前面。
@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());
}
}
2、 事件处理适配器只能被带一个抽象方法的接口或者抽象类使用。
@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);
}
}
}
如果一个监听带有多个方法,我们就必须创建多个方法进行处理。
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewDetachedFromWindow {
void onViewDetachedFromWindow(View v);
}
@TargetApi(VERSION_CODES.HONEYCOMB_MR1)
public interface OnViewAttachedToWindow {
void onViewAttachedToWindow(View v);
}
可是上面的监听传参是固定的,改变一个监听将会影响到其他的。官方也给了解决的方案:
we must have three different binding adapters, one for each attribute and one for both, should they both be set.
@BindingAdapter("android:onViewAttachedToWindow")
public static void setListener(View view, OnViewAttachedToWindow attached) {
setListener(view, null, attached);
}
@BindingAdapter("android:onViewDetachedFromWindow")
public static void setListener(View view, OnViewDetachedFromWindow detached) {
setListener(view, detached, null);
}
@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"})
public static void setListener(View view, final OnViewDetachedFromWindow detach,
final OnViewAttachedToWindow attach) {
if (VERSION.SDK_INT >= VERSION_CODES.HONEYCOMB_MR1) {
final 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);
}
}
};
}
final OnAttachStateChangeListener oldListener = ListenerUtil.trackListener(view,
newListener, R.id.onAttachStateChangeListener);
if (oldListener != null) {
view.removeOnAttachStateChangeListener(oldListener);
}
if (newListener != null) {
view.addOnAttachStateChangeListener(newListener);
}
}
}
以下是官方的说明:
The above example is slightly more complicated than normal because View uses add and remove for the listener instead of a set method for View.OnAttachStateChangeListener. The android.databinding.adapters.ListenerUtil class helps keep track of the previous listeners so that they may be removed in the Binding Adaper.
By annotating the interfaces OnViewDetachedFromWindow and OnViewAttachedToWindow with @TargetApi(VERSION_CODES.HONEYCOMB_MR1), the data binding code generator knows that the listener should only be generated when running on Honeycomb MR1 and new devices, the same version supported by addOnAttachStateChangeListener(View.OnAttachStateChangeListener).
4 Final
这次主要是学习了类型转换以及属性设置的进阶操作。官方的使用说明也仅到此,而且目前似乎使用普及率还不够高,作为一个储备知识也是不错的,技多不压身嘛。
好了,本系列的学习也告一段落,下面就得多多去实现它。