本文是在前文的基础上继续深入DataBinding的使用这一块,如果有不懂的地方,请移步上一篇
Android Jetpack之DataBinding(一)
Android Jetpack之DataBinding(二)
绑定适配器负责发出相应的框架调用来设置值。如setText()、setOnClickListener()等,同时适配器绑定库允许自定义方法、逻辑、和
返回值类型等操作。
只要绑定的值放生改变,生成的绑定类就会调用对应的set方法实现相应的改变,看个最简单的例子如下,其实是在调用了响应的setText方法:
xml代码:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="student" type="com.zgj.demojetpackapp.bean.Student"/>
data>
<LinearLayout
android:gravity="center_horizontal"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_marginTop="@dimen/dimen_10"
android:id="@+id/tv_nick"
android:text="@{student.name}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_marginTop="@dimen/dimen_10"
android:id="@+id/tv_age"
android:text="@{student.age+``}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
LinearLayout>
layout>
class TestActivity7 : AppCompatActivity() {
var binding: ActivityTestDataBinding7Binding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(
this,
R.layout.activity_test_data_binding7
)
binding?.student = Student("小美女", 5)
}
}
binding?.student = Student("小美女", 5)
protected void executeBindings() {
....
//此处自动选择get方法取值
// read student.name
studentName = student.getName();
// read student.age
studentAge = student.getAge();
...此处进行值的设置
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvAge, studentAgeJavaLangString);
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.tvNick, studentName);
...
}
最终调用到了TextViewBindingAdapter的setText方法,再进入可以看到具体的设置过程,可以看到在进行了一些列的判断之后最终执行的方法是view的setText方法,和我们平时设置的过程是一样的。
@BindingAdapter(“android:text”)
public static void setText(TextView view, CharSequence text) {
final CharSequence oldText = view.getText();
if (text == oldText || (text == null && oldText.length() == 0)) {
return;
}
if (text instanceof Spanned) {
if (text.equals(oldText)) {
return; // No change in the spans, so don’t set anything.
}
} else if (!haveContentsChanged(text, oldText)) {
return; // No content changes, so don’t set anything.
}
// 这里
view.setText(text);
}
...
<variable name="listener"
type="com.zgj.demojetpackapp.databinding.TestActivity7.EventListener"/>
<TextView
android:padding="10dp"
android:text="点击事件"
android:onClick="@{listener::onClick}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
...
...
inner class EventListener {
fun onClick(view: View) {
Toast.makeText(this@TestActivity7, "点击按钮", Toast.LENGTH_SHORT).show()
}
}
...
binding?.listener = EventListener()
...
binding?.listener = EventListener()
protected void executeBindings() {
....
//声明了一个View本身的listener
android.view.View.OnClickListener listenerOnClickAndroidViewViewOnClickListener = null;
//这块是取到设置的listener
com.zgj.demojetpackapp.databinding.TestActivity7.EventListener listener = mListener;
...
//对上面声明的变量原生listener进行赋值,三目运算符操作,是一个新的实现类OnClickListenerImpl
// read listener::onClick
listenerOnClickAndroidViewViewOnClickListener = (((mListenerOnClickAndroidViewViewOnClickListener == null) ? (mListenerOnClickAndroidViewViewOnClickListener = new OnClickListenerImpl()) : mListenerOnClickAndroidViewViewOnClickListener).setValue(listener));
...
//最终将声明的原生监听器设置给setOnClickListener方法
this.mboundView3.setOnClickListener(listenerOnClickAndroidViewViewOnClickListener);
...
}
OnClickListenerImpl代码如下:
// Listener Stub Implementations
public static class OnClickListenerImpl implements android.view.View.OnClickListener{
//持有传入的listener
private com.zgj.demojetpackapp.databinding.TestActivity7.EventListener value;
public OnClickListenerImpl setValue(com.zgj.demojetpackapp.databinding.TestActivity7.EventListener value) {
this.value = value;
return value == null ? null : this;
}
@Override
public void onClick(android.view.View arg0) {
//此处调用传入的listener的方法实现回调
this.value.onClick(arg0);
}
}
此处可以看出listener具体绑定的实现,当然也可以发现前面说过的问题原因,
listener的监听器的方法名和参数必须和原生的一样(value.onClick(arg0))。
此处不做过多的说明,一般来说系统的方法都能自动选择实现绑定,可以看下TextViewBindingAdapter的源码,
此处进行了自定义方法的绑定,如下:
@BindingMethods({
@BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"),
@BindingMethod(type = TextView.class, attribute = "android:drawablePadding", method = "setCompoundDrawablePadding"),
@BindingMethod(type = TextView.class, attribute = "android:editorExtras", method = "setInputExtras"),
@BindingMethod(type = TextView.class, attribute = "android:inputType", method = "setRawInputType"),
@BindingMethod(type = TextView.class, attribute = "android:scrollHorizontally", method = "setHorizontallyScrolling"),
@BindingMethod(type = TextView.class, attribute = "android:textAllCaps", method = "setAllCaps"),
@BindingMethod(type = TextView.class, attribute = "android:textColorHighlight", method = "setHighlightColor"),
@BindingMethod(type = TextView.class, attribute = "android:textColorHint", method = "setHintTextColor"),
@BindingMethod(type = TextView.class, attribute = "android:textColorLink", method = "setLinkTextColor"),
@BindingMethod(type = TextView.class, attribute = "android:onEditorAction", method = "setOnEditorActionListener"),
})
此处以加载图片为例,ImageView只有一个src设置图片,没有办法根据特殊情况,如路径问题、网络问题导致的图片加载不成功进行相关的
加载过程,一般来说项目中加载图片会有网络路径、占位图和错误图,这里通过自定义逻辑实现这个过程。
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable name="url" type="String"/>
data>
<LinearLayout
android:gravity="center_horizontal"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iv_img"
app:url="@{url}"
android:layout_width="match_parent"
android:layout_height="200dp"/>
LinearLayout>
layout>
class TestActivity7 : AppCompatActivity() {
var binding: ActivityTestDataBinding7Binding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(
this,
R.layout.activity_test_data_binding7
)
binding?.student = Student("小美女", 5)
binding?.listener = EventListener()
val url =
"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1589877841442&di=55353d36cb62454230a0b86e90bd0c36&imgtype=0&src=http%3A%2F%2Fpic1.win4000.com%2Fwallpaper%2F2018-11-06%2F5be15b7635e5d.jpg"
binding?.imgBean = ImgBean(url, R.mipmap.error, R.mipmap.placeholder)
}
companion object {
@BindingAdapter(value = ["url", "error", "placeholder"], requireAll = false)
@JvmStatic
fun loadImg(view: ImageView, url: String, err: Int?, placeholder: Int?) {
Glide.with(view.context).load(url).also {
placeholder?.let { it1 -> it.placeholder(it1) }
err?.let { it1 -> it.error(it1) }
}.into(view)
}
}
}
效果如下(第一个网络图加载成功,第二个只有占位图(展示占位图),第三个只有错误图(或者错误图占位图都有,这两种都是展示错误图)):
当声明或者返回的对象为Object类型时候,会选择设置值的方法进行设置,Object会转换为所选方法的参数类型,例如:
...
<variable name="info" type="Object"/>
...
<TextView
android:text="@{info}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
...
//注意此处类型,不能直接写个1 那样会报类型转换错误
binding?.info ="123456789"
效果如下:
转换的位置是在ActivityTestDataBinding7BindingImpl的executeBindings方法中,代码如下:
protected void executeBindings() {
...
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView5, (java.lang.CharSequence) info);
...
}
自定义转换这块,这块提供两个示例,一个是boolean转换drawable,一个是Date转换String,代码如下:
...
<variable name="isException" type="boolean"/>
<variable name="date" type="java.util.Date"/>
...
<TextView
android:layout_margin="@dimen/dimen_10"
android:padding="@dimen/dimen_10"
android:text="展示内容"
android:textColor="@android:color/white"
android:background="@{isException? @android:color/holo_red_dark: @android:color/darker_gray}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_margin="@dimen/dimen_10"
android:padding="@dimen/dimen_10"
android:text="展示内容1"
android:textColor="@android:color/white"
android:background="@{isException}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:text="@{date}"
android:layout_margin="@dimen/dimen_10"
android:padding="@dimen/dimen_10"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
...
//转换器单独定义类 @JvmStatic表示生成静态方法
object BindingConverts {
@BindingConversion
@JvmStatic
fun convertDateToString(date: Date): String {
return SimpleDateFormat.getDateInstance(0).format(date)
}
@BindingConversion
@JvmStatic
fun convertBooleanToDrawable(back: Boolean): Drawable {
return if (back) {
ColorDrawable(Color.RED)
} else {
ColorDrawable(Color.DKGRAY)
}
}
}
展示效果如下(三个TextView对应三个效果,刚开始前两个背景为灰色,2s之后会变色,因为延时2s修改了isException的值):
protected void executeBindings() {
...
androidx.databinding.adapters.ViewBindingAdapter.setBackground(this.mboundView6, androidx.databinding.adapters.Converters.convertColorToDrawable(isExceptionMboundView6AndroidColorHoloRedDarkMboundView6AndroidColorDarkerGray))
...
}
最终调用了Converters的convertColorToDrawable的方法,找到该方法如下,很清楚很明白,还是转换器,只是系统已经定义好了
public class Converters {
/**
* Converts {@code int} color into a {@link ColorDrawable}.
*
* @param color The integer representation of the color.
*
* @return ColorDrawable matching the color
*/
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
/**
* Converts {@code int} color into a {@link ColorStateList}.
*
* @param color The integer representation of the color.
*
* @return ColorStateList from the single color
*/
@BindingConversion
public static ColorStateList convertColorToColorStateList(int color) {
return ColorStateList.valueOf(color);
}
}
protected void executeBindings() {
...
//boolean转换Drawable
androidx.databinding.adapters.ViewBindingAdapter.setBackground(this.mboundView7, com.zgj.demojetpackapp.databinding.BindingConverts.convertBooleanToDrawable(isException));
...
//date转换String
androidx.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView8, com.zgj.demojetpackapp.databinding.BindingConverts.convertDateToString(date));
...
}
报警告,其实报的很明白,这块声明isException为Boolean类型为装箱类型,需要拆箱后进行操作,当然此处不处理运行也是正常的,将Boolean改成boolean就不再包警告了
警告: isException is a boxed field but needs to be un-boxed to execute isException ? @android:color/holo_red_dark :
@android:color/darker_gray. This may cause NPE so Data Binding will safely unbox it. You can change the expression
and explicitly wrap isException with safeUnbox() to prevent the warning
自定义转换器的时候注意将转换器单独写在一个object声明的类中,如BindingConverts ,不要写在companion object 伴生对象中,如果写进去会报错如下:
提示你BindingConversion注解只能用在public static 方法上,但是其实此时也生成public static方法,我们通过查看对应的java代码如下:
...
@JvmStatic
public static final void loadImg(@NotNull ImageView view, @NotNull String url, @Nullable Integer err, @Nullable Integer placeholder) {
Companion.loadImg(view, url, err, placeholder);
}
...
也生成了静态方法,但是系统仍然报错,报错在伴生类的方法上
@BindingConversion
@JvmStatic
@NotNull
public final String convertDateToString(@NotNull Date date) {
Intrinsics.checkParameterIsNotNull(date, "date");
String var10000 = SimpleDateFormat.getDateInstance(0).format(date);
Intrinsics.checkExpressionValueIsNotNull(var10000, "SimpleDateFormat.getDateInstance(0).format(date)");
return var10000;
}
至于这个的具体原因目前还没有探究清楚。
源码链接:源码
1、databinding绑定适配器的使用及分析先到此处。
2、本以为这块适配器就是个小内容,准备分析完分析双向绑定相关的内容,但是这块的使用及坑还不少,限于篇幅,此篇先到此处结束,
后续再去分析双向绑定相关的内容。
3、以上的代码都是经过测试正常的,但是难免在粘贴或者修改的时候会有粗心或者错误,欢迎指出讨论。
4、以上的注意问题都是自己踩过的坑,当然也可能由于各方面的局限性会存在偏见,欢迎指出讨论。
5、自勉,多研究多使用,深入其源码往往会有意想不到的收获,同时也更容易消化相关的只是与内容。