DataBinding的双向绑定

这篇之前在简书上面发布的blog,由于不打算在简书上面写东西了,就搬过来这里。

之前曾经发布过一篇blog来记录之前使用DataBinding的一些心得体会,当时对于DataBinding的双向绑定简单提了一下。现在对这个双向绑定有了进一步的了解,于是继续分享。

回顾

我们先来回顾一下之前双向绑定的做法:

<EditText
android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:layout_marginBottom="5dp"
  android:text="@={item.price}"/>

  <Button
   android:layout_width="match_parent"
   android:layout_height="wrap_content"
   android:text="@{item.price}"/>

这里就只是通过使用* “@={}”*就完成了EditTextButton的文本属性的双向绑定。这里面隐藏了什么秘密呢?直接打开源码来看下吧;

隐藏起来的秘密

EditTextButton继承TextView,我们可以在TextViewBindingAdapter里面看到Goolge工程师对android:text这个属性做了什么处理。打开源码,首先就被下面这段代码吸引了目光(反正我就被吸引了):

   @InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
    public static String getTextString(TextView view) {
        return view.getText().toString();
    }

虽然很疑惑这个@InverseBindingAdapter注解的作用,不过还是先把相关源码的逻辑找出来吧:

   @BindingAdapter(value = {"android:beforeTextChanged", "android:onTextChanged",
            "android:afterTextChanged", "android:textAttrChanged"}, requireAll = false)
    public static void setTextWatcher(TextView view, final BeforeTextChanged before,
            final OnTextChanged on, final AfterTextChanged after,
            final InverseBindingListener textAttrChanged) {
        final TextWatcher newValue;
        if (before == null && after == null && on == null && textAttrChanged == null) {
            newValue = null;
        } else {
            newValue = new TextWatcher() {
                //删去部分代码,这里不是重点
                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                    if (on != null) {
                        on.onTextChanged(s, start, before, count);
                    }
                    if (textAttrChanged != null) {
                        textAttrChanged.onChange();
                    }
                }
               //删去部分代码,这里不是重点
            };
        }
        final TextWatcher oldValue = ListenerUtil.trackListener(view, newValue, R.id.textWatcher);
        if (oldValue != null) {
            view.removeTextChangedListener(oldValue);
        }
        if (newValue != null) {
            view.addTextChangedListener(newValue);
        }
    }

EditText的输出监听回调方法就是上面的onTextChanged,不过在这里貌似看到几样有趣的东西:android:textAttrChangedtextAttrChanged参数ListenerUtil.trackListener

牵连起来的线索

android:textAttrChanged这个关键词语出现了两次,第一次是出现在InverseBindingAdapter里面。
很明显,这不是偶然。
我们继续将目光放在setTextWatcher方法里面,发现里面还有一个textAttrChanged参数,这是一个InverseBindingListener对象。InverseBindingListener是用来做什么的?直接到官网搜一下,得到这么一个解释:A listener implemented by all two-way bindings to be notified when a triggering change happens.
翻译过来的大意就是所有的双向绑定都必须实现这个监听,当事件触发时,这个监听将得到通知
InverseBindingListener具体是怎么实现双向监听的,我们先来看下它的源码吧:

public interface InverseBindingListener {
    void onChange();
}

很简单的一个回调接口,我们再看看下DataBinding生成的Java类里的代码吧,这里面应该有这个接口的实现(生成类的路径一般都是app\build\generated\source\apt\debug\XXX):

 // values
    // listeners
    // Inverse Binding Event Handlers
    private android.databinding.InverseBindingListener mboundView1androidTextAttrChanged = new 
       android.databinding.InverseBindingListener() {
        @Override
        public void onChange() {
            // Inverse of item.price
            //is item.setPrice((java.lang.String) callbackArg_0)
    java.lang.String callbackArg_0 = android.databinding.adapters.TextViewBindingAdapter.getTextString(mboundView1);
            // localize variables for thread safety
            // item
            com.fritz.study.Model.InfoBean item = mItem;
            // item != null
            boolean itemJavaLangObjectNull = false;
            // item.price
            java.lang.String itemPrice = null;
            itemJavaLangObjectNull = (item) != (null);
            if (itemJavaLangObjectNull) {
                item.setPrice(((java.lang.String) (callbackArg_0)));
            }
        }
    };

看到这里的代码,我相信很多人都明白了这个双向绑定到底是怎么回事了。
TextViewBindingAdapter.getTextString,这个方法是InverseBindingAdapterattribute(属性)标注的方法!
到这里,可以推导出InverseBindingAdapter里面的两个注解的作用了:

event:这个是生成InverseBindingListener对象的标签名字,用于在DataBinding生产类中创建InverseBindingListener对象时来起名字用的,正如上面的mboundView1android**TextAttrChanged**参数。
同时DataBinding也是通过这个textAttrChangedevent标签找到setTextWatcher方法,而setTextWatcher通知InverseBindingListeneronChange方法,而onChange方法则使用找到的get和set方法去进行检查和更新。

attribute:这个标签很明显就是用来标注getTextString方法了,DataBinding就是通过这个标签名字找到这个方法的

潜伏起来的陷阱

分析到这里就已经基本搞明白双向绑定的原理,不过其实我们细细思考一下这里双向绑定的逻辑:用户输入触发onChange事件发生,更新了参数的属性,而属性改变又会刷新EditText的文本,而EditText的文本改变又会触发onChange事件发生,这不是成了一个互相触发的死循环吗?
这点我们能想到,Google的工程师也肯定早就想到了,他们在TextViewBindingAdapter源码里面的解决方法如下:

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

在上面的setText方法可以看到,如果两次的文本没有改变,则会直接return,杜绝了无限循环调用的可能性。
如果是自己做自定义双向绑定的话,必须要注意这一点!
差点掉到一个大坑里面,不能大意再看下有无什么潜伏起来的陷阱。我们将目光回到setTextWatcher,发现里面还有一个差点漏掉的ListenerUtil.trackListener。我相信Google工程师不会无缘无故在这里写这么一句代码的,我们先点进去看下代码的具体实现:

8X0HCHRG(G)SAS$O)2E7$@R.png

原来就是通过某个id来从View里面拿出一个listener,然后将这个id作为一个新的listener键值来将这个新的listener传入View里面保存,然后返回这个取出的listener。

       final TextWatcher oldValue = ListenerUtil.trackListener(view, newValue, R.id.textWatcher);
        if (oldValue != null) {
            view.removeTextChangedListener(oldValue);
        }
        if (newValue != null) {
            view.addTextChangedListener(newValue);
        }

那这段代码的作用就是追踪TextView之前的listener,将其移除后再给TextView添加新的listener。
考虑到我们可能只会绑定某一个或两个的TextWatcher的接口方法,如果一个页面多次使用这个TextWatcher方法来设置监听的话,可能会出现listener混乱的情况,为了避免这个问题,我们最好是在给TextView添加listener之前检查它是否持有listener,如果有,就将旧listener移除再add新的listener。

结语

到此我们就清楚了DataBinding双向绑定要怎么玩了,双向绑定说起来很高大上,但是其实搞明白了也很简单。

猜你喜欢

转载自blog.csdn.net/f409031mn/article/details/80868884