View元素标签属性绑定事件
当在layout文件中的binding表达式与View绑定的变量值改变时,生成Binding类会在View上调用相应的setter方法。DataBinding库会自动调用相对应的方法去设置值。
- 自动设置属性值setter
对于每一个元素的属性,DataBinding都会去查找相关的方法如setXXX(属性名)来设置属性值。例如,对于TextView中的属性android:text=”@{ user.name}”,那么DataBinding就会去调用TextView中的setText(user.name)方法来设置属性值。这取决于@{ }中的变量数据类型,例如上述中的user.name是String,那么就会去查找调用setText(String),如果@{ user.age }中user.age是int,那么久会去查找调用setText(int)。所以必须注意Binding表达式即@{ } 中的数据类型,可以在@{ }中使用强制类型转换运算符()来转换需要的数据类型。
注意,在Layout布局文件中的View元素上,有些设置View的属性是默认没有提供的,例如android:onLongClick属性等等,但是使用DataBinding库就可以直接在layout中使用这些默认没有提供的属性。例如Button中没有提供android:onLongClick属性,但我们可以直接设置: -
<Button android:layout_width="100dp" android:layout_height="60dp" android:background="@drawable/bg_main_btn" android:text="Click me" android:onLongClick="@{mainActivity.onClick}" />
再例如,支持库控件DrawerLayout在layout中没有任何可以设置的属性,但可以使用DrawerLayout中提供的大量设置setter属性的方法:
<android.support.v4.widget.DrawerLayout android:layout_width="wrap_content" android:layout_height="wrap_content" app:scrimColor="@{@color/scrim}" app:drawerListener="@{fragment.drawerListener}"/> Renamed Setters
- 重命名设置属性方法setter或者为属性绑定setter方法
在一些View中的属性setter方法的后戳与这个属性的名字可能有点不一样,例如android:tint这个属性,它的setter方法名为 setImageTintList(ColorStateList),而不是setTint()。对于这些setter方法,我们可以使用@BindingMethods 注解来关联这个属性和setter方法。必须在一个类中为每一个被重命名的方法使用@BindingMethod 注解。例如: -
@BindingMethods({ @BindingMethod(type = "android.widget.ImageView", attribute = "android:tint", method = "setImageTintList"),//定义绑定的方法 }) })
- 自定义setter关联属性
在一些属性中并没有相对应的setter方法来设置这个属性,所以必须自定义绑定逻辑。例如,android:paddingLeft这个属性在View中并没有setPaddingLeft这样的方法,只有setPadding(left,top,right,bottom)这个方法存在来设置paddingLeft属性。我们可以使用@BindingAdapter注解来绑定一个静态绑定适配器方法和属性,然后当设置这个属性的时候就会调用绑定的静态方法。 -
在Android中每一个属性已经使用@BindingAdapter绑定方法了,例如下面的系统的实现:
@BindingAdapter("android:paddingLeft") public static void setPaddingLeft(View view, int padding) { view.setPadding(padding, view.getPaddingTop(), view.getPaddingRight(), view.getPaddingBottom()); }
我们可以自己创建一个新的绑定适配器来覆盖默认绑定数据的绑定适配器,例如我们可以设置imageView的加载使用Glide框架来加载:
首先创建一个ImageUtil工具类加载图片:public class ImageUtil { @BindingAdapter({"image"}) public static void loadImage(ImageView view, int imgUrl){ Glide.with(view.getContext()).load(imgUrl).into(view); } }
然后在layout布局文件中给app:image属性赋予url:
<data> <variable name="imgUrl" type="int"/> </data> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="10dp"> <ImageView android:layout_margin="20dp" android:layout_width="200dp" android:layout_height="200dp" app:image="@{imgUrl}" /> </LinearLayout>
在Activity中初始化imgUrl变量,然后DataBinding会自动调用上面的ImageUtil的loadImage的方法刷新imageView:
@Override protected void initView() { binding= DataBindingUtil.setContentView(this,R.layout.activity_main); binding.setImgUrl(R.drawable.headimg); }
注意:
1。@BindAdapter 中允许传递多个参数:public class ImageUtil { @BindingAdapter({"image","error"}) public static void loadImage(ImageView view, int imgUrl, Drawable error){ Glide.with(view.getContext()).load(imgUrl).error(error).into(view); } } <ImageView android:layout_margin="20dp" android:layout_width="200dp" android:layout_height="200dp" app:image="@{imgUrl}" app:error="@{@drawable/error}"/>
只要同时设置image和error属性才会调用loadImage方法,否则编译会报错,因为找不到只有一个参数的方法。注意方法中的第一个参数总是view,然后之后的参数顺序会匹配BindingAdapter中使用的属性顺序。
2。在匹配的过程中会忽略自定义命名空间,也就是app:
xmlns:app=”http://schemas.android.com/apk/res-auto”3。可以为android命名空间重新绑定适配器。
4。可以在Binding适配器中的函数处理程序获取上一次的旧值,在函数处理程序中旧值的参数位置会在新值的参数位置前面,例如:
@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()); } }
5。Binding适配器中的函数处理程序只能与接口或者只包含一个抽象方法的抽象类使用,例如:
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); } } }
6。当一个监听器中包含多个方法时,必须把这一个监听器分割成多个监听器。例如,View.OnAttachStateChangeListener监听器中包含两个方法: onViewAttachedToWindow() 和onViewDetachedFromWindow()。我们必须创建两个接口来区分这两个属性方法和处理它们。
@TargetApi(VERSION_CODES.HONEYCOMB_MR1) public interface OnViewDetachedFromWindow { void onViewDetachedFromWindow(View v); } @TargetApi(VERSION_CODES.HONEYCOMB_MR1) public interface OnViewAttachedToWindow { void onViewAttachedToWindow(View v); }
当改变其中一个监听器的时候会影响另一个监听器,所以我们必须创建3个不同Binding适配器来处理它们,例如:
@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); } } }
在上面的例子中使用ListenerUtil这个工具类,全限定名是android.databinding.adapters.ListenerUtil。这个类可以帮助追踪之前add的旧监听器,以便在BindingAdapter中的方法中remove这个旧监听器。
上面的例子因为有了add和remove监听器所以显得有点麻烦,对于一般的监听器如EditText的addTextChangedListener监听器,里面包含了3个方法beforeTextChanged,onTextChanged,afterTextChanged,我们可以直接在layout布局文件中为EditText绑定其中一个或所有事件处理方法:
<android.support.v7.widget.AppCompatEditText android:id="@+id/custom_text" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="3" android:onTextChanged="@{mainActivity.onTextChanged}" android:beforeTextChanged="@{mainActivity.beforeTextChanged}"/>
然后在MainActivity中添加事件处理函数,注意事件处理函数的参数列表必须和监听器方法中的方法的参数列表一致:
public void onTextChanged(CharSequence s, int start, int before, int count) { user.setName(String.valueOf(s)); Log.e("MVVM", user.getName()); } public void beforeTextChanged(CharSequence s, int start, int count, int after) { Log.e("MVVM",String.valueOf(s)); }