自定义视图上的双向绑定

时间:2018-01-11 16:23:05

标签: android data-binding two-way-binding android-binding-adapter

我在android中有一个编译视图包含几个textView和一个EditText。我为自定义视图定义了一个名为textgetTextsetText方法的属性。现在我想以一种绑定到内部编辑文本的方式为我的自定义视图添加双向数据绑定,这样如果我的数据得到更新,编辑文本也应该更新(现在可以正常工作)和编辑时文本更新我的数据也应该更新。

我的绑定类看起来像这样

@InverseBindingMethods({
        @InverseBindingMethod(type = ErrorInputLayout.class, attribute = "text"),
})
public class ErrorInputBinding {
    @BindingAdapter(value = "text")
    public static void setListener(ErrorInputLayout errorInputLayout, final InverseBindingListener textAttrChanged) {
        if (textAttrChanged != null) {
            errorInputLayout.getInputET().addTextChangedListener(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) {
                    textAttrChanged.onChange();
                }
            });
        }
    }
}

我尝试使用下面的代码绑定文本。 userInfo是一个可观察的类。

            <ir.avalinejad.pasargadinsurance.component.ErrorInputLayout
                android:id="@+id/one_first_name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:title="@string/first_name"
                app:text="@={vm.userInfo.firstName}"
                />

当我运行项目时,我收到此错误

  

错误:(20,13)无法找到事件&#39; textAttrChanged&#39;在视图类型&#39; ir.avalinejad.pasargadinsurance.component.ErrorInputLayout&#39;

我的自定义视图看起来像这样

public class ErrorInputLayout extends LinearLayoutCompat implements TextWatcher {
    protected EditText inputET;
    protected TextView errorTV;
    protected TextView titleTV;
    protected TextView descriptionTV;

    private int defaultGravity;

    private String title;
    private String description;
    private String hint;
    private int inputType = -1;
    private int lines;
    private String text;

    private Subject<Boolean> isValidObservable = PublishSubject.create();

    private Map<Validation, String> validationMap;

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

    public ErrorInputLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        readAttrs(attrs);
        init();
    }

    public ErrorInputLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        readAttrs(attrs);
        init();
    }

    private void readAttrs(AttributeSet attrs){
        TypedArray a = getContext().getTheme().obtainStyledAttributes(
                attrs,
                R.styleable.ErrorInputLayout,
                0, 0);

        try {
            title = a.getString(R.styleable.ErrorInputLayout_title);
            description = a.getString(R.styleable.ErrorInputLayout_description);
            hint = a.getString(R.styleable.ErrorInputLayout_hint);
            inputType = a.getInt(R.styleable.ErrorInputLayout_android_inputType, -1);
            lines = a.getInt(R.styleable.ErrorInputLayout_android_lines, 1);
            text = a.getString(R.styleable.ErrorInputLayout_text);

        } finally {
            a.recycle();
        }
    }


    private void init(){
        validationMap = new HashMap<>();
        setOrientation(VERTICAL);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        titleTV = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.error_layout_default_title_textview, null, false);
        addView(titleTV);

        descriptionTV = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.error_layout_default_description_textview, null, false);
        addView(descriptionTV);

        readInputFromLayout();

        if(inputET == null) {
            inputET = (EditText) LayoutInflater.from(getContext()).inflate(R.layout.error_layout_defult_edittext, this, false);
            addView(inputET);
        }

        errorTV = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.error_layout_default_error_textview, null, false);
        addView(errorTV);

        inputET.addTextChangedListener(this);
        defaultGravity = inputET.getGravity();

        //set values
        titleTV.setText(title);

        if(description != null && !description.trim().isEmpty()){
            descriptionTV.setVisibility(VISIBLE);
            descriptionTV.setText(description);
        }

        if(inputType != -1)
            inputET.setInputType(inputType);

        if(hint != null)
            inputET.setHint(hint);

        else
            inputET.setHint(title);

        inputET.setLines(lines);

        inputET.setText(text);
    }

    private void readInputFromLayout() {
        if(getChildCount() > 3){
            throw new IllegalStateException("Only one or zero view is allow in layout");
        }

        if(getChildCount() == 3){
            View view = getChildAt(2);
            if(view instanceof EditText)
                inputET = (EditText) view;
            else
                throw new IllegalStateException("only EditText is allow as child view");
        }
    }

    public void setText(String text){
        inputET.setText(text);
    }

    public String getText() {
        return text;
    }

    public void addValidation(@NonNull Validation validation, @StringRes int errorResourceId){
        addValidation(validation, getContext().getString(errorResourceId));
    }

    public void addValidation(@NonNull Validation validation, @NonNull String error){
        if(!validationMap.containsKey(validation))
            validationMap.put(validation, error);
    }

    public void remoteValidation(@NonNull Validation validation){
        if(validationMap.containsKey(validation))
            validationMap.remove(validation);
    }

    public EditText getInputET() {
        return inputET;
    }

    public TextView getErrorTV() {
        return errorTV;
    }

    @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) {
        checkValidity();

        if(editable.toString().length() == 0) //if hint
            inputET.setGravity(Gravity.RIGHT);
        else
            inputET.setGravity(defaultGravity);
    }

    public Subject<Boolean> getIsValidObservable() {
        return isValidObservable;
    }

    private void checkValidity(){
        //this function only shows the first matched error.
        errorTV.setVisibility(INVISIBLE);
        for(Validation validation: validationMap.keySet()){
            if(!validation.isValid(inputET.getText().toString())) {
                errorTV.setText(validationMap.get(validation));
                errorTV.setVisibility(VISIBLE);
                isValidObservable.onNext(false);
                return;
            }
        }

        isValidObservable.onNext(true);
    }
}

3 个答案:

答案 0 :(得分:4)

经过数小时的调试,我找到了解决方案。我改变了我的Binding类。

@InverseBindingMethods({
        @InverseBindingMethod(type = ErrorInputLayout.class, attribute = "text"),
})
public class ErrorInputBinding {
    @BindingAdapter(value = "textAttrChanged")
    public static void setListener(ErrorInputLayout errorInputLayout, final InverseBindingListener textAttrChanged) {
        if (textAttrChanged != null) {
            errorInputLayout.getInputET().addTextChangedListener(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) {
                    textAttrChanged.onChange();
                }
            });
        }
    }

    @BindingAdapter("text")
    public static void setText(ErrorInputLayout view, String value) {
        if(value != null && !value.equals(view.getText()))
            view.setText(value);
    }

    @InverseBindingAdapter(attribute = "text")
    public static String getText(ErrorInputLayout errorInputLayout) {
        return errorInputLayout.getText();
    }

首先,我在AttrChanged这样的文本之后添加@BindingAdapter(value = "textAttrChanged"),这是监听器的默认名称,然后我在这里添加了getter和setter方法。

答案 1 :(得分:2)

event = "android:textAttrChanged"为我工作:

object DataBindingUtil {
    @BindingAdapter("emptyIfZeroText")        //replace "android:text" on EditText
    @JvmStatic
    fun setText(editText: EditText, text: String?) {
        if (text == "0" || text == "0.0") editText.setText("") else editText.setText(text)
    }

    @InverseBindingAdapter(attribute = "emptyIfZeroText", event = "android:textAttrChanged")
    @JvmStatic
    fun getText(editText: EditText): String {
        return editText.text.toString()
    }
}

答案 2 :(得分:1)

您还需要添加一项功能

@BindingAdapter("app:textAttrChanged")
fun ErrorInputLayout.bindTextAttrChanged(listener: InverseBindingListener) {

}