如何删除使用addTextChangedListener添加的所有监听器

82

我有一个 ListView, 每一行包含一个 EditText 控件。我想为每一行添加一个 TextChangedListener,其中包含额外的数据,表示 EditText 在哪一行。问题是,由于 convertView 已经有一个指向另一行的 TextWatcher,所以每次调用 getView 时都会添加多个 TextWatchers

MyTextWatcher watcher = new MyTextWatcher(currentQuestion);
EditText text = (EditText)convertView.findViewById(R.id.responseText);
text.addTextChangedListener(watcher);

MyTextWatcher 是我实现的一个类,它实现了 TextWatcher 接口并处理文本事件。CurrentQuestion 让我知道我正在处理哪一行。当我在文本框中输入时,会调用多个 TextWatcher 实例。

有没有办法在添加新的 TextWatcher 之前移除旧的?我看到了 removeTextChangedListener 方法,但它需要传递一个特定的 TextWatcher,而我不知道如何获取已经存在的 TextWatcher 的指针。


2
所有元素只使用一个TextWatcher不是更简单吗? - Jodes
1
当文本更改方法触发时,我如何知道哪一行被修改了? - GendoIkari
2
@Jodes:我认为最好不要为所有元素使用一个TextWatcher。如果您想要添加/删除它们,您需要不同的TextWatchers。 - IgorGanapolsky
15个回答

57

使用当前的EditText界面没有直接实现这一点的方法。 我看到两种可能的解决方案:

  1. 重新设计应用程序,以便您始终知道添加到特定EditText实例的TextWatcher
  2. 扩展EditText并添加清除所有观察者的可能性。

这里是第二种方法的示例 - ExtendedEditText

public class ExtendedEditText extends EditText
{   
    private ArrayList<TextWatcher> mListeners = null;

    public ExtendedEditText(Context ctx)
    {
        super(ctx);
    }

    public ExtendedEditText(Context ctx, AttributeSet attrs)
    {
        super(ctx, attrs);
    }

    public ExtendedEditText(Context ctx, AttributeSet attrs, int defStyle)
    {       
        super(ctx, attrs, defStyle);
    }

    @Override
    public void addTextChangedListener(TextWatcher watcher)
    {       
        if (mListeners == null) 
        {
            mListeners = new ArrayList<TextWatcher>();
        }
        mListeners.add(watcher);

        super.addTextChangedListener(watcher);
    }

    @Override
    public void removeTextChangedListener(TextWatcher watcher)
    {       
        if (mListeners != null) 
        {
            int i = mListeners.indexOf(watcher);
            if (i >= 0) 
            {
                mListeners.remove(i);
            }
        }

        super.removeTextChangedListener(watcher);
    }

    public void clearTextChangedListeners()
    {
        if(mListeners != null)
        {
            for(TextWatcher watcher : mListeners)
            {
                super.removeTextChangedListener(watcher);
            }

            mListeners.clear();
            mListeners = null;
        }
    }
}

以下是如何在xml布局中使用ExtendedEditText的示例:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" 
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

    <ua.inazaruk.HelloWorld.ExtendedEditText 
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" 
        android:text="header"
        android:gravity="center" /> 

</LinearLayout>

/扶额 - 真希望我能想到那个。不错! - the_prole

30

您可以从EditText中删除TextWatcher。首先,我建议您将TextWatcher声明移动到editText.addTextChangedListener(...)之外:

protected TextWatcher yourTextWatcher = new TextWatcher() {

    @Override
    public void afterTextChanged(Editable s) {
        // your logic here
    }

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

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
       // your logic here
    }
};

之后,您将能够更轻松地设置TextWather:

editText.addTextChangedListener(yourTextWatcher);

您可以像下面这样移除TextWatcher:

editText.removeTextChangedListener(yourTextWatcher);

如果需要的话,可以设置另外一个。


19
那意味着你需要知道应用于你的EditText的确切textWatcher名称。我相信问题是如何删除EditText上的所有textWatcher。 - IgorGanapolsky
3
伊戈尔·加纳波尔斯基,你说得对。但在许多其他情况下,它是非常有用的东西。 - Leo DroidCoder
2
@IgorGanapolsky 正确。但是我想不出需要在单个EditText上使用多个文本观察器的情况.....一个文本观察器用于多个EditText是可以理解的,但为什么在单个EditText上需要多个呢? - varun

