Android 自定义组件的双向数据绑定?

Android two way data binding for custom component?

我正在尝试关注 this 博客 post 以尝试获得两种方式的数据绑定以用于自定义组件(其中包含 EditText 的约束视图)。

我能够让两个标准的 EditText 组件与我的模型同步(双向),但我无法让自定义组件中的更改流入我的模型(尽管单向数据装订作品)。

我的模特:

public class Model extends BaseObservable {
    private String value;

    @Bindable
    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
        notifyPropertyChanged(company.com.databinding.BR.value);
    }

    public Model() {
        value = "Value";
    }
}

Activity:

@InverseBindingMethods({
        @InverseBindingMethod(
                type = CustomComponent.class,
                attribute = "value",
                method = "getValue")
})
public class MainActivity extends AppCompatActivity {
    @BindingAdapter("value")
    public static void setColor(CustomComponent view, String value) {
        if (!value.equals(view.getValue())) {
            view.setValue(value);
        }
    }

    @BindingAdapter(
            value = {"onValueChange", "valueAttrChanged"},
            requireAll = false
    )
    public static void setListeners(CustomComponent view,
                                    final ValueChangeListener onValueChangeListener,
                                    final InverseBindingListener inverseBindingListener) {
        ValueChangeListener newListener;
        if (inverseBindingListener == null) {
            newListener = onValueChangeListener;
        } else {
            newListener = new ValueChangeListener() {
                @Override
                public void onValueChange(CustomComponent view,
                                          String value) {
                    if (onValueChangeListener != null) {
                        onValueChangeListener.onValueChange(view,
                                value);
                    }
                    inverseBindingListener.onChange();
                }
            };
        }

        ValueChangeListener oldListener =
                ListenerUtil.trackListener(view, newListener,
                        R.id.textWatcher);

        if (oldListener != null) {
            view.removeListener(oldListener);
        }
        if (newListener != null) {
            view.addListener(newListener);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.activity_main);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setModel(new Model());
    }
}

自定义组件:

public class CustomComponent extends ConstraintLayout {
    private String value;
    private EditText txt;
    private TextWatcher textWatcher;
    ValueChangeListener listener;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
        if (txt != null) {
            txt.setText(value);
        }
    }

    public CustomComponent(Context context) {
        super(context);
        init(context);
    }

    public CustomComponent(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public CustomComponent(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(context, attrs);
    }

    private void init(Context context) {

    }

    private void init(Context context, AttributeSet attrs) {
        View.inflate(context, R.layout.custom_component, this);
        txt = findViewById(R.id.txt_box);
        final CustomComponent self = this;
        textWatcher = new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void afterTextChanged(Editable editable) {
                if (listener != null) {
                    listener.onValueChange(self, editable.toString());
                }
            }
        };
        txt.addTextChangedListener(textWatcher);
    }

    public void addListener(ValueChangeListener listener) {
        this.listener = listener;
    }

    public void removeListener(ValueChangeListener listener) {
        this.listener = null;
    }
}

public interface ValueChangeListener {
    public void onValueChange(CustomComponent view, String value);
}

我认为 post 中的 "Hooking The Event" 部分完全超出了我的理解;我真的只需要一个简单的 setter 和 getter 作为组件,所以不太明白那个 BindingAdapter 做了什么。在所有这些中,我认为这是我根本不了解的这一行:

ValueChangeListener oldListener =
            ListenerUtil.trackListener(view, newListener,
                    R.id.textWatcher);

演示地址:https://github.com/indgov/data_binding

抱歉,ListenerUtil 令人困惑。这仅在您的组件支持多个侦听器时才有用。在那种情况下,你不能只设置一个新的监听器,你必须删除旧的并添加新的。 ListenerUtil 帮助您跟踪旧的侦听器,以便将其删除。在你的情况下,它可以简化:

@BindingAdapter(
        value = {"onValueChange", "valueAttrChanged"},
        requireAll = false
)
public static void setListeners(CustomComponent view,
                                final ValueChangeListener onValueChangeListener,
                                final InverseBindingListener inverseBindingListener) {
    ValueChangeListener newListener;
    if (inverseBindingListener == null) {
        newListener = onValueChangeListener;
    } else {
        newListener = new ValueChangeListener() {
            @Override
            public void onValueChange(CustomComponent view,
                                      String value) {
                if (onValueChangeListener != null) {
                    onValueChangeListener.onValueChange(view,
                            value);
                }
                inverseBindingListener.onChange();
            }
        };
    }

    view.setListener(newListener);
}

然后用 setListener() 替换 addListener() 并且你不需要 removeListener() 因为你总是可以将侦听器设置为 null.

您遇到的问题出在您的组件中:

public String getValue() {
    return value;
}

您返回的是 setter 上次设置的值,而不是 EditText 中的值。要解决这个问题:

public String getValue() {
    return txt.getText().toString();
}