【版权申明】非商业目的注明出处可自由转载
博文地址:https://blog.csdn.net/ShuSheng0007/article/details/106308380
出自:shusheng007
系列:
秒懂Android开发之DataBinding详解 (part 1)
秒懂Android开发之DataBinding详解 (part 2)
概述
在秒懂Android开发之DataBinding详解 (part 1)一文中我们已经了解并上手了DataBinding,意味着你可以干活了。 接下来就该看看其绑定规则及 BindingAdapter了,学习了DataBinding而不理解绑定原理及BindingAdapter的话,只能算是学到了皮毛。
后面行文中以DB作为DataBinding的缩写, BA作为BindingAdapter的缩写
DataBinding的绑定规则
你是否想过我们在layout
文件中对TextView使用了绑定表达式后,系统是如何为其赋值的呢?以及我们可以修改TextView的哪些特性呢?例如字体颜色可以使用表达式吗?字体大小呢?背景可以吗?文字超出Textview宽度后的结束方式可以吗?等等这些问题都应该搞清楚,正是因为这些原理性的知识将程序员分为了庸俗和优秀两类。不理解原理,一旦遇到特殊需求或者异常情况就会显得束手无策。
绑定原理
DataBinding通过自动生成代码的方式帮助程序员省去了赋值操作,整体生成的代码比较多,最核心的是那个绑定类的具体实现类。例如我的layout
文件生成绑定类为ActivityDataBindingBinding
,那么核心类为其子类ActivityDataBindingBindingImpl
。
有兴趣的应该研究一下这个生成代码的实现,由于是插件生成的,可读性稍差,但仍然是可以阅读的。
绑定规则
绑定规则分3种情况
- 由类库按规则自动选择绑定方法
此方式系统会尝试调用那个属性的setter方法,例如我们为Textview的透明度赋值 android:alpha=“@{xxx}”
那么类库就会寻找Textview里面的setAlpha(arg)。如果TextView里面有好几个setAlpha的重载,由表达式xxx
的返回类型确定调用哪一个,所以这个表达式返回值的类值型很重要。
只要是Android Framwork 提供了的属性都可以直接使用data binding 的赋值表达式,这里说的属性就是你可以在layout
文件中使用的那些。
data binding甚至可以绑定View中那些不存在对应属性的setter方法。 例如DrawerLayout
中存在 setScrimColor(int)
这样一个set方法,但是不存在可以在layout
中使用的属性,所以没有DB的时候我们只能从代码中调用,不能在layout
文件中设置。但是使用DB就可以在layout
文件中进行绑定了,如下代码所示。
<androidx.drawerlayout.widget.DrawerLayout
...
app:scrimColor="@{xxx}"/>
- 自定义绑定方法名称
从第一条我们了解到,系统会去找属性对应的setter
方法, 那如果属性和对应的setter方法没有遵循传统格式怎么办呢?例如 Textview的一个属性叫 autoLink
但是其对应的设置方法叫setAutoLinkMask()
而不是叫setAutoLink()
,那系统就找不到对应的设置方法了,那怎么办呢?
使用@BindingMethod
注解来对其重命名, 这个注解可以放在任何类上面,一般是放在你正在处理的类上面。下面的代码将绑定方法重命名了,通过这个注解系统就知道给autoLink
赋值时调用setAutoLinkMask()
而不是setAutoLink()
方法
@BindingMethods({
@BindingMethod(type = TextView.class, attribute = "android:autoLink", method = "setAutoLinkMask"),
...
})
public class TextViewBindingAdapter {}
对于Android Framwork的属性,即以android:
开头的属性都不用管,DB已经帮忙做好了。但是如果你自己写了一个扩展Textview的自定义View, 自定义属性发生了这种问题,你就需要按照上面的方法处理了。所以你就不要找不自在了,老老实实的按照setter的语法书写!
- 自定义赋值方法名称和逻辑
终于到了BindingAdapter出场的时候了。如果一个View的属性没有Setter方法怎么办呢?或者你想为一个View新加一个自定义属性怎么办呢?BindingAdapter 为此而生。
例如 android:paddingLeft
属性就不存在相应的setter方法,那我们就可以使用BA使其可以在layout
文件中绑定。Android Framework 已经为我们实现了这个绑定适配器,如下代码所示:
@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, padding: Int) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom())
}
明确了上面的绑定规则,写起代码来也就能做到心中有数了。 接下来就让我们近距离观察一下BindingAdatper
吧。
BindingAdatper
通过前面的阅读,相应你已经清楚BindingAdatper就是DataBiding库用来为layout
中View设置值的,包括属性和事件,的一种补充机制, 其最有魅力之处在于可以自定义名称和逻辑。
例如下面代码中对 android:text
值的绑定就是使用了DataBinding预先实现好的BindingAdapter。这里需要提一下的是如果自定义BA与前的绑定规则冲突,那么后者胜!
<TextView
android:id="@+id/tv_girl"
...
android:text="@{viewModel.targetGirl.name}"
app:wrapWithSymbol='@{viewModel.symbol}'/>
DataBinding预先为很多View实现了各种BindingAdapter,这些你可以从你project的External Libaries里面的
androidx.databinding:databinding-adapters:xxx
找到,如下图所示:
我们简单来看一下TextViewBindingAdapter
,这个是DataBinding 为TextView
实现的BindingAdapter
@BindingAdapter("android:text")
public static void setText(TextView view, CharSequence text) {
...
ew.setText(text);
}
@InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
public static String getTextString(TextView view) {
return view.getText().toString();
}
可以看到对应Textview的text
属性有两个BindingAdapter,一个正向的,一个反向的,反向的用于双向绑定,这个后面再说。layout
文件中TextView 的text
属性的绑定表达式就是由上面那个正向BindingAdapter负责赋值的。
通过BindingAdapter,我们可以为某个View使用自定义的属性,例如前面例子中的 app:wrapWithSymbol='@{viewModel.symbol}'
就是我给Textview自定义的一个属性,这个绑定适配器的功能给人的感觉就和Kotlin里面的扩展函数似的,只是其扩展的对象是各种View.
如何写一个BindingAdapter
又到了躬行的时候了,不然终觉浅,让我们动手写一个简单的BA吧。其实很简单,可以分为3步:
- 写一个
public static
方法。 - 确定方法入参,第一个入参为要绑定的View类型,例如Textview。第二个参数为要绑定的属性的值
- 使用
@BindingAdapter
注解标记,其参数为自定义属性名称。名称可以是任意字符串,不要带命名空间,例如app:wrapWithSymbol
这种方式是不提倡的,因为在查找BA的时候命名空间是会被忽略的,还一堆警告。
完成以上3步后一个BindingAdapter就写好了,当然了,你要在方法体里面写上逻辑。举个例子?sure!
先上一个Java版本吧,比较容易理解
public class MyDataBindingAdapters {
@BindingAdapter("wrapWithSymbol")
public static wrapWithSymbol(TextView view, String symbol) {
view.text = symbol+view.getText()+symbol;
}
}
对应的kotlin版本如下,稍微复杂一点,其中@JvmStatic
就是为了让kotlin编译器产生静态函数的。
object MyDataBindingAdapters {
@JvmStatic
@BindingAdapter("wrapWithSymbol")
fun wrapWithSymbol(view: TextView, symbol: String) {
view.text = "$symbol${view.text}$symbol"
}
}
BindingAdapter 里的参数wrapWithSymbol
就是我们在layout文件中要使用的属性名称,可以任意定义。 方法的入参很重要,DB通过入参的类型去匹配BindingAdapter 。
多属性BindingAdapter
自定义BindingAdapter本来就不是太常用,用到的话大多也是单属性的,然而查看@BindingAdapter
注解可以发现,它是允许传入多个值的。
@Target(ElementType.METHOD)
public @interface BindingAdapter {
String[] value();
boolean requireAll() default true;
}
第一个方法类型为数组,第二个为bool,表示数组中传入的属性是否都必须赋值。
例如我们可以定义如下一个包含两个属性的BindingAdapter
@JvmStatic
@BindingAdapter(value=["imageUrl", "error"],requireAll = true)
fun loadImage(view: ImageView, url: String, error: Drawable) {
Picasso.get().load(url).error(error).into(view)
}
可以在layout
文件中按照如下方式调用
<ImageView
...
app:imageUrl="@{viewModel.imageUrl}"
app:error="@{@drawable/venueError}" />
因为我们设置了requireAll = true
,所以在layout
中这两个属性都必须赋值.
至此本文应该结束了,但是我们稍微扩展一下事件的绑定,如果没有兴趣的就可以散场了。
事件绑定
前面我们一直在谈论属性的赋值,没有具体谈论对事件的绑定,DB绑定事件时有两种方式:
- Method references
这种方式要求我们的业务逻辑类里的方法签名必须与对应的Listener
里面的方法在返回值和入参上保持签名一致。例如View的OnClickListener源码如下
public interface OnClickListener {
void onClick(View v);
}
那么我们的handler方法签名必须与Listener里的onCLick方法一致,即返回类型为void
,入参为1个View类型
class MyHandlers {
fun onClickFriend(view: View) { ... }
}
这样我们就可以在layout
文件中以这种方式绑定事件了,如下代码所示
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.MyHandlers"/>
</data>
<LinearLayout
...
>
<TextView
...
android:onClick="@{handlers::onClickFriend}"/>
</LinearLayout>
</layout>
这种方式在事件进行绑定的时候系统已经为我们生成了相应Listener的实现了。
- Listener bindings
这种方式就比较灵活了,相应Listener的实现类在事件触发的时候才生成。
只要求处理类的方法返回值类型与对应Listener
一致即可,例如还是绑定View 的Click事件,我们的处理类中的方法可以传入任意个数和类型的参数了
class MyHandlers {
fun onClickFriend(girl: Girl) { ... }
}
我们可以使用如下绑定
<data>
<variable name="girl" type="com.android.example.Girl" />
<variable name="handlers" type="com.example.MyHandlers"/>
</data>
<Button
...
android:onClick="@{() -> handlers.onClickFriend(girl)}" />
OnClickListener 里onClick(View v);的参数v
可以省略,也可以不省略,但是要求:要不全传,要不全省。
<Button
...
android:onClick="@{(v) -> handlers.onClickFriend(girl)}" />
之所以讲上面两种事件绑定方式,主要是想谈论一下如何绑定非函数式接口的监听,换句话说,Listener里面不止一个方法。例如View 中有这么一个接口,它有两个方法,那么就不能直接绑定,怎么办呢?
/**
* Interface definition for a callback to be invoked when this view is attached
* or detached from its window.
*/
public interface OnAttachStateChangeListener {
public void onViewAttachedToWindow(View v);
public void onViewDetachedFromWindow(View v);
}
答案是使用BindingAdapter,但即使使用BA也是不容易的,需要按照如下方式处理:
- 将原来的接口拆成多个接口,每个方法对应一个。
@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多属性方式处理
@BindingAdapter({"android:onViewDetachedFromWindow", "android:onViewAttachedToWindow"}, requireAll=false)
public static void setListener(View view, OnViewDetachedFromWindow detach, OnViewAttachedToWindow attach) {
...
}
- 绑定
<TextView
...
android:onViewDetachedFromWindow="@{() -> handlers.onClickFriend()}"/>
总结
不知不觉嘚嘚了这么多,我将自己认为正确打开DataBinding的那些事都尽量说清楚了,但正所谓师父领进门修行在个人,就像俺高中时候一个老师教育俺们的:“考清华北大的学生根本就不是教出来的,而是自己学出来的”。其实他说的很对,因为他自己只是山西临汾师范学院毕业的。
so,加油吧,少年!
下一篇就是收宫之篇了,我们谈一下双向绑定。
本文源码:AndroidDevMemo
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/14730.html