How to databind to onTextChanged for an EditText o

2020-01-28 03:45发布

问题:

In Yigit Boyar and George Mount's talk on Android Databinding they illustrate how easy it is to bind to TextWatcher's onTextChanged (at 13:41). On a Button. Are their slides wrong? First of all the Button View doesn't have an onTextChanged property. It neither has a setOnTextChanged method. Neither does EditText. But they both have addTextChangedListener which takes a TextWatcher as input.

So what are they talking about? How do they do it? Their example code does not compile, but gives this error:

Error:(17) No resource identifier found for attribute 'onTextChanged' in package 'android'

How do I bind to a "Text Changed Event" on any View, or EditText in particular, with the Android Databinding framework?

回答1:

Actually it works out of the box. I think my mistake was using an old version of the data binding framework. Using the latest, this is the procedure:

View:

<EditText
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/username"
    android:text="Enter username:"
    android:onTextChanged="@{data.onTextChanged}" />

Model:

public void onTextChanged(CharSequence s, int start, int before, int count) {
    Log.w("tag", "onTextChanged " + s);
}

Make sure you are referncing gradle build tools v1.5.0 or higher and have enabled databinding with android.dataBinding.enabled true in your build.gradle.

edit: Functioning demo project here. view. model.



回答2:

To extend @Nilzors answer, it is also possible to pass text and/or other parameters in the layout:

View:

<EditText
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/username"
    android:text="Enter username:"
    android:onTextChanged="@{(text, start, before, count) -> viewModel.onUsernameTextChanged(text)}" />

ViewModel:

public void onUsernameTextChanged(CharSequence text) {
    // TODO do something with text
}

You always need to pass either zero or all parameters.



回答3:

The Easy Way

If you are using onTextChange() to have updated text in model then you can directly use Two-way Binding.

<data>
    <variable
        name="user"
        type="com.package.User"/>
</data>

<EditText
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@={user.name}"/>

And you model class will have getter & setter.

public class User {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Now the Name in Model will be changed realtime with user interaction. So whenever use binding.getUser().getName(), you will get latest text.

One-Way Binding will only gets updated when model value is changed. It does not update model back real time.

android:text="@{user.name}"

Two-Way Binding update model variable in real time with user input.

android:text="@={user.name}"

The ONLY difference is of = (equal sign) receives data changes to the property and listen to user updates at the same time.



回答4:

the best way for this is adding bind adapter and a text watcher.

public class Model{
    private TextWatcher textWatcher;

public Model(){
        this.textWatcher= getTextWatcherIns();
}

private TextWatcher getTextWatcherIns() {
        return new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                //do some thing
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                //do some thing

            }

            @Override
            public void afterTextChanged(Editable s) {
                //do some thing
            }
        };
    }

    public TextWatcher getTextWatcher() {
        return textWatcher;
    }

    public void setTextWatcher(TextWatcher textWatcher) {
        this.textWatcher = textWatcher;
    }

    @BindingAdapter("textChangedListener")
    public static void bindTextWatcher(EditText editText, TextWatcher textWatcher) {
        editText.addTextChangedListener(textWatcher);
    }
}

and in your xml add this attr to your edit text

<EditText
            android:id="@+id/et"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:textChangedListener="@{model.textWatcher}" />


回答5:

  1. create a class (I named him BindingAdapters). Then define your bindingAdapter methods.

    @BindingAdapter("app:textChangedListener")
    fun onTextChanged(et: EditText, number: Int) {
    et.addTextChangedListener(object : TextWatcher {
        override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
            if (et.text.toString().trim().length >= number) {
                et.setBackgroundColor(Color.GREEN)
            }
        }
    
        override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
        override fun afterTextChanged(s: Editable) {}
    })
    

    }

  2. set this attr for editText in xml layout

    <EditText
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:textChangedListener="@{3}" />  
    


回答6:

If you just need text parameter after text has changed, you could use android:afterTextChanged binding adapter. for example:

android:afterTextChanged="@{(text) -> viewModel.onTextChange(text)}"

Then in your ViewModel just implement like this:

fun onTextChange(text: CharSequence) {
    Log.d("TAG","New text: $text")
}

Furthermore, there is android:beforeTextChanged which used to know old text before text change event, usage is same as android:afterTextChanged.



回答7:

Attach an setOnFocusChangeListener to the EditText, and use compare the textual content with a global variable (of the previous state/content of the field) to determine whether it has changed:

    mEditTextTitle.setOnFocusChangeListener(new View.OnFocusChangeListener() {
        @Override
        public void onFocusChange(View v, boolean hasFocus) {
            if(!hasFocus)
                // check if text has changed       
        }
    });