12

我也花了很多时间寻找解决方案,最终通过以下标签的帮助解决了问题。它可以通过从convertView的标签中获取引用来移除先前的TextWatcher实例。 它完美地解决了这个问题。在CustomAdapter文件中,设置一个新的内部类,如下所示:

private static class ViewHolder {

        private TextChangedListener textChangedListener;
        private EditText productQuantity;

        public EditText getProductQuantity() {
            return productQuantity;
        }    

        public TextChangedListener getTextChangedListener() {
            return textChangedListener;
        }

        public void setTextChangedListener(TextChangedListener textChangedListener) {
            this.textChangedListener = textChangedListener;
        }
    }

然后在您重写的public View getView(int position, View convertView, ViewGroup parent)方法中实现以下逻辑:

@Override
public View getView(int position, View convertView, ViewGroup parent) {

     EditText productQuantity;
    TextChangedListener textChangedListener;

    if(convertView==null) {
        LayoutInflater mInflater = (LayoutInflater)
                context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
        convertView = mInflater.inflate(R.layout.cart_offer_item, parent, false);

        productQuantity=(EditText)convertView.findViewById(R.id.productQuantity);
        addTextChangedListener(viewHolder, position);
        convertView.setTag(viewHolder);
    }
    else
    {
        ViewHolder viewHolder=(ViewHolder)convertView.getTag();
        productQuantity=viewHolder.getProductQuantity();
        removeTextChangedListener(viewHolder);
        addTextChangedListener(viewHolder, position);
    }

    return convertView;
}



private void removeTextChangedListener(ViewHolder viewHolder)
{
    TextChangedListener textChangedListener=viewHolder.getTextChangedListener();
    EditText productQuantity=viewHolder.getProductQuantity();
    productQuantity.removeTextChangedListener(textChangedListener);
}

private void addTextChangedListener(ViewHolder viewHolder, int position)
{
    TextChangedListener textChangedListener=new TextChangedListener(position);
    EditText productQuantity=viewHolder.getProductQuantity();
    productQuantity.addTextChangedListener(textChangedListener);
    viewHolder.setTextChangedListener(textChangedListener);
}

然后按照以下方式实现TextWatcher类:

private class TextChangedListener implements TextWatcher
{
    private int position;
    TextChangedListener(int position)
    {
        this.position=position;
    }
    @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) {
    Log.d("check", "text changed in EditText");

    }
}
它将通过从convertView的标签中获取引用来移除先前的TextWatcher实例。

8

虽然这个问题很久以前就被问过了,但是有人可能会发现这很有用。在Recyclerview中使用TextWatcher的问题在于,我们必须确保在视图被回收之前将其移除。否则,我们会丢失TextWatcher的实例,并且在OnBindViewHolder()中调用removeTextChangedListener(textWatcher)仅会删除当前TextWatcher的实例。

我解决这个问题的方法是在FocusChangedListener中添加TextChangedListener:

editText.setOnFocusChangeListener(new OnFocusChangeListener() {          
public void onFocusChange(View v, boolean hasFocus) {
    if(hasFocus) {
        editText.addTextChangedListener(textWatcher)
    }
    else{
        editText.removeTextChangedListener(textWatcher)
    }
  }
});

这样,我确信当editText没有焦点时,textwatcher会被移除,而当它获得焦点时再次添加。因此,当recyclerview被回收时,editText将不会有任何文本更改监听器。


救了我的命。谢谢! - Rajneesh Shukla

6

我也在RecyclerView中遇到了许多EditText的类似问题。我用反射解决了这个问题。在绑定视图之前,调用ReflectionTextWatcher.removeAll(your_edittext)。这段代码会查找所有的TextWatcher,并将它们从名为“mListeners”的本地EditText列表中删除。

