Свой ArrayAdapter элементом - EditText на Java для Android при добавлении элемента очищаются все

Нужны добавляемые текстовые поля в ListView. В классе активити вызываю adapter.addItem() - что добавляет текстовое поле в ListView, но при этом, во всех остальных добавленных полях стирается содержимое. Хотя, в this.mData данные остаются (смотрел в дебаггера Android Studio). При добавлении поля - фокус со всех полей сбрасывается. Как я понял, во все поля вставляется текст из полученного нового элемента, а он пустой. Если добавлять какую-нибудь строку, то она вставляется во все поля. Помогите разобраться, почему так происходит? Спасибо! Вот код класса адаптера:

package com.domain.adapter;

import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;

import java.util.ArrayList;

public class StringLineAdapter extends ArrayAdapter<String> {
    private final Context mContext;
    private ArrayList<String> mData;

    public StringLineAdapter(Context context, ArrayList<String> data) {
        super(context, R.layout.list_item_text_line);
        this.mContext = context;
        this.mData = data;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;
        EditTextHolder holder;

        if (row == null) {
            LayoutInflater inflater = LayoutInflater.from(mContext);
            row = inflater.inflate(R.layout.list_item_text_line, parent, false);
            holder = new EditTextHolder();
            holder.editText = row.findViewById(R.id.comment_text);
            row.setTag(holder);
        } else {
            holder = (EditTextHolder) row.getTag();
        }

        String content = mData.get(position);
        holder.editText.setText(content);

        holder.editText.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) {
                mData.set(position, s.toString());
            }
        });

        return row;
    }

    @Override
    public int getCount() {
        return mData.size();
    }

    public ArrayList<String> getData() {
        return mData;
    }

    public void addItem() {
        this.mData.add("");
        notifyDataSetChanged();
    }

    public void setData(ArrayList<String> data) {
        this.mData = data;
        notifyDataSetChanged();
    }

    static class EditTextHolder {
        EditText editText;
    }
}

ListView в активити:

<ListView
    android:id="@+id/things_list"
    android:layout_width="match_parent"
    android:layout_height="250dp"
    android:layout_weight="1"
    android:divider="@color/material_blue_grey_800"
    android:footerDividersEnabled="false"
    android:headerDividersEnabled="false"
    android:layout_marginBottom="10dp"
    android:background="@color/transparent"
    android:paddingTop="1dp"
    android:paddingBottom="1dp"
    android:stackFromBottom="true"
    android:transcriptMode="normal" />

Элемент списка:

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

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:weightSum="10"
        android:orientation="vertical">

        <EditText
            android:id="@+id/comment_text"
            android:layout_width="match_parent"
            android:layout_weight="1"
            android:layout_height="50dp"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:inputType="text"
            android:autofillHints=""
            tools:ignore="LabelFor,NestedWeights" />
    </LinearLayout>

</LinearLayout>

Ответы (1 шт):

Автор решения: woesss

addTextChangedListener(), как видно из названия, добавляет слушатель (не удаляя ранее добавленные) - в вашем случае при каждом показе/изменении элемента. Те есть при прокрутке списка и обновлении данных из вне - слушатели плодятся как тараканы. Но вьюхи ещё и переиспользуются - параметром convertView подаётся вью, которая уже использовалась для позиции, которая сейчас не видна, и слушатели, висящие на ней будут изменять данные и в новой позиции, и во всех позициях, которые она когда-то отображала, так как анонимный слушатель захватывает значение позиции на момент его создания.

Как вариант: установить слушатель однажды при создании вью, а при показе обновлять в нём позицию. Допустим, делегируем реализацию холдеру, чтобы не создавать дополнительных классов:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;
        EditTextHolder holder;

        if (row == null) {
            LayoutInflater inflater = LayoutInflater.from(mContext);
            row = inflater.inflate(R.layout.list_item_text_line, parent, false);
            holder = new EditTextHolder();
            holder.editText = row.findViewById(R.id.comment_text);
            // устанавливаем слушатель только когда вью создана
            holder.editText.addTextChangedListener(holder);
            row.setTag(holder);
        } else {
            holder = (EditTextHolder) row.getTag();
        }

        String content = mData.get(position);
        holder.editText.setText(content);
        // обновляем позицию при каждом показе
        holder.position = position;

        return row;
    }

    class EditTextHolder implements TextWatcher {
        EditText editText;
        int position;

        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) {
            mData.set(position, s.toString());
        }
    }
→ Ссылка