继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

Android Jetpack系列——DataBinding 最佳实践

Demo_Yang
关注TA
已关注
手记 45
粉丝 35
获赞 116

这是第「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)
}

从上面,我们可以注意到几个关键的地方:

  1. 修饰的方法,必须是 public static
  2. 方法参数第一个要求必须是 View
  3. 方法名可以随意
  4. 最后一个 booblean 类型是可选参数。可以要求是否所有参数都需要填写,默认是true。
  5. 如果这里requireAll为false, 你没有填写的属性值将为null. 所以需要做非空判断。

这里需要特殊说明的是:

当发生冲突时,定义的绑定适配器将覆盖Android框架提供的默认适配器。

@BindingMethods

DataBinding默认可以在布局中使用setter方法作为自定义属性,

但是如果不是setter格式的方法就要使用BindingMethod注解了. 通过创建一个自定义属性来关联一个类中已有的方法。

该注解属于一个容器. 内部参数是一个@BindingMethod数组, 只能用于修饰类(任意类都可以, 类可以为空).

下面我们看一看官方示例:

@BindingMethods(value = [
    BindingMethod(
        type = android.widget.ImageView::class,
        attribute = "android:tint",
        method = "setImageTintList")])

这里需要注意的是,这个注解必须有三个属性。

  1. type:字节码
  2. attribute:属性
  3. 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"/>

通过,以上我们可以注意到:

  1. 只能修饰 public static 方法。
  2. 任意位置任意方法名都不被限制。
  3. DataBinding自动匹配被该注解修饰的方法和匹配参数类型。
  4. 返回值类型必须喝属性setter方法匹配,且参数只能有一个。
  5. 要求属性值必须是@{}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的便捷之处。接下来,我们在讲讲双向绑定。如有任何问题,欢迎给我留言,我们一起讨论。

打开App,阅读手记
1人推荐
发表评论
随时随地看视频慕课网APP