DataBinding难点解析之Observable和BindingAdapter(二)

本文主要探讨了BindingAdapter,力求深入透彻,让你读完之后不再有疑惑。

一、为什么需要BindingAdapter?

让我们接着上文的例子来讲。在上文的例子中,activity_main.xml中的TextView有如下binding表达式:

android:text = "@{user.name}"

我们来看看DataBinding是怎么处理上面这行代码的。

首先,DataBinding框架会对binding表达式进行求值,具体怎么求值,在我的文章DataBinding实现原理探析中有详细的论述,这里不再重复。

假设现在DataBinding框架已经对binding表达式求完值,值为CharSequence “milter”。

现在DataBinding框架面对的情况是这样的:要把值为的“milter”的字符串赋值给TextView的命名空间为android的text属性。
上面这句话读起来比较绕,我们把它做成表格来看:

类型
CharSequence “milter”
TextView username
命名空间 android
属性 text

DataBinding如何使用些信息?我们先从最简单的情况说起,假如没有BindingAdapter机制,DataBinding框架会这样做:

  • 忽略命名空间android,也就是说不管属性前面是什么命名空间,android也好,自定义(如app)的也罢,DataBinding框架都不care,它根本不需要这个信息。
  • 根据属性值text和binding表达式的值CharSequence “milter”在TextView中寻找有如下签名的方法:
    setText(CharSequence text)
  • 在id为username的TextView上调用:setText("milter")
  • 结束!

上面的过程看起来非常理想对不对?毕竟我们自己也经常调用setText方法。但是由于databinding表达式的存在,事情开始变得复杂。

在我们的例子中,databinding表达式最后算出来的值是“milter”,可在实际情况中,这个表达式的值有很多变数,比如它可能是null,可能与TextView已有的text一样等,这些情况下,我们完全没有必要调用TextView的setText方法。

要知道,setText方法是一个复杂耗时间的操作,尤其是如果它的参数是一个Spanned类型,操作会更复杂。

怎么解决这个问题?最好的方法就是当DataBinding框架算出binding表达式的值之后,能够让我们介入,让我们根据求出的值的情形来决定是否调用TextView的setText。

DataBinding框架确实给我们提供了这样的介入机制,这就是BindingAdapter。

二、使用BindingAdapter

继续我们上面的例子,为了实现我们上面的介入目的,我们按照DataBinding框架的要求,定义出下面的BindingAdapter:

    @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);
    }

要理解这个BindingAdapter,有三大关键点:

  • 注解@BindingAdapter的参数“android:text”
  • 方法第一个参数TextView
  • 方法第二个参数CharSequence

DataBinding框架会汇总以上三个信息,进而得出结论:

当在TextView上设置text属性,且设置的值的类型是CharSequence时,就不要直接调用TextView相应的setText方法,而是调用用户定义的这个BindingAdapter方法。

Note:DataBinding框架根本不关心text属性前面的命名空间是什么,也不关心这个BindingAdapter的方法名字是什么,我们把它定义成setText,纯属巧合 :)。

在这个BindingAdapter中,我们先是判断是否有必要调用TextView的setText方法,确认有必要后,我们才调用TextView的setText方法,对于没有必要的情况,我们选择直接返回。关于这个BindingAdapter放在哪里,答案是:看你心情,随意放!

好了,现在我们来总结一下:

当在任意一个View的任意一个属性上使用binding表达式时,DataBinding框架的处理过程分成三步:
1、对binding表达式求值
2、寻找合适的BindingAdapter,如果找到,就调用它的方法
3、如果没有找到合适的BindingAdapter,就在View上寻找合适的方法调用

现在问题来了,UI控件那么多,它们的属性就更多了,难道对每个需要使用binding表达式的属性,我们都要像上面那样写一个BindingAdapter?有木有想死的冲动?

不用担心,DataBinding框架已经帮我们写好了许许多多的BindingAdapter,覆盖了Android提供的所有控件的绝大多数属性!

告诉你一个秘密,其实上面的BindingAdapter代码,根本不是我写的,而是DataBinding框架已经提供好的。

2016.12.18补充


有的朋友问,DataBinding框架写好的setText方法在哪里,我怎么找不着呢?下面用一张图告诉你!

DataBinding难点解析之Observable和BindingAdapter(二)_第1张图片
where_is_binding_adapter.PNG

该图是在Project视图模式下截取的。图左侧那一群BindingAdapter中就有本文中使用的TextViewBindingAdapter,其内容显示在图中右侧,可以看到setText方法了吧 :)

三、自定义BindingAdapter

假如我们觉得上面那个系统提供的BindingAdapter不能满足我们的需求,我们想要这个BindingAdapter能够将CharSequence全部变成大写,然后再调用TextView的setText方法,这时,我们就需要自定义BindingAdapter。

DataBinding框架很开明,它承诺:在寻找合适的BindingAdapter时,会优先使用用户定义的BindingAdapter。

现在我们自定义一个我们的BindingAdapter:

    @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.
        }
        //下面这句代码,就是我们加进去的
        CharSequence upperText = text.toUpperCase();
        view.setText(upperText);
    }

这个BindingAdapter其实就比系统提供的BindingAdapter多一句将字符串变成大写的代码,其他完全一样。

有了这个BindingAdapter,任何时候,任何情况下,只要我们在TextView的text属性上使用binding表达式,并且这个表达式的值是CharSequence,那么,我们自定义的BindingAdapter就会被DataBinding框架调用,它会把binding表达式的值变成大写后设置给TextView。如下图所示:

DataBinding难点解析之Observable和BindingAdapter(二)_第2张图片
upper.png

总结:以上,我们深入探讨了DataBinding框架的BindingAdapter机制,从为什么需要它,怎么使用它,怎么自定义它三个方面进行了分析。重点在于理解它的原理。关于BindingAdapter各种好玩的使用方法,请参考官方文档:https://developer.android.com/topic/libraries/data-binding/index.html(要科学上网哈!)

你可能感兴趣的:(DataBinding难点解析之Observable和BindingAdapter(二))