10

I've been using 2-way databinding for a basic application, it was going pretty well, until i start with custom views and attrs.

I want to create a custom view, with has a TextView and a EditText, and use it inside another layout:

 <TextView
    android:text="Holder"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/tvTitle"
    android:layout_weight="1" />

<EditText
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:inputType="none"
    android:text="Name"
    android:ems="10"
    android:id="@+id/etAnwser"
    android:layout_weight="1" />

And i have the custom attr for it

<resources>
<declare-styleable name="form_item">
    <attr name="tvTitle" format="string" />
    <attr name="anwserHint" format="string" />
    <attr name="anwserText" format="string" />
    <attr name="android:enabled" />
</declare-styleable>

In the fragment i do the following:

 <rhcloud.com.financialcontrol.tabutil.FormItem
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:enabled="@{state.get()}"
            form_item:anwserText='@={expense.description}'
            form_item:tvTitle="Description:" />

It works nice has 1-way databind, but whatever i change the text, he don't send me the callback in class

@InverseBindingMethods(value = {
        @InverseBindingMethod(type = FormItem.class, attribute = "anwserText"),
})
public class FormItem extends LinearLayout {

    private TextView tvTitle;
    private EditText etAnwser;

    public FormItem(@NonNull Context context) {
        super(context);
        LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        inflater.inflate(R.layout.form_item, this);

        tvTitle = (TextView) findViewById(R.id.tvTitle);
        etAnwser = (EditText) findViewById(R.id.etAnwser);
    }

    public FormItem(@NonNull Context context, @NonNull String title) {
        this(context);
        setTvTitle(title);
    }

    public FormItem(@NonNull Context context, @NonNull String title, @NonNull String hint) {
        this(context, title);
        setAnwserHint(hint);
    }

    public FormItem(@NonNull Context context, @NonNull String title, @NonNull String hint, @NonNull String anwserText) {
        this(context, title, hint);
        setAnwserHint(anwserText);
    }


    public FormItem(@NonNull Context context, @NonNull AttributeSet attrs) {
        super(context, attrs);

        LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

        inflater.inflate(R.layout.form_item, this);

        tvTitle = (TextView) findViewById(R.id.tvTitle);
        etAnwser = (EditText) findViewById(R.id.etAnwser);

        TypedArray a = context.getTheme().obtainStyledAttributes(
                attrs,
                R.styleable.form_item,
                0, 0);

        try {
            setTvTitle(a.getString(R.styleable.form_item_tvTitle));
            setAnwserHint(a.getString(R.styleable.form_item_anwserHint));
            setAnwserText(a.getString(R.styleable.form_item_anwserText));
            String isEnabled = a.getString(R.styleable.form_item_android_enabled);
            if (isEnabled != null) {
                setEnable(Boolean.parseBoolean(isEnabled));
            }
        } finally {
            a.recycle();
        }
    }

    public void setTvTitle(String title) {
        tvTitle.setText(title);
    }

    public String getTvTitle() {
        return tvTitle.getText().toString();
    }

    public void setAnwserHint(String hint) {
        etAnwser.setHint(hint);
    }

    public String getAnwserHint() {
        return etAnwser.getHint().toString();
    }

    public void setEnable(boolean isEnable) {
        tvTitle.setEnabled(isEnable);
        etAnwser.setEnabled(isEnable);
    }

    public void setAnwserText(String anwserText) {
        etAnwser.setText(anwserText);
    }

    public String getAnwserText() {
        return etAnwser.getText().toString();
    }

    @InverseBindingAdapter(attribute = "form_item:anwserText")
    public static String setOnAnwserTextAttrChanged(final String value){

        Log.d("Test","Calling InverseBindingAdapter: " + value);
        return value;
    }


    @BindingAdapter(value = {"anwserTextAttrChanged"},
            requireAll = false)
    public static void setOnAnwserTextAttrChanged(final FormItem view,final InverseBindingListener anwserTextAttrChanged){

        Log.d("Test","Calling BindingAdapter: " + view.getAnwserText());


    if(anwserTextAttrChanged == null){

        }else{
        Log.d("Test","Calling here");
            anwserTextAttrChanged.onChange();

        }
    }

    @BindingAdapter(value = {"android:enabled"})
    public static void customEnable(FormItem formItem, boolean isEnable) {
        formItem.setEnable(isEnable);
    }
}

Does anyone know how to make it work properly?

Fully code can be found at here

1 Answer 1

13

This works for me:

@InverseBindingMethods(value = {
        @InverseBindingMethod(type = FilterPositionView.class, attribute = "bind:filterStringValue", method = "getFilterValue", event = "android:filterStringValuetAttrChanged")
})
public class FilterPositionView extends LinearLayout {
    private FilterPositionBinding mBinding;

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

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

    public FilterPositionView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    public FilterPositionView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init(context);
    }

    private void init(Context context) {
        mBinding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.filter_position, this, true);
        setOrientation(HORIZONTAL);

        mBinding.filterPositionCheck.setOnCheckedChangeListener((buttonView, isChecked) -> {
            mBinding.filterPositionValue.setEnabled(isChecked);
            if (!isChecked) mBinding.filterPositionValue.setText("");
        });
    }

    /**
     * Zwraca wpisywany text
     *
     * @return wpisane litery tekstu
     */
    public String getFilterValue() {
        return mBinding.filterPositionValue.getText().toString();
    }

    @BindingAdapter(value = {"bind:filterTitle", "bind:filterStringValue", "bind:filterDateValue"}, requireAll = false)
    public static void setFilterBinding(FilterPositionView positionView, String filterTitle,
                                        String filterStringValue, Long filterDateValue) {
        positionView.mBinding.filterPositionTitle.setText(filterTitle);
        if (filterStringValue != null)
            positionView.mBinding.filterPositionValue.setText(filterStringValue);
        if (filterDateValue != null)
            positionView.mBinding.filterPositionValue.setText(DateTimeFormatUtil.format(filterDateValue));
    }

    @BindingAdapter(value = {"android:afterTextChanged", "android:filterStringValuetAttrChanged"}, requireAll = false)
    public static void setTextWatcher(FilterPositionView filterPositionView, final TextViewBindingAdapter.AfterTextChanged after,
                                      final InverseBindingListener textAttrChanged) {
        TextWatcher newValue = new TextWatcher() {

            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
            }

            @Override
            public void afterTextChanged(Editable s) {
                if (after != null) {
                    after.afterTextChanged(s);
                }
                if (textAttrChanged != null) {
                    textAttrChanged.onChange();
                }
            }
        };
        TextWatcher oldValue = ListenerUtil.trackListener(filterPositionView.mBinding.filterPositionValue, newValue, R.id.textWatcher);
        if (oldValue != null) {
            filterPositionView.mBinding.filterPositionValue.removeTextChangedListener(oldValue);
        }
        filterPositionView.mBinding.filterPositionValue.addTextChangedListener(newValue);
    }
}

Of course You have to add @={} in your XML layouts like below:

<com.example.customviews.FilterPositionView
                style="@style/verticalLabeledValueStyle"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                bind:filterTitle="@{@string/filter_product}"
                bind:filterStringValue="@={sfmodel.product}"/>
Sign up to request clarification or add additional context in comments.

1 Comment

I have same issue but do not wanna have my custom view to have any dependency to data binding. Just like textView that has nothing with data binding but you can use its attribute "text" as two way data binding. So i wanna prepare some thing that if my library users wanna use it with databinding they can do that.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.