public class ReflectionTextWatcher {
    public static void removeAll(EditText editText) {
        try {
            Field field = findField("mListeners", editText.getClass());
            if (field != null) {
                field.setAccessible(true);
                ArrayList<TextWatcher> list = (ArrayList<TextWatcher>) field.get(editText); //IllegalAccessException
                if (list != null) {
                    list.clear();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private static Field findField(String name, Class<?> type) {
        for (Field declaredField : type.getDeclaredFields()) {
            if (declaredField.getName().equals(name)) {
                return declaredField;
            }
        }
        if (type.getSuperclass() != null) {
            return findField(name, type.getSuperclass());
        }
        return null;
    }
}

我希望这能帮到某人。

非常感谢!它运作得很好。对我非常有帮助。谢谢! - jujuf1
它对我有效! - Zhming

5

将当前的TextWatcher保存在ViewHolder中,这样你就可以找到想要移除的那个。


3
如果在viewholder中保存当前的文本监听器,下次重用viewholder时,它将拥有另一个视图和一个旧的文本监听器。因此,无法从旧的视图(现在属于其他viewholder)中删除该旧的文本监听器。 - Kirill Starostin

2
如果像我一样处理 ViewHolder,则仅在其创建时保存文本监视器的引用将无济于事。在重复使用时,视图将到达其他 ViewHolder,它不会具有对旧文本监视器的引用,因此无法删除它。个人而言,我选择像 @inazaruk 一样解决问题,但更新了 Kotlin 代码并重新命名了类以更好地反映其目的。
class EditTextWithRemovableTextWatchers(context: Context?, attrs: AttributeSet?) : TextInputEditText(context, attrs) {

    private val listeners by lazy { mutableListOf<TextWatcher>() }

    override fun addTextChangedListener(watcher: TextWatcher) {
        listeners.add(watcher)
        super.addTextChangedListener(watcher)
    }

    override fun removeTextChangedListener(watcher: TextWatcher) {
        listeners.remove(watcher)
        super.removeTextChangedListener(watcher)
    }

    fun clearTextChangedListeners() {
        for (watcher in listeners) super.removeTextChangedListener(watcher)
        listeners.clear()
    }
}

1
会导致崩溃。适用于“EditText”,但不适用于“AppCompatEditText”。尝试在空对象引用上调用接口方法'java.lang.Object kotlin.Lazy.getValue()' E / AndroidRuntime:在androidx.emoji2.viewsintegration.EmojiEditTextHelper $ HelperInternal19中。 <init>(EmojiEditTextHelper.java:267) 在androidx.emoji2.viewsintegration.EmojiEditTextHelper中的 <init>(EmojiEditTextHelper.java:109) - Boldijar Paul

1

正如您在这里看到的:TextView的CodeSearch,没有办法删除所有的监听器。唯一的方法是提供您用于注册它的观察者。

我还不完全理解为什么已经注册了其他侦听器。但是,您可以子类化EditText,覆盖addTextChangedListener(..),并在其中自己保留所有添加的引用的副本,然后委托给超类实现。然后,您还可以提供另一种方法来删除所有侦听器。

如果您需要进一步的解释,请联系我们。


如果它在匿名内部类中,你如何提供已注册的观察者? - IgorGanapolsky

1
我解决了这个问题,而不需要扩展TextView类。
private ArrayList<TextWatcher> mEditTextWatcherList = new ArrayList<>();
private TextWatcher mTextWatcher1;
private TextWathcer mTextWatcher2;

mTextWathcer1 = 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) {}
};

mTextWathcer2 = 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) {}
};

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

    setListener(mTextWatcher1);
    setListener(mTextWatcher2);

    removeListeners();
}

private setListener(TextWatcher listener) {
    mEditText.addTextChangedListener(listener);
    mEditTextWatcherList.add(listener);
}

private removeListeners() {
    for (TextWatcher t : mEditTextWatcherList)
        mEditText.removeTextChangedListener(t);

    mEditTextWatcherList.clear();
}

1

我曾经遇到过类似的问题。我通过在一个 ArrayList 中保存我的 textWatcher 的引用来解决它:

private final List<TextWatcher> textWatchersForProfileNameTextBox = new ArrayList<>();

public void addTextWatcherToProfileNameTextBox(TextWatcher textWatcher){
    textWatchersForProfileNameTextBox.add(textWatcher);
    getProfileNameTextView().addTextChangedListener(textWatcher);
}

public void removeAllTextWatchersFromProfileNameTextView(){
    while (!textWatchersForProfileNameTextBox.isEmpty())
        getProfileNameTextView().removeTextChangedListener(textWatchersForProfileNameTextBox.remove(0));
}

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