这是第「25篇」原创分享
看了谷歌官方文章确实写的太简略了,甚至看完之后有很多地方还不知道怎么回事儿或者怎么用,那么接下来我将通过几篇文章全面介绍一下 DataBinding 以及 DataBinding 的使用。
GitHub传送门 欢迎Star 下载
写在前面
我们通过两篇文章,,分别介绍了 DataBinding以及 DataBinding 的简单使用,这篇文章,我们来介绍一下 DataBinding 最佳实践——Binding adapters
我之所以说 Binding adapters 是 DataBinding 的最佳实践,是因为用过了才知道是真的好用!
下面我们就通过这篇文章全面的介绍一下 Binding adapters。
请耐心看完这篇文章,就知道真的好用!
DataBinding里的注解方法讲解
在正式介绍 Binding adapters 之前,我们先了解一下 DataBinding 里的注解方法。
@Bindable
用于数据更新自动刷新视图。
@BindingAdapter
这个注解用于支持自定义属性,或者是修改原有属性。注解值可以是已有的 xml 属性,例如 android:src、android:text等,也可以自定义属性然后在 xml 中使用。
列如官方示列当中,就介绍了个 「setPadding」 的例子。
@BindingAdapter("android:paddingLeft")
fun setPaddingLeft(view: View, padding: Int) {
view.setPadding(padding,
view.getPaddingTop(),
view.getPaddingRight(),
view.getPaddingBottom())
}
接受多个属性的适配器。
@BindingAdapter(value = { "imageUrl", "error" }, requireAll = false)
fun loadImage(view: ImageView, url: String, error: Drawable) {
Picasso.get().load(url).error(error).into(view)
}
从上面,我们可以注意到几个关键的地方:
- 修饰的方法,必须是 public static
- 方法参数第一个要求必须是 View
- 方法名可以随意
- 最后一个 booblean 类型是可选参数。可以要求是否所有参数都需要填写,默认是true。
- 如果这里requireAll为false, 你没有填写的属性值将为null. 所以需要做非空判断。
这里需要特殊说明的是:
当发生冲突时,定义的绑定适配器将覆盖Android框架提供的默认适配器。
@BindingMethods
DataBinding默认可以在布局中使用setter方法作为自定义属性,
但是如果不是setter格式的方法就要使用BindingMethod注解了. 通过创建一个自定义属性来关联一个类中已有的方法。
该注解属于一个容器. 内部参数是一个@BindingMethod数组, 只能用于修饰类(任意类都可以, 类可以为空).
下面我们看一看官方示例:
@BindingMethods(value = [
BindingMethod(
type = android.widget.ImageView::class,
attribute = "android:tint",
method = "setImageTintList")])
这里需要注意的是,这个注解必须有三个属性。
- type:字节码
- attribute:属性
- method:方法
会在指定的字节码(type)中寻找方法(method), 然后通过你创建的布局属性(Attribute)来回调方法。
如果属性名和@BindingAdapter冲突会报错
该注解只是单纯地关联已有的方法, 并不能新增方法. 所以全都是注解的空类.
@BindingConversion
属性值自动进行类型转换
列如,我们用的 android:background 属性是 Drawable 的,但是需要指定一个颜色值,而这个值是整数的。
那么我就需要用到了 @BindingConversion 注解。
<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
这里我们就可以用带有bindingConversion注释的静态方法进行转换,如下所示:
@BindingConversion
fun convertColorToDrawable(color: Int) = ColorDrawable(color)
但是,绑定表达式中提供的值类型必须一致。不能在同一表达式中使用不同的类型,列如如下的错误示范:
<View
android:background="@{isError ? @drawable/error : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
通过,以上我们可以注意到:
- 只能修饰 public static 方法。
- 任意位置任意方法名都不被限制。
- DataBinding自动匹配被该注解修饰的方法和匹配参数类型。
- 返回值类型必须喝属性setter方法匹配,且参数只能有一个。
- 要求属性值必须是@{}DataBinding表达式。
Binding adapters的使用实践
通过上面的介绍,我们了解到了这几个注释方法,接下来,我们就要开始使用这些方法。
下面就开始实践使用:
@Bindable
这个注解的理解还是十分简单的。
使用 @Bindable 来标记的 get 方法,在编译时,会在BR类当中生成对应的字段,然后与 notifyPropertyChanged() 方法配合使用,当该字段中的数据被修改时,dataBinding 会自动刷新对应view的数据,而不用我们在拿到新数据后重新把数据在setText()一遍,就凭这一点,dataBinding就可以简化大量的代码。
以此来实现双向绑定,关于双向绑定的内容,我会通过下一篇文章来详细讲述,现在先简单介绍一下使用。
实体类也可以不用继承BaseObservable,而是实现Observable接口,但是需要自行处理一些接口方法逻辑,BaseObservable是实现Observable接口的类,内部已经做好了相关逻辑处理,所以选择继承BaseObservable相对简单一些。
接下来我们看一下如何在代码里实现:
class StudentInfo : BaseObservable() {
@get:Bindable
var name: String? = null
@get:Bindable
var age: Int = 0
@get:Bindable
var sex: String? = null
@get:Bindable
var score: Int = 0
}
这样,我们的实体类就完成了。具体的使用方法和效果,我们在之后讲解双向绑定的时候会着重介绍。
@BindingAdapter
这里我们必须着重介绍一下 BindingAdapter 这个注解。这个可能是我们在之后的使用当中,最常用的一个注解。
这个注解厉害了!
除了重新定义已经有的方法,还可以定义新的属性!
列如,我们有个View既没有android:xxx="“或者app:xxx=”“属性,也没有setXxx()方法,我们通过@BindingAdapter同样可以实现自定义android:xxx=”“或者app:xxx=”"属性,然后使用!
除了定义属性职位,我们还可以定义一些不属于这个View的属性!
我们可以通过 @BindingAdapter 自定义一个或者一些属性,让我们可以在这个View当中,使用相应的属性!
例如我们定一个ImageView通过 @BindingAdapter 来定义一些属性。
@BindingAdapter(value = {"android:imageUrl", "android:placeHolder", "android:error"}, requireAll = false)
public static void loadImage(ImageView view, String url, Drawable error, Drawable placeHolder) {
Glide.with(view.getContext()).load(url).into(view);
}
定义好之后,我们就可以开始使用了!
<ImageView
android:id="@+id/iv_binding_adapter"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:placeHolder="@{@drawable/ic_launcher"
android:imageUrl="@{url}"
android:error="@{@drawable/ic_launcher}"
/>
值得注意的是,这里的 @drawable/ic_launcher,用@{}括住资源使其成为有效的绑定表达式。
但是,我有一点疑惑的是,现在我们图片的资源都放在mipmap系列文件夹下了,但是这里设置只能在drawable文件夹下面找到对应的资源?如果有什么想法和办法欢迎给我留言,我们交流一下!
还有一点注意的是,我们设置glide的时候,别忘了在AndroidManifest文件当中把权限设置上!
<uses-permission android:name="android.permission.INTERNET" />
接下来,我们就可以看到我们要实现的效果了
是不是很厉害!这样,我们可以节约多少代码!
以上只是一个简单的使用,还有更厉害的!
那就是配合RecyclerView设置adapter。RecyclerView可以说是我们最常用的一个控件,如果吧adapter和DataBinding结合之后,你会发现写Adapter会变得十分的简单!
更多的属性,都可以在xml当中完成。
我们可以为RecyclerView的Adapter当中,设置我们常用的一些属性,比如 setOnItemClickListener 、 setOnLoadMoreListener 、
setEnableLoadMore 、setOnRefreshListener等等这些我们常用的一些方法。
列如,我们可以定义一个BindAdapter
public class RecyclerViewBindingAdapter {
@BindingAdapter(value = {"android:onItemClick", "android:onLoadMore",
"android:loadMoreEnable"}, requireAll = false)
public static void setupAdapter(RecyclerView recyclerView, final ItemClickListener itemClickListener,
final LoadMoreListener loadMoreListener, final boolean loadMoreEnable) {
RecyclerView.Adapter adapter = recyclerView.getAdapter();
if (adapter == null || !(adapter instanceof BaseQuickAdapter)) {
return;
}
BaseQuickAdapter quickAdapter = (BaseQuickAdapter) adapter;
quickAdapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
itemClickListener.onItemClick(adapter, view, position);
}
});
quickAdapter.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() {
@Override
public void onLoadMoreRequested() {
loadMoreListener.onLoadMore();
}
}, recyclerView);
quickAdapter.setEnableLoadMore(loadMoreEnable);
quickAdapter.setLoadMoreView(new RVLoadMoreView());
quickAdapter.openLoadAnimation(BaseQuickAdapter.ALPHAIN);
}
public interface ItemClickListener {
void onItemClick(BaseQuickAdapter adapter, View view, int position);
}
public interface LoadMoreListener {
void onLoadMore();
}
}
在布局文件当中,使用我们刚才定义的属性
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:loadMoreEnable="@{true}"
android:onItemClick="@{presenter.onItemClick}"
android:onLoadMore="@{presenter.onLoadMore}"
app:adapter="@{adapter}"
app:layoutManager="LinearLayoutManager"/>
通过上面的方式,我们就实现了通过在RecyclerView中配置属性达到为adapter设置点击监听,上拉加载监听,以及是否开启监听的目的。
这里值得注意的是:
其中的app:adapter="@{adapter}"是因为RecyclerView有setAdapter方法,结合databinding的特性,故而可以这样写。而app:layoutManager="LinearLayoutManager"属性是RecyclerView自己提供的一个属性,为了方便我们为RecyclerView设置layoutManager,其内部采用反射构造一个目标layoutManager,然后通过RecyclerView的public void setLayoutManager(LayoutManager layout)再进行设置。
最后
相信,通过上面的内容。已经能体会到了DataBinding的便捷之处。接下来,我们在讲讲双向绑定。如有任何问题,欢迎给我留言,我们一起讨论。