【问题标题】:Two way binding on custom view自定义视图上的两种方式绑定
【发布时间】:2018-06-21 01:17:33
【问题描述】:

我在 android 中有一个组合视图,其中包含几个 textView 和一个 EditText。我为我的自定义视图定义了一个属性,称为textgetTextsetText 方法。现在我想以绑定到内部编辑文本的方式为我的自定义视图添加 2 路数据绑定,因此如果我的数据得到更新,编辑文本也应该更新(现在可以使用),当我的编辑文本得到更新时,我的数据也应该更新。

我的绑定类是这样的

@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) 在视图类型 'ir.avalinejad.pasargadinsurance.component.ErrorInputLayout' 上找不到事件 'textAttrChanged'

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

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

【问题讨论】:

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


    【解决方案1】:

    经过数小时的调试,我找到了解决方案。我像这样更改了我的 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();
        }
    

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

    【讨论】:

      【解决方案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()
          }
      }
      

      【讨论】:

        【解决方案3】:

        你需要再添加一个功能

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

        【讨论】:

          猜你喜欢
          • 2013-09-23
          • 1970-01-01
          • 2016-02-27
          • 2017-01-15
          • 1970-01-01
          • 2017-09-05
          • 2014-02-13
          • 1970-01-01
          • 2012-01-14
          相关资源
          最近更新 更多