监听多个EditText的TextWatcher

75

我想为多个EditText字段实现TextWatcher接口。目前我正在使用:

text1.addTextChangedListener(this);
text2.addTextChangedListener(this);

然后在我的Activity中覆盖这些方法:

public void afterTextChanged(Editable s) {}

public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int before, int count) 
{
 // do some operation on text of text1 field
 // do some operation on text of text2 field 
}

目前这种做法可以正常工作,但我正在寻找其他方法,以便能够明确地识别当前 SoftKeyboard 正在聚焦于哪个 EditText 字段。


1
可能是重复的问题:如何为多个EditText使用单个TextWatcher? - Sky Kelsey
1
@SkyKelsey,您所编写的答案实际上创建了多个TextWatcher实例,因此我认为这不是OP所问的完美答案,而我的问题是关于在TextWatcher接口方法中识别每个EditText。 - Vikas Patidar
https://github.com/henrychuangtw/AutoInsertEditText - HenryChuang
17个回答

97

@Sebastian Roth的答案中提出的建议不是为某些EditTexts创建一个TextWatcher的实例,而是为n个EditTexts创建一个类并创建n个该类的实例。

每个EditText都有自己的Spannable。 TextWatcher的事件将这个Spannable作为s参数。我检查它们的hashCode(每个对象的唯一标识符)。myEditText1.getText()返回该Spannable。因此,如果myEditText1.getText().hashCode()等于s.hashCode(),则意味着s属于myEditText1

因此,如果您想为一些EditTexts创建一个TextWatcher的实例,则应使用以下代码:

private TextWatcher generalTextWatcher = new TextWatcher() {    

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

        if (myEditText1.getText().hashCode() == s.hashCode())
        {
            myEditText1_onTextChanged(s, start, before, count);
        }
        else if (myEditText2.getText().hashCode() == s.hashCode())
        {
            myEditText2_onTextChanged(s, start, before, count);
        }
    }

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

        if (myEditText1.getText().hashCode() == s.hashCode())
        {
            myEditText1_beforeTextChanged(s, start, count, after);
        }
        else if (myEditText2.getText().hashCode() == s.hashCode())
        {
            myEditText2_beforeTextChanged(s, start, count, after);
        }
    }

    @Override
    public void afterTextChanged(Editable s) {
        if (myEditText1.getText().hashCode() == s.hashCode())
        {
            myEditText1_afterTextChanged(s);
        }
        else if (myEditText2.getText().hashCode() == s.hashCode())
        {
            myEditText2_afterTextChanged(s);
        }
    }

};

myEditText1.addTextChangedListener(generalTextWatcher);
myEditText2.addTextChangedListener(generalTextWatcher);

3
你有没有仔细检查过这个解决方案?如果用户在两个输入框中输入相同的文本会发生什么? - Sebastian Roth
16
每个EditText都有自己的Spannable对象。当发生TextWatcher事件时,这个Spannable对象会作为CharSequence或Editable参数。我不检查EditText的文本值,而是检查它们的hashCode(每个对象的唯一ID)。myEditText1.getText()返回的是该Spannable对象,而不是文本值。因此,如果myEditText1.getText().hashCode()等于s.hashCode(),那么s就属于myEditText1。 - Bobs
1
好的,感谢您的编辑。但是现在我有另一个问题(我对Android和Java还比较新,请耐心等待)。我不明白为什么要使用hashCode()方法来测试Spannable对象是否为相同对象。为什么不能使用“==”进行比较呢?(在C#中,我会使用ObjectEquals(),但在Java中,是不是只需要使用“==”?) - RenniePet
1
这是一个糟糕的解决方案。一旦您有两个具有相同字符串的EditText,它将失败,因为它们在内存中将是相同的字符串(因此具有相同的哈希码)。 - Eurig Jones
1
你确定所有哈希值都是唯一的吗?哈希冲突呢? - miva2
显示剩余6条评论

89

我会这样做:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    EditText e = new EditText(this);
    e.addTextChangedListener(new CustomTextWatcher(e));
}

private class CustomTextWatcher implements TextWatcher {
    private EditText mEditText;

    public CustomTextWatcher(EditText e) { 
        mEditText = e;
    }

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

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

    public void afterTextChanged(Editable s) {
    }
}

6
有趣的想法,但这意味着你需要为每个EditText单独创建一个文本监视器。 - ArtOfWarfare
13
这个答案不是“一个TextWatcher控制多个EditText”,而是使用了一个TextWatcher类的三个实例。因此,三个独立的TextWatcher控制着三个EditText。 - Bobs
1
@breceivemail,一个类,3个实例。当人们不指定对象类型时,这确实很困难。他的目标是只编写一次代码,所以就是这样。 - SparK
这有什么特别的?我不明白。 - r3dm4n
@Bobs,我认为制作3个对象比编写一个困难的对象并支持其未来更改要容易。 - CoolMind

15

我使用这个解决方案:

  • 添加一个返回监听器的方法:

private TextWatcher getTextWatcher(final EditText editText) {
    return 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) {
            // do what you want with your EditText
            editText.setText("blabla");
        }

        @Override
        public void afterTextChanged(Editable editable) {

        }
    };
}
  • 添加多个EditText的监听器,你也可以传递其他参数:

    editText1.addTextChangedListener(getTextWatcher(editText1));
    editText2.addTextChangedListener(getTextWatcher(editText2));
    editText3.addTextChangedListener(getTextWatcher(editText3));
    

  • 最好的答案...代码中没有任何需要更改的地方。非常感谢您。 - Vishal Patel
    简单因此是“最佳答案”。 - Vibhav
    运行得非常顺利。 - Kunwar Shekhar Singh

    12

    --编辑--

    如果您只想在afterTextChanged中使用editables进行比较:

    @Override
    public void afterTextChanged(Editable editable) {
        if (editable == mEditText1.getEditableText()) {
            // DO STH
        } else if (editable == mEditText2.getEditableText()) {
            // DO STH
        }
    }
    

    1
    这是否假设多个EditText视图中永远不会有相同的文本?根据应用程序,这可能不是一个有效的假设。 - RenniePet
    我非常抱歉,但我现在确信我错了。我现在认为你的原始版本和编辑后的版本都是正确的 - 你正在比较EditText.getEditableText()返回的Java对象与提供的实现Editable接口的对象,而不是涉及到的文本。(在我的测试中,这两个对象似乎总是一个SpannableStringBuilder对象,而Google的SpannableStringBuilder.equals()文档清楚地说明它比较的是对象,而不是包含的文本。) - RenniePet
    @RenniePet 我先前回答的版本是错误的。我们不应该比较编辑文本的值。现在它运行得很好。我认为我的答案应该在这个线程的顶部某处。 - Tomasz
    由于某些原因,这在我的模拟器中可以运行,但在真实设备上无法运行。最终,这个答案对我有用:https://dev59.com/I2855IYBdhLWcg3wik-D#13787221 比较EditablehashCode() - Mahmudul Hasan Shohag

    12

    使用"CustomTextWatcher"的思路,我完成了以下操作:

    1)创建了一个新的TextWatcherListener接口:

    public interface TextWatcherExtendedListener extends NoCopySpan
    {
        public void afterTextChanged(View v, Editable s);
    
        public void onTextChanged(View v, CharSequence s, int start, int before, int count);
    
        public void beforeTextChanged(View v, CharSequence s, int start, int count, int after);
    }
    

    2) 创建并使用了EditTextExtended,而不是EditText(在我的情况下):

    public class EditTextExtended extends EditText
    {
       private TextWatcherExtendedListener  mListeners = null;
    
       public EditTextExtended(Context context) 
       {
         super(context);
       }
    
       public EditTextExtended(Context context, AttributeSet attrs)
       {
          super(context, attrs);
       }
    
       public EditTextExtended(Context context, AttributeSet attrs, int defStyle)
       {
            super(context, attrs, defStyle);
       }
    
       public void addTextChangedListener(TextWatcherExtendedListener watcher) 
       {    
           if (mListeners == null) 
           {
               mListeners = watcher;
           }
       }
    
       public void removeTextChangedListener(TextWatcherExtendedListener watcher) 
       {
           if (mListeners != null) 
           {
               mListeners = null;        
           }
       }
    
       void  sendBeforeTextChanged(CharSequence text, int start, int before, int after)
       {
           if (mListeners != null) 
           {
               mListeners.beforeTextChanged(this, text, start, before, after);
           }
       }
    
       void  sendOnTextChanged(CharSequence text, int start, int before,int after) 
       {
           if (mListeners != null) 
           {
               mListeners.onTextChanged(this, text, start, before, after);
           }
       }
    
       void  sendAfterTextChanged(Editable text) 
       {
           if (mListeners != null)
           {
               mListeners.afterTextChanged(this, text);
           }
       }
    }
    

    3) 因此,您需要在哪里编写此代码:

    myEditTextExtended.addTextChangedListener(this) //Let implement TextWatcherExtendedListener methods
    

    4) 使用它们:

    @Override
    public void onTextChanged(View v, CharSequence s, int start, int before, int count) 
    {
       //Tested and works
       //do your stuff  
    }
    
    
    @Override
    public void beforeTextChanged(View v, CharSequence s, int start, int count, int after)
    {   
         //not yet tested but it should work    
    }
    
    @Override
    public void afterTextChanged(View v, Editable s) 
    {
        //not yet tested but it should work 
    }
    

    好的,请让我知道您的想法。


    我认为这是解决这个问题最直接的方法。+1 - AustrianDude

    3

    我将其实现为:

    edittext1.addTextChangedListener(this);
    edittext2.addTextChangedListener(this);
    edittext3.addTextChangedListener(this);
    

    并且:

    @Override
    public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
    
    }
    
    @Override
    public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
        if(edittext1.hasFocus()){
            //text changed for edittext1
        }else if(edittext2.hasFocus()){
            //text changed for edittext2
        }else {
            //text changed for edittext3
        }
    }
    
    @Override
    public void afterTextChanged(Editable editable) {
    
    }
    

    2

    在尝试了几种方法后,我发现使用EditText.isFocused()是区分一个输入框与另一个输入框的正确方法。例如:

        private class OnTextChangedListener implements 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) {
            if (edtName.isFocused()) {
                //do something
            } else if (edtEmail.isFocused()) {
                //do something
            } else if (edtContent.isFocused()) {
                 //do something
            }
        }
    }
    

    2
    另一种方法是将OnClickListener添加到EditText,并设置一个全局变量,如下所示。
    EditText etCurrentEditor;//Global variable
    
    @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
            if(v instanceof EditText){
                etCurrentEditor=(EditText)v;
            }
        }
    

    使用etCurrentEditor作为当前编辑的EditText的参考。
    @Override
        public void afterTextChanged(Editable editable) {
            // TODO Auto-generated method stub
            switch (etCurrentEditor.getId()) {
            case R.id.EDITTEXTID:
                break;
            default:
                break;
            }
        }
    

    1
    您可以使用以下方法获取编辑文本的ID。虽然未经测试,但如果有效,请告知我。

    //setting textWatcher for the editText
    textWatcher(owner_name);
    
    
    public void textWatcher(final EditText editText){
    
        TextWatcher watcher = new TextWatcher() {
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
    
               if(editText.getId()==R.id.your_id){
                 //Do something
               }   
            }
    
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
              if(editText.getId()==R.id.your_id){
              //Do something
              }
            }
    
            @Override
            public void afterTextChanged(Editable s) { }
        };
    
        editText.addTextChangedListener(watcher);
    }
    

    1
    是的,您可以使用多个自定义TextWatcher实例来存储TextView。(TextView实际上是具有addTextChangedListener的类。)
    与上面的hashCode解决方案类似,您只需检查getText()==s。 而不是存储所有控件或多次findViewById,您可以自己扫描一次内容树以获取具有CharSequence的控件。
    public TextView findTextView(View v, CharSequence s)
    {
       TextView tv;
       ViewGroup vg;
       int i, n;
    
       if (v instanceof TextView)
       {
          tv = (TextView) v;
          if (tv.getText()==s) return(tv);
       }
    
       else if (v instanceof ViewGroup)
       {
          vg = (ViewGroup) v;
          n = vg.getChildCount();
          for(i=0;i<n;i++)
          {
             tv = findTextView(vg.getChildAt(i), s);
             if (tv!=null) return(tv);
          }
       }
    
       return(null);
    }
    
    public void afterTextChanged(Editable s)
    {
       TextView tv=findTextView(findViewById(android.R.id.content), s);
       if (tv==null) return;
       switch(tv.getId())
       {
          case R.id.path:
             break;
          case  R.id.title:
             break;
       }
    }
    

    当然,您也可以在beforeTextChangedonTextChanged中使用findTextView

    很棒的解决方案。当在TextWatcher重写方法中使用时,它可以识别我正在编辑的EditText。您能否稍微解释一下您是如何得出这个解决方案并使用findViewById(android.R.id.content)来获取编辑视图的? - Randika Vishman
    findViewById(android.R.id.content) 有点过度设计,它会得到包含内容的 ViewGroup。最好选择包含所有感兴趣的文本更改的最底层的 ViewGroup - Renate

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接