TwoWayDataBinding

https://developer.android.com/topic/libraries/data-binding/two-way

Infinite loops using two-way data binding

Be careful not to introduce infinite loops when using two-way data binding. When the user changes an attribute, the method annotated using @InverseBindingAdapter is called, and the value is assigned to the backing property. This, in turn, would call the method annotated using @BindingAdapter, which would trigger another call to the method annotated using @InverseBindingAdapter, and so on.

For this reason, it’s important to break possible infinite loops by comparing new and old values in the methods annotated using @BindingAdapter.

TwoWayDataBinding

ensure that the view’s attribute is changed before update it if it’s in two way binding

graph TB
subgraph DataFlow

UserAction/Lifecycle-->LoadData

end

subgraph TwoWayDataBinding
Attribute-->|Change by user,and call method|AttrChanged("method annotated with BindingAdapter-"app:xxxAttrChanged"")
-->|InverseBindingListener.onChange|DataBindingSystem("DataBindingSystem knows attribute has changed")
-->|Call annotated InverseBindingAdapter, assign value to|BackingProperty("BackingProperty:LiveData")
-->|Call annotated BindingAdapter, may change attribute|Attribute
LoadData-->|Resource<>|BackingProperty
end


TextViewBindingAdapter上的对应

@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 beforeTextChanged(CharSequence s, int start, int count, int after) {
                if (before != null) {
                    before.beforeTextChanged(s, start, count, after);
                }
            }

            @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();
                }
            }
@InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
public static String getTextString(TextView view) {
    return view.getText().toString();
}
@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);
}