Comment utiliser Single TextWatcher pour plusieurs EditTexts?

Réponses:

190

Je viens de rencontrer ce problème. Je l'ai résolu en créant une implémentation de classe interne TextWatcherqui prend une vue comme argument. Ensuite, dans l'implémentation de la méthode, activez simplement la vue pour voir de laquelle elle Editableprovient

Déclaration:

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

Usage:

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
la source
1
Pouvez-vous s'il vous plaît dire ce qu'est le «modèle» dans le code ci-dessus et où le déclarer?
YuDroid
33
cette réponse n'est pas Single TextWatcher for multiple EditTexts. Il s'agit de 3 instances d'une classe TextWatcher. Donc, 3 TextWatchers distincts contrôlent 3 EditTexts.
Bobs
2
La solution suggérée n'est pas un TextWatcher pour certains EditTexts. Vérifiez cette réponse: stackoverflow.com/a/13787221/779408
Bobs
2
Fonctionne comme un charme, en le combinant avec un fragment contenant des éléments de formulaire pour ne pas perdre de données lors du changement d'orientation de l'aspect.
Mathijs Segers
1
@breceivemail Pour être tout à fait juste, "single TextWatcher" ne signifie pas nécessairement une seule instance, peut aussi être une seule classe.
Malcolm
42

Si vous souhaitez utiliser uniquement les éditables de comparaison afterTextChanged:

@Override
public void afterTextChanged(Editable editable) {
    if (editable == mEditText1.getEditableText()) {
        // DO STH
    } else if (editable == mEditText2.getEditableText()) {
        // DO STH
    }
}
Tomasz
la source
10
Cela ne fonctionnera correctement que si vous utilisez à la ==place de .equals().
Jarett Millard
2
Tu es un génie! Il s'agit simplement de comparer des pointeurs et non des valeurs réelles stockées sur Editable! Oui!
Burak Tamtürk
3
et si mEditText1 et mEditText2 ont le même texte?
Tuss
@tuss c'est pourquoi ils sont comparés par référence au lieu de valeur
Joakim
1
@Tomasz Comment ça marche? Avez-vous implémenté TextWatcher? N'avez-vous pas à remplacer trois méthodes si vous le faites?
leonheess
10

Implémentation de 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);
    }
}

Usage

    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
la source
Pourquoi voudriez-vous créer une autre classe pour les enchaîner?! Je veux dire que vous ne savez toujours pas d'où vient TextViewle changement.
Farid le
10

Si vous souhaitez utiliser la comparaison onTextChangedhashCode() mentionnée ci-dessous -

@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

Si vous souhaitez utiliser la comparaison afterTextChangedEditable mentionnée ci-dessous -

@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
la source
9

Cela fonctionnera avec ce code

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

        }
    };

Puis ajoutez ceci dans oncreate

  mInputText.addTextChangedListener(watcher);
        e2.addTextChangedListener(watcher);
        e3.addTextChangedListener(watcher);
        e4.addTextChangedListener(watcher);
Asha Babu
la source
Alors d'où vient le mOutputText?
Kimi Chiu
5

Je sais que c'est un vieux problème et qu'il y a la bonne décision. J'écrirai le leur, peut-être que cela aidera quelqu'un.

Émulation de l'exemple classique où nous avons N EditText, et nous voulons afficher le bouton si tous les champs sont remplis. Cet exemple est logique, surtout si vous utilisez davantage des validateurs pour chacun.

J'ai fait un exemple concernant le problème, mais vous pouvez faire n'importe quel ensemble

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

    }
}

Ça a l'air très simple, maintenant

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

Je vais ajouter une mise en page, pour ne pas perdre de temps

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
la source
5

Demandez à votre classe d'hériter d'Activity et d'implémenter TextWatcher.

Ensuite, grâce à la magie du polymorphisme, il vous suffit de vous abonner aux événements.

Cela ne vous dira pas ce que TextEdit a changé, mais en utilisant une combinaison de cela et de la réponse de Sky Kelsey , vous pourriez bien régler cela.

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
la source
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) {
    }
};

Ensuite:

editText1.addTextChangedListener(watcher);
editText2.addTextChangedListener(watcher);
editText3.addTextChangedListener(watcher);
Cristian
la source
2
(Message d'avertissement) Il a probablement un code de validation très similaire pour tous les contrôles et ne veut pas le copier et le coller 3 fois :) Je l'ai déjà frappé, pourquoi peuvent-ils envoyer le contrôle qui a généré le clic sur onClickListener et non sur des choses comme TextWatcher ... La seule solution de contournement à laquelle je peux penser est de créer 3 TextWatchers qui appellent la même procédure mais avec un pointeur vers leurs contrôles d'édition respectifs.
Torp
1
@Torp, @bie: Cette réponse pourrait être intéressante: stackoverflow.com/questions/4283062/… Je ne suis pas sûr que cela résout exactement le problème indiqué ici, mais, comme indiqué, vous pourriez demander au CustomTextWatcher d'appeler automatiquement une autre fonction qui prend en charge le passé Modifiable.
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
la source
3

C'est ma solution pour kotlin. Vous pouvez simplement utiliser l'égalité référentielle (===) pour vérifier le même objet et cela fonctionne parfaitement.

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
la source
0

Je sais que cette question est ancienne, mais je voulais partager une de mes solutions (à Kotlin). Ma solution est une amélioration de la réponse de @Shwarz Andrei, ma raison était de savoir si vous vouliez manipuler plus de choses / objets.

Au lieu de passer à la fois list of EditTextset en a Buttontant que paramètres, vous ne passeriez que votre list of editText. Ensuite, dans votre classe personnalisée, vous implémenteriez un lambda tel:

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

Ensuite, vous le placerez ou le rehausserez à l'intérieur du 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 
           }
       }
   }

Donc à chaque fois qu'il y a un changement dans certains EditText, votre lambda est invoqué

        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
la source
0

Voici comment je l'ai fait:

Créez un ArrayList de EditTexts, puis utilisez une boucle for pour appliquer le TextWatcher à tous les EditTexts, si vous avez un comportement pour tous les editTexts, appliquez-le simplement là, si vous spécifiez des comportements pour certains editTexts spécifiques, alors vous pouvez utiliser un if instruction pour sélectionner et appliquer à des editTexts individuels.

Voici mon code:

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

}

J'espère que cela t'aides

SamiHajjaj
la source
Merci pour la réponse, mais est-ce vraiment la seule façon de le faire? Je veux dire que cela semble un peu compliqué pour quelque chose d'aussi commun que onTextChangedsur un EditText. L'ajout d'un onFocusChangepour plusieurs widgets est beaucoup plus simple, car il a passé l'objet expéditeur avec l'appel de méthode. Ensuite, vous pouvez examiner quel objet a déclenché l'appel et le gérer à partir de là.
BdR