Android上的文本更改监听器

355

我有一个情况,有两个字段。field1field2。我想做的就是当field1被更改时清空field2,反之亦然。因此最终只有一个字段有内容。

field1 = (EditText)findViewById(R.id.field1);
field2 = (EditText)findViewById(R.id.field2);

field1.addTextChangedListener(new TextWatcher() {

   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) {
      field2.setText("");
   }
  });

field2.addTextChangedListener(new TextWatcher() {

   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) {
     field1.setText("");
   }
  });

如果我只将addTextChangedListener附加到field1,那么它可以正常工作,但是当我对两个字段都进行附加时,应用程序会崩溃。显然,这是因为它们无限地尝试相互更改。一旦field1发生变化,它就会在此时清除field2。此时field2被更改,所以它将清除field1,以此类推......

有人能提出任何解决方案吗?


1
对于新用户而言,在进行双向数据绑定时,应当使用可观察字符串字段。所有在此处提供的解决方案都可能会产生“starting waiting blocking gc alloc”这种类型的错误,甚至可能导致崩溃和挂起。因此,请使用数据绑定,这是现在由谷歌推荐的安全方法。 - Maifee Ul Asad
19个回答

559

您可以添加一个检查来仅在字段中的文本不为空时清除(即长度不为0时)。

field1.addTextChangedListener(new TextWatcher() {

   @Override
   public void afterTextChanged(Editable s) {}

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

   @Override    
   public void onTextChanged(CharSequence s, int start,
     int before, int count) {
      if(s.length() != 0)
        field2.setText("");
   }
  });

field2.addTextChangedListener(new TextWatcher() {

   @Override
   public void afterTextChanged(Editable s) {}

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

   @Override
   public void onTextChanged(CharSequence s, int start,
     int before, int count) {
      if(s.length() != 0)
         field1.setText("");
   }
  });

TextWatcher的文档在这里

同时请遵守命名约定


1
如何检测所有字段更改后,因为它会在任何按钮被按下时检测每次更改。 - Rafael Guimarães
我可以问一下,如果我正在使用TextInputLayout会怎样? - Joshua Tabi

57
在 Kotlin 中只需使用 KTX 扩展函数:(它使用 TextWatcher)
yourEditText.doOnTextChanged { text, start, count, after -> 
        // action which will be invoked when the text is changing
    }


导入core-KTX

implementation "androidx.core:core-ktx:1.2.0"

1
很好。整洁多了。 - Alt-Cat
Core 的最新版本可在 https://developer.android.com/jetpack/androidx/releases/core 上获取。 - Francis
当用户旋转屏幕时调用。 - t0m

26

我知道这个问题有点老,但是某天会有人再次遇到它。

我曾经遇到一个类似的问题,当我调用EditText的setText方法时,onTextChanged方法也被调用了,而我并不希望它被调用。我的第一种解决方案是在调用setText()之后编写一些代码来撤销监听器所做的操作,但这并不是很优雅。

经过一些研究和测试,我发现使用getText().clear()可以清除文本,就像使用setText("")一样,但由于它没有设置文本,因此监听器不会被调用,这解决了我的问题。我将所有的setText("")调用都改成了getText().clear(),然后我就不需要这些hack了,也许这也能解决你的问题。

试试这个:

Field1 = (EditText)findViewById(R.id.field1);
Field2 = (EditText)findViewById(R.id.field2);

Field1.addTextChangedListener(new TextWatcher() {

   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) {
      Field2.getText().clear();
   }
  });

Field2.addTextChangedListener(new TextWatcher() {

   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) {
     Field1.getText().clear();
   }
  });

17
如果你在进行Android开发时使用Kotlin语言,那么你可以使用以下代码添加TextChangedListener():TextChangedListener()
myTextField.addTextChangedListener(object : TextWatcher{
        override fun afterTextChanged(s: Editable?) {}

        override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}

        override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
    })

14
var filenameText = findViewById(R.id.filename) as EditText
filenameText.addTextChangedListener(object : TextWatcher {
    override fun afterTextChanged(s: Editable?) {
        filename = filenameText.text.toString()
        Log.i("FileName: ", filename)
    }
    
    override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
    override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
})

7

虽然有点晚了,但这里有一个可重复使用的解决方案:

/**
 * An extension of TextWatcher which stops further callbacks being called as 
 * a result of a change happening within the callbacks themselves.
 */
public abstract class EditableTextWatcher implements TextWatcher {

    private boolean editing;

    @Override
    public final void beforeTextChanged(CharSequence s, int start, 
                                                    int count, int after) {
        if (editing)
            return;

        editing = true;
        try {
            beforeTextChange(s, start, count, after);
        } finally {
            editing = false;
        }
    }

    protected abstract void beforeTextChange(CharSequence s, int start, 
                                                     int count, int after);

    @Override
    public final void onTextChanged(CharSequence s, int start, 
                                                int before, int count) {
        if (editing)
            return;

        editing = true;
        try {
            onTextChange(s, start, before, count);
        } finally {
            editing = false;
        }
    }

    protected abstract void onTextChange(CharSequence s, int start, 
                                            int before, int count);

    @Override
    public final void afterTextChanged(Editable s) {
        if (editing)
            return;

        editing = true;
        try {
            afterTextChange(s);
        } finally {
            editing = false;
        }
    }

    public boolean isEditing() {
        return editing;
    }

    protected abstract void afterTextChange(Editable s);
}

因此,当使用上述方法时,在TextWatcher内部发生的任何setText()调用都不会导致再次调用TextWatcher:

/**
 * A setText() call in any of the callbacks below will not result in TextWatcher being 
 * called again.
 */
public class MyTextWatcher extends EditableTextWatcher {

    @Override
    protected void beforeTextChange(CharSequence s, int start, int count, int after) {
    }

    @Override
    protected void onTextChange(CharSequence s, int start, int before, int count) {
    }

    @Override
    protected void afterTextChange(Editable s) {
    }
}

6

我也遇到了同样的问题并不断收到 stackOverflow 异常,下面是我的解决方案。

edt_amnt_sent.addTextChangedListener(new TextWatcher() {    
    @Override
    public void afterTextChanged(Editable s) {
        if (skipOnChange)
            return;

        skipOnChange = true;
        try {
            //method
        } catch (NumberFormatException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            skipOnChange = false;
        }
    }
});

edt_amnt_receive.addTextChangedListener(new TextWatcher() {

    @Override
    public void afterTextChanged(Editable s) {

        if (skipOnChange)
            return;

        skipOnChange = true;
        try {
            //method
        } catch (NumberFormatException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            skipOnChange = false;
        }
    }
});

最初声明了一个布尔类型的skipOnChange变量,其初始值为false。


2
"stack full" 我认为你想说的是 Stack Overflow ;) - Just The Highlights

6
我为此编写了自己的扩展程序,对我非常有帮助。(Kotlin)
你可以只写这样:
editText.customAfterTextChanged { editable -> 
    //You have accessed the editable object. 
}

我的扩展:

fun EditText.customAfterTextChanged(action: (Editable?)-> Unit){
    this.addTextChangedListener(object : TextWatcher {
       override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
       override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {}
       override fun afterTextChanged(editable: Editable?) {
        action(editable)
    }
})}

谢谢您的回复。但是我不理解在这种情况下Kotlin语法。您能否为我讲解一下“editable ->”的语法,以及如何全局覆盖EditText字段而不影响其他活动中的所有EditText? - Bungles
抱歉,@Bungles,我无法理解。你想做什么? - Burak Dizlek
我只是想理解你上面的代码。 - Bungles
我向EditText添加了一个文本更改监听器。每次使用添加为参数的“action”函数进行更改时,此监听器都会返回一个值。 - Burak Dizlek

5
您可以使用hasFocus()方法:
public void onTextChanged(CharSequence s, int start,
     int before, int count) {
     if (Field2.hasfocus()){
         Field1.setText("");
     }
   }

我为大学作业测试了这个程序,它可以在用户输入时转换温度单位。它完美地运行,并且更加简洁。


1
当用户在EditText中输入时,editText.setText怎么样?在这种情况下,EditText具有焦点。 - Evgenii Vorobei
最佳解决方案。 - Hissaan Ali

3
editText.addTextChangedListener(new TextWatcher() 
{
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) 
            {
            }

            @Override
            public void onTextChanged(CharSequence chr, int i, int i1, int i2) 
            {
                //Check char sequence is empty or not
                if (chr.length() > 0)
                {
                    //Your Code Here
                }
            }

            @Override
            public void afterTextChanged(Editable editable) 
            {
            }
 });

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