Como usar o Single TextWatcher para vários EditTexts?

Respostas:

190

Acabei de encontrar esse problema. Eu o resolvi criando uma implementação de classe interna TextWatcherque usa uma View como argumento. Em seguida, na implementação do método, basta ativar a exibição para ver de qual Editableé a origem.

Declaração:

private class GenericTextWatcher implements TextWatcher{

    private View view;
    private GenericTextWatcher(View view) {
        this.view = view;
    }

    public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
    public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}

    public void afterTextChanged(Editable editable) {
        String text = editable.toString();
        switch(view.getId()){
            case R.id.name:
                model.setName(text);
                break;
            case R.id.email:
                model.setEmail(text);
                break;
            case R.id.phone:
                model.setPhone(text);
                break;
        }
    }
}

Uso:

name = (EditText) findViewById(R.id.name);
name.setText(model.getName());
name.addTextChangedListener(new GenericTextWatcher(name));

email = (EditText) findViewById(R.id.email);
email.setText(model.getEmail());
email.addTextChangedListener(new GenericTextWatcher(email));

phone = (EditText) findViewById(R.id.phone);
phone.setText(model.getPhone());
phone.addTextChangedListener(new GenericTextWatcher(phone));
Sky Kelsey
fonte
1
Você pode dizer o que é 'modelo' no código acima e onde declará-lo?
YuDroid 25/06
33
esta resposta não é Single TextWatcher for multiple EditTexts. São 3 instâncias de uma classe TextWatcher. Portanto, três TextWatchers separados estão controlando 3 EditTexts.
Bobs
2
A solução sugerida não é um TextWatcher para alguns EditTexts. Verifique esta resposta: stackoverflow.com/a/13787221/779408
Bobs
2
Funciona como um encanto, combinando isso com um fragmento contendo elementos de formulário para não perder dados ao alterar a orientação do aspecto.
Mathijs Segers
1
@breceivemail Para ser completamente justo, "TextWatcher único" não significa necessariamente uma única instância, também pode ser uma classe única.
Malcolm
42

Se você deseja usar apenas afterTextChanged, compare os editáveis:

@Override
public void afterTextChanged(Editable editable) {
    if (editable == mEditText1.getEditableText()) {
        // DO STH
    } else if (editable == mEditText2.getEditableText()) {
        // DO STH
    }
}
Tomasz
fonte
10
Isso só funcionará corretamente se você usar em ==vez de .equals().
Jarett Millard 17/10
2
Você é um gênio! Isso está apenas comparando ponteiros e não valores reais armazenados no Editável! Sim!
Burak Tamtürk
3
e se mEditText1 e mEditText2 tiverem o mesmo texto?
Tuss
@tuss é por isso que eles são comparados por referência em vez do valor
Joakim
1
@Tomasz Como isso funciona? Você implementou o TextWatcher? Você não precisa substituir três métodos, se o fizer?
Leonheess 17/10/19
10

Implementação do MultiTextWatcher

public class MultiTextWatcher {

    private TextWatcherWithInstance callback;

    public MultiTextWatcher setCallback(TextWatcherWithInstance callback) {
        this.callback = callback;
        return this;
    }

    public MultiTextWatcher registerEditText(final EditText editText) {
        editText.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                callback.beforeTextChanged(editText, s, start, count, after);
            }

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

            @Override
            public void afterTextChanged(Editable editable) {
                callback.afterTextChanged(editText, editable);
            }
        });

        return this;
    }

    interface TextWatcherWithInstance {
        void beforeTextChanged(EditText editText, CharSequence s, int start, int count, int after);

        void onTextChanged(EditText editText, CharSequence s, int start, int before, int count);

        void afterTextChanged(EditText editText, Editable editable);
    }
}

Uso

    new MultiTextWatcher()
            .registerEditText(editText1)
            .registerEditText(editText2)
            .registerEditText(editText3)
            .setCallback(new TextWatcherWithInstance() {
                @Override
                public void beforeTextChanged(EditText editText, CharSequence s, int start, int count, int after) {
                    // TODO: Do some thing with editText
                }

                @Override
                public void onTextChanged(EditText editText, CharSequence s, int start, int before, int count) {
                    // TODO: Do some thing with editText
                }

                @Override
                public void afterTextChanged(EditText editText, Editable editable) {
                    // TODO: Do some thing with editText
                }
            });
Ilya Gazman
fonte
Por que você criaria outra classe para encadeá-las ?! Quero dizer, você ainda não sabe de onde TextViewvem a mudança.
Farid
10

Se você deseja usar a comparação onTextChangedhashCode() mencionada abaixo -

@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
    if(charSequence.hashCode() == first_edit_text.getText().hashCode()){
        // do other things 
    }

    if(charSequence.hashCode() == second_edit_text.getText().hashCode()){
       // do other things 
    }

}

Ou

Se você deseja usar a comparação afterTextChangedEditable mencionada abaixo -

@Override
public void afterTextChanged(Editable editable) {
    if (editable == first_edit_text.getEditableText()) {
        // do other things 
    } else if (editable == second_edit_text.getEditableText()) {
       // do other things 
    }
}
Aman Jham
fonte
9

Funcionará com este código

TextWatcher watcher = new TextWatcher() {
  @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
            //YOUR CODE
        }

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

        @Override
        public void afterTextChanged(Editable s) {
          String outputedText = s.toString();

  mOutputText.setText(outputedText);

        }
    };

Em seguida, adicione isso no oncreate

  mInputText.addTextChangedListener(watcher);
        e2.addTextChangedListener(watcher);
        e3.addTextChangedListener(watcher);
        e4.addTextChangedListener(watcher);
Asha Babu
fonte
Então, de onde é o mOutputText?
Kimi Chiu
5

Sei que esse é um problema antigo e que existe a decisão certa. Vou escrever a sua própria, talvez ajude alguém.

Emulando o exemplo clássico em que temos N EditText, e queremos mostrar o botão se todos os campos estiverem preenchidos. Este exemplo faz sentido, especialmente se usar validadores para cada um deles.

Fiz um exemplo com relação ao problema, mas você pode fazer qualquer conjunto

MultiEditText.class

public class MultiEditText extends AppCompatActivity{

EditText ed_1, ed_2, ed_3;
Button btn_ok;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.multi_edit_text);

    ed_1 = (EditText) findViewById(R.id.ed_1);
    ed_2 = (EditText) findViewById(R.id.ed_2);
    ed_3 = (EditText) findViewById(R.id.ed_3);
    btn_ok = (Button) findViewById(R.id.btn_ok);
    btn_ok.setEnabled(false);

    //if want more here can cycle interface List

     EditText[] edList = {ed_1, ed_2, ed_3};
     CustomTextWatcher textWatcher = new CustomTextWatcher(edList, btn_ok);
     for (EditText editText : edList) editText.addTextChangedListener(textWatcher);

    }
}

Parece muito simples, agora

CustomTextWatcher.class

public class CustomTextWatcher implements TextWatcher {

View v;
EditText[] edList;

public CustomTextWatcher(EditText[] edList, Button v) {
    this.v = v;
    this.edList = edList;
}

@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) {
    for (EditText editText : edList) {
        if (editText.getText().toString().trim().length() <= 0) {
            v.setEnabled(false);
            break;
        }
        else v.setEnabled(true);
    }
  }
}

Vou adicionar um layout, para que você não perca tempo

multi_edit_text.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">

<EditText
    android:id="@+id/ed_1"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentTop="true"
    android:layout_centerHorizontal="true"
    android:layout_marginTop="8dp" />

<EditText
    android:id="@+id/ed_2"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_below="@+id/ed_1"
    android:layout_centerHorizontal="true"
    android:layout_marginTop="8dp" />

<EditText
    android:id="@+id/ed_3"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_below="@+id/ed_2"
    android:layout_centerHorizontal="true"
    android:layout_marginTop="8dp" />

<Button
    android:id="@+id/btn_ok"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_below="@+id/ed_3"
    android:layout_centerHorizontal="true"
    android:layout_marginTop="8dp"
    android:text="OK" />
</RelativeLayout>
Shwarz Andrei
fonte
5

Faça sua classe herdar de Activity e implementar o TextWatcher.

Então, através da magia do polimorfismo, você só precisa se inscrever nos eventos.

Isso não vai lhe dizer o que o TextEdit mudou, no entanto, usando uma combinação disso e da resposta de Sky Kelsey , você pode resolver isso de maneira adequada.

public YourActivity extends Activity implements TextWatcher {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_YourActivity);

        //Subscribe to the events
        EditText txt1 = (EditText) findViewById(R.id.txt1);
        txt1.addTextChangedListener(this);

        EditText txt2 = (EditText) findViewById(R.id.txt2);
        txt2.addTextChangedListener(this);
    }

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

            EditText txt1 = (EditText) findViewById(R.id.txt1);
            EditText txt2 = (EditText) findViewById(R.id.txt2);
            // You probably only want the text value from the EditText. But you get the idea. 
                doStuff(txt1,txt2);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.calc, menu);
        return true;
    }

    @Override
    public void afterTextChanged(Editable s) {
        // TODO Auto-generated method stub
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        // TODO Auto-generated method stub
    }
}
NitroxDM
fonte
3
TextWatcher watcher = 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) {
    }
};

Então:

editText1.addTextChangedListener(watcher);
editText2.addTextChangedListener(watcher);
editText3.addTextChangedListener(watcher);
Cristian
fonte
2
(Aviso de pós-lamentação) Ele provavelmente possui um código de validação muito semelhante para todos os controles e não deseja copiá-lo e colá-lo três vezes :) Já o bati antes, por que eles podem enviar o controle que gerou o clique no onClickListener e não em coisas como o TextWatcher ... A única solução alternativa em que consigo pensar é criar três TextWatchers que chamam o mesmo procedimento, mas com um ponteiro para seus respectivos controles de edição.
quer
1
@Torp, @bie: Essa resposta pode ser interessante: stackoverflow.com/questions/4283062/… Não tenho certeza de que resolveu exatamente o problema descrito aqui, mas, como mostrado, você pode fazer com que o CustomTextWatcher chame automaticamente outra função que aceite o passou Editável.
Kevin Coppock
3
public class MainActivity extends AppCompatActivity{
    EditText value1, value2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //instantiate EditText controls
        value1 = (EditText)findViewById(R.id.txtValue1);
        value2 = (EditText)findViewById(R.id.txtValue2);

        //set up text changed listener
        value1.addTextChangedListener(new TextChange(value1));               
        value2.addTextChangedListener(new TextChange(value2));                       

        //inner class
        private class TextChange implements TextWatcher {

             View view;
             private TextChange (View v) {
                 view = v;
             }

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

             }


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

                 switch (view.getId()) {
                     case R.id.txtValue1:
                         //insert your TextChangedListener codes here
                         break;

                     case R.id.txtValue2:
                         //insert your TextChangedListener codes here
                         break;
                 }
             }   
         }
     }
}
Ng Kian Keong
fonte
3

Esta é a minha solução para o kotlin. Você pode simplesmente usar Igualdade referencial (===) para verificar o mesmo objeto e ele está funcionando perfeitamente.

val mTextWatcher = object : TextWatcher {
        override fun afterTextChanged(et: Editable?) {

            when {
                et === et1.editableText -> {
                    Toast.makeText(this@MainActivity, "EditText 1", Toast.LENGTH_LONG).show()
                }
                et === et2.editableText -> {
                    Toast.makeText(this@MainActivity, "EditText 2", Toast.LENGTH_LONG).show()
                }

            }
        }

        override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
        }
        override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
        }
    }
    et1.addTextChangedListener(mTextWatcher)
    et2.addTextChangedListener(mTextWatcher)
Shohan Ahmed Sijan
fonte
0

Sei que essa pergunta é antiga, mas queria compartilhar uma das minhas soluções (no Kotlin). Minha solução é uma melhoria da resposta de @Shwarz Andrei, minha razão era o que se você quisesse manipular mais coisas / objetos.

Em vez de passar ambos list of EditTextse a Buttoncomo parâmetros, você apenas passaria o seu list of editText. Em sua classe personalizada, você implementaria um lambda como:

var hasFilled:((Boolean)->Unit)? = null 

Então você o definirá ou aumentará dentro do afterTextChanged

override fun afterTextChanged(p0: Editable?) {
       for (edit in _editTextList) {
           if (edit?.text.toString().trim().isEmpty()) {
                 hasFilled?.invoke(false) //<-- here 
               break
           } else {
               hasFilled?.invoke(true) //<--- here 
           }
       }
   }

Portanto, toda vez que há uma alteração em alguns EditText, sua lambda é invocada

        val editTexts = listOf(emailEditText,passwordEditText) // your list of editText
        val textWatcher = customTextWatcher(editTexts) // initialize your custom object 
        editTexts.forEach { it -> it?.addTextChangedListener(textWatcher) } // each editText would listen for changes 


        textWatcher.hasFilled = { value ->  // now you have access to your lambda 
            if (value != true)  {
               // change the state of the button to unable 
              // do other things 
            } else {
              // change the state of the button to enable 
              // do other things 
            }
        }
Lamour
fonte
0

Aqui está como eu fiz isso:

Crie um ArrayList de EditTexts e use um loop for para aplicar o TextWatcher a todos os EditTexts, se você tiver um comportamento para todos os editTexts, apenas aplique-o lá, se você tiver comportamentos específicos para alguns editTexts específicos, você pode usar if para selecionar e aplicar a editTexts individuais.

Aqui está o meu código:

ArrayList<EditText> editTexts = new ArrayList<>(); // Container list

editText1 = (EditText) findViewById(R.id.editText1);
editText2 = (EditText) findViewById(R.id.editText2);
editText3 = (EditText) findViewById(R.id.editText3);

editTexts.add(editText1); // editTexts[0]
editTexts.add(editText2); // editTexts[1]
editTexts.add(editText3); // editTexts[2]

for (final EditText editText : editTexts) { //need to be final for custom behaviors 
    editText.addTextChangedListener(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) {
            //Apply general behavior for all editTexts

            if (editText == editTexts.get(1)) {
                //Apply custom behavior just for this editText                           
            }
        }
    });

}

Espero que isto ajude

SamiHajjaj
fonte
Obrigado pela resposta, mas esta é realmente a única maneira de fazer isso? Quero dizer, parece meio complicado para algo tão comum quanto um onTextChangedem um EditText. Adicionar um onFocusChangepara vários widgets é muito mais simples, porque ele passou o objeto remetente com a chamada de método. Em seguida, você pode examinar qual objeto acionou a chamada e lidar com ela a partir daí.
BdR