Збереження вмісту EditText у RecyclerView


82

У мене є елемент списку EditText, я не знаю, скільки буде елементів. У мене проблема, коли я ввожу текст EditText, а потім прокручую a RecyclerView, після того як я знову прокручую вгору, у моєму тексті немає тексту EditText.

Мені цікаво, що і де я повинен писати код, щоб, поки користувач набирає текст або закінчував вводити текст (я думав зробити це за допомогою a TextWatcher), EditTextтекст зберігався у файлі (я зберігаю його у файлі. txt-файл у зовнішній пам'яті)

Чи повинен я це робити в onCreateметоді діяльності чи в класі адаптера чи деінде?

Ось деякий код

Основний код діяльності

 public class MainActivity extends Activity {

    RecyclerView mRecyclerView;
    MyAdapter mAdapter;
    String[] mDataSet= new String[20];
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // generating text for editText Views
        for (int i = 0; i<=19; i++){
        mDataSet[i]= "EditText n: "+i;

    }
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        mAdapter = new MyAdapter(mDataSet);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());
        mRecyclerView.setAdapter(mAdapter);
        mRecyclerView.setHasFixedSize(true);
    }

Мій код адаптера

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

private String[] mDataset;


public static class ViewHolder extends RecyclerView.ViewHolder {
    // each data item is just a string in this case
    public EditText mEditText;

    public ViewHolder(View v) {
        super(v);

        mEditText = (EditText) v.findViewById(R.id.list_item_edittext);
    }
}

public MyAdapter(String[] myDataset) {
    mDataset = myDataset;
}

@Override
public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                 int viewType) {

    View v = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.list_item, parent, false);

    ViewHolder vh = new ViewHolder(v);
    return vh;
}

@Override
public void onBindViewHolder(ViewHolder holder,  final int position) {
    holder.mEditText.setText(mDataset[position]);

    //without this addtextChangedListener my code works fine ovbiusly
    // not saving the content of the edit Text when scrolled
    // If i add this code then when i scroll all textView that go of screen
    // and than come back in have messed up content
    holder.mEditText.addTextChangedListener(new TextWatcher() {

        @Override
        public void onTextChanged(CharSequence s, int start,
                                  int before, int count) {
           //setting data to array, when changed
           // this is a semplified example in the actual app i save the text
           // in  a .txt in the external storage
           mDataset[position] = s.toString();
        }

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

        }

        @Override
        public void afterTextChanged(Editable s) {

        }
    });

}

@Override
public int getItemCount() {
    return mDataset.length;
}

без цього "addtextChangedListener" мій код працює чудово, не зберігаючи вміст тексту редагування при прокрутці. Якщо я додаю цей код, коли я прокручую всі подання editText, які виходять з екрану і повертаються, переплутали вміст.


2
Будь ласка, поділіться своїм кодом адаптера, макетів, активності / фрагмента, на якому розміщено reciclerView
dkarmazi

Використовуйте TextWatcher, щоб зберегти введений текст у представленому в пам'яті списку, а потім збережіть його у SharedPreferences у методі Activity або Fragment onStop ().
BladeCoder

Ви можете отримати цей editText у своїй діяльності. Вам просто потрібно знайти вид вашого editText цієї позиції. Якщо ви хочете цей тип коду, то я можу вам допомогти.
Harvi Sirja

опублікуйте на onBindViewHolder
Апурва

@dkarmazi, я додав код
gatteo

Відповіді:


176

Основною проблемою вашого рішення є розподіл та призначення TextWatcher у onBindViewHolder, що є дорогою операцією, яка вводить лаги під час швидких прокруток, а також, здається, заважає визначити, яку позицію слід оновити в mAdapter.

Виконання всіх операцій у onCreateViewHolder є більш кращим варіантом. Ось повне перевірене робоче рішення:

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

    private String[] mDataset;

    public MyAdapter(String[] myDataset) {
        mDataset = myDataset;
    }

    @Override
    public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                   int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_edittext, parent, false);
        // pass MyCustomEditTextListener to viewholder in onCreateViewHolder
        // so that we don't have to do this expensive allocation in onBindViewHolder
        ViewHolder vh = new ViewHolder(v, new MyCustomEditTextListener());

        return vh;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {
        // update MyCustomEditTextListener every time we bind a new item
        // so that it knows what item in mDataset to update
        holder.myCustomEditTextListener.updatePosition(holder.getAdapterPosition());
        holder.mEditText.setText(mDataset[holder.getAdapterPosition()]);
    }

    @Override
    public int getItemCount() {
        return mDataset.length;
    }


    public static class ViewHolder extends RecyclerView.ViewHolder {
        // each data item is just a string in this case
        public EditText mEditText;
        public MyCustomEditTextListener myCustomEditTextListener;

        public ViewHolder(View v, MyCustomEditTextListener myCustomEditTextListener) {
            super(v);

            this.mEditText = (EditText) v.findViewById(R.id.editText);
            this.myCustomEditTextListener = myCustomEditTextListener;
            this.mEditText.addTextChangedListener(myCustomEditTextListener);
        }
    }

    // we make TextWatcher to be aware of the position it currently works with
    // this way, once a new item is attached in onBindViewHolder, it will
    // update current position MyCustomEditTextListener, reference to which is kept by ViewHolder
    private class MyCustomEditTextListener implements TextWatcher {
        private int position;

        public void updatePosition(int position) {
            this.position = position;
        }

        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
            // no op
        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
            mDataset[position] = charSequence.toString();
        }

        @Override
        public void afterTextChanged(Editable editable) {
            // no op
        }
    }
}

3
Вас найбільше вітають! Щодо вашого запитання, збереження даних на зовнішньому сховищі (будь то запис на диск або через мережу) є дуже дорогою операцією, і її потрібно виконувати поза потоком інтерфейсу користувача. Таким чином, це потрібно зробити асинхронно і зменшити кількість цих дзвінків до мінімуму. Оскільки у вас є 20 EditTextViews, здійснення окремого виклику асинхронізації для кожного ще дорожче. Що ви можете зробити, це зберегти локально в String [], як і зараз. Покинувши цей екран, синхронізуйте локальний рядок [] із зовнішнім сховищем. Повернувшись до цього екрана, витягніть String [] із зовнішнього сховища
dkarmazi

4
Варуне, в такому випадку вам доведеться додати другий editText до макета, додати його до viewHolder, створити другий MyCustomEditTextListener, щоб слухати зміни у другому об’єкті editText. Найголовніше, вам потрібно буде створити нову структуру даних, ідентичну mDataset, щоб запам'ятати значення для другого editTexts.
dkarmazi

1
@ quangson91, нам не потрібно викликати notifyItemChanged, оскільки це перегляд EditText, який ми зараз бачимо на екрані. У цьому поданні відображатиметься все, що було введено останнім користувачем, поки вигляд видно. Тепер mDataset стає зручним, коли вигляд виходить з екрана, а потім повертається. Якщо у нас було щось на зразок звичайного TextView, тоді потрібно буде зателефонувати notifyItemChanged для оновлення поточного відображуваного значення подання.
dkarmazi

3
ОНОВЛЕННЯ: слід використовувати holder.getAdapterPosition () замість позиції в: holder.myCustomEditTextListener.updatePosition ( position );
Кодування ніндзя,

3
Це рішення має помилку. Ніколи не слід зберігати інформацію про позицію, передану на, onBindViewHolderоскільки позиція предмета може змінитися без іншого onBindViewHolderдзвінка. Натомість MyCustomEditTextListenerслід мати посилання на a ViewHolderта дзвінок ViewHolder.getAdapterPosition()для отримання правильної позиції.
Андрій Макаров

16

У мене була та сама проблема, я додав наступний рядок, і, схоже, вирішив проблему на моєму боці.

mRecyclerview.setItemViewCacheSize(mDataset.size());

Сподіваємось, це вирішує проблему на вашому боці.


28
Це спрацює, але перешкоджає цілі "перегляду утилізації" у RecyclerView.
SMBiggs

8
ідеально підходить для невеликого набору даних
Mohsin

Дійсно корисно !! після стільки боротьби, нарешті, зробленої з цією відповіддю. (y)
Stuti Kasliwal

5

Я реалізував рішення @dkarmazi, але мені це не допомогло. Отже, я пішов далі, і є справді робоче рішення.

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

    private String[] mDataset;

    public MyAdapter(String[] myDataset) {
        mDataset = myDataset;
    }

    @Override
    public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                               int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_edittext, parent, false);
        // pass MyCustomEditTextListener to viewholder in onCreateViewHolder
        // so that we don't have to do this expensive allocation in onBindViewHolder
        ViewHolder vh = new ViewHolder(v, new MyCustomEditTextListener());

        return vh;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {
        // update MyCustomEditTextListener every time we bind a new item
        // so that it knows what item in mDataset to update
        holder.myCustomEditTextListener.updatePosition(holder.getAdapterPosition());
        holder.mEditText.setText(mDataset[holder.getAdapterPosition()]);
    }

    @Override
    public int getItemCount() {
        return mDataset.length;
    }

    @Override
    public void onViewAttachedToWindow(@NonNull RecyclerView.ViewHolder holder) {
        ((ViewHolder) holder).enableTextWatcher();
    }

    @Override
    public void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) {
        ((ViewHolder) holder).disableTextWatcher();
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        // each data item is just a string in this case
        public EditText mEditText;
        public MyCustomEditTextListener myCustomEditTextListener;

        public ViewHolder(View v, MyCustomEditTextListener myCustomEditTextListener) {
            super(v);

            this.mEditText = (EditText) v.findViewById(R.id.editText);
            this.myCustomEditTextListener = myCustomEditTextListener;
        }
        
        void enableTextWatcher() {
            mEditText.addTextChangedListener(myCustomEditTextListener);
        }

        void disableTextWatcher() {
            mEditText.removeTextChangedListener(myCustomEditTextListener);
        }
    }

    // we make TextWatcher to be aware of the position it currently works with
    // this way, once a new item is attached in onBindViewHolder, it will
    // update current position MyCustomEditTextListener, reference to which is kept by ViewHolder
    private class MyCustomEditTextListener implements TextWatcher {
        private int position;

        public void updatePosition(int position) {
            this.position = position;
        }

        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
            // no op
        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {
            mDataset[position] = charSequence.toString();
        }

        @Override
        public void afterTextChanged(Editable editable) {
            // no op
        }
    }
}

Основною проблемою було те, що застосований TextWatcher продовжував працювати під час переробки елементів.

Я намагався вимкнути його перед переробкою, але жодних методів подій "beforeRecycle" немає. Тож я використав onViewDetachedFromWindowметод, і він спрацював добре.


1
На сьогодні найкраща відповідь, це повинна бути прийнята відповідь. Найбільш прихильна відповідь має недоліки у виконанні, крім цього
Aklesh Singh

4

Створіть масив String із розміром даних адаптера.

Наприклад: String[] texts = new String[dataSize];

у методі onBindViewHolder всередині адаптера додайте TextChangedListener до Textview.

Наприклад: -

@Override
    public void onBindViewHolder(Viewholder holder, int position) {

//binding data from array 
   holder.yourEditText.setText(texts [position]);
   holder.yourEditText.addTextChangedListener(new TextWatcher() {

            @Override
            public void onTextChanged(CharSequence s, int start,
                    int before, int count) {
                //setting data to array, when changed
                texts [position] = s.toString();
            }

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

            @Override
            public void afterTextChanged(Editable s) {
                //blank
            }
        });


}

1
Привіт @JITHINRAJ, без цього "addtextChangedListener" мій код чудово працює, не зберігаючи вміст тексту редагування при прокрутці. Якщо я додаю цей код, коли я прокручую всі подання editText, які виходять з екрану і повертаються, переплутали вміст.
gatteo

3

Я б створив інтерфейс і передав поточну позицію адаптера для обробки події зміни тексту

    public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

    private String[] mDataset;

    public MyAdapter(String[] myDataset) {
        mDataset = myDataset;
    }

    @Override
    public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                   int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_edittext, parent, false);
        ViewHolder vh = new ViewHolder(v, new ViewHolder.ITextWatcher() {
            @Override
            public void beforeTextChanged(int position, CharSequence s, int start, int count, int after) {
                // do something
            }

            @Override
            public void onTextChanged(int position, CharSequence s, int start, int before, int count) {
                mDataset[position] = s.toString();
            }

            @Override
            public void afterTextChanged(int position, Editable s) {
                // do something
            }
        });

        return vh;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, final int position) {
        holder.mEditText.setText(mDataset[position]);
    }

    @Override
    public int getItemCount() {
        return mDataset.length;
    }


    public static class ViewHolder extends RecyclerView.ViewHolder {

        public EditText mEditText;
        private ITextWatcher mTextWatcher;

        public interface ITextWatcher {
            // you can add/remove methods as you please, maybe you dont need this much
            void beforeTextChanged(int position, CharSequence s, int start, int count, int after);

            void onTextChanged(int position, CharSequence s, int start, int before, int count);

            void afterTextChanged(int position, Editable s);
        }

        public ViewHolder(View v, ITextWatcher textWatcher) {
            super(v);

            this.mEditText = (EditText) v.findViewById(R.id.editText);

            this.mTextWatcher = textWatcher;

            this.mEditText.addTextChangedListener(new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                    mTextWatcher.beforeTextChanged(getAdapterPosition(), s, start, count, after);
                }

                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                    mTextWatcher.onTextChanged(getAdapterPosition(), s, start, before, count);
                }

                @Override
                public void afterTextChanged(Editable s) {
                    mTextWatcher.afterTextChanged(getAdapterPosition(), s);
                }
            });
        }
    }

}

найкрасивіші та найчистіші з усіх відповідей. це, безсумнівно, шлях
Nasz Njoka-старший

2

Привіт @mikwee, переконайтеся, що ви додаєте прослуховувач тексту, змінений нижче, ніж додаєте його до onBindViewHolder ().

public ViewHolder(View v) {
        super(v);

  yourEditText.addTextChangedListener(new TextWatcher() {
        @Override
        public void onTextChanged(CharSequence s, int start,
                int before, int count) {
            //setting data to array, when changed
            texts [position] = s.toString();
        }

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

        }

        @Override
        public void afterTextChanged(Editable s) {

        }
    });


}

2

На мою думку, це більше оптимізує відповідь @ dkarmazi

public class UploadPhotoAdapter extends RecyclerView.Adapter<UploadPhotoAdapter.MyViewHolder> {
        ArrayList<Feed> feeds;
        Activity activity;
        public UploadPhotoAdapter(Activity activity, ArrayList<Feed> feeds) {
            this.feeds = feeds;
            this.activity = activity;
        }

        @Override
        public UploadPhotoAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.upload_feeds_recycler, parent, false);
            return new UploadPhotoAdapter.MyViewHolder(itemView);
        }

        @Override
        public void onBindViewHolder(final UploadPhotoAdapter.MyViewHolder holder, int position) {
            Feed feed = feeds.get(holder.getAdapterPosition());
            holder.captionEditText.setText(feed.getDescription());
            holder.captionEditText.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) {
                    feeds.get(holder.getAdapterPosition()).setDescription(s.toString());
                }
                @Override
                public void afterTextChanged(Editable s) {}
            });
        }
        @Override
        public int getItemCount() {
            return feeds.size();
        }

        public class MyViewHolder extends RecyclerView.ViewHolder {
            EditText captionEditText;
            public MyViewHolder(View view) {
                super(view);
                captionEditText = (EditText) view.findViewById(R.id.captionEditText);
            }
        }

    }

2

Перевизначити метод onViewRecycled в адаптері RecyclerView таким чином:

@Override
public void onViewRecycled(@NonNull ViewHolder holder) {
    mDataSet[holder.getAdapterPosition()] = holder.mEditText.getText().toString();
}

Використання holder.getAdapterPosition()всередині TextWatcher є найкращим рішенням - тоді вам не потрібно відстежувати своє становище, оскільки переробник робить це за вас. Просто переконайтеся, що в конструктор власників перегляду додано лише один TextWatcher.
zkon

@zkon Моє рішення не потребує TextWatcher. Це просто замінює метод onViewRecycled класу RecyclerView.Adapter.
Pourqavam

@Pourqavam Проблема з цим полягає в тому, що ви можете редагувати текст, а потім натиснути кнопку збереження (на панелі додатків), і onViewRecycled()вони не будуть викликані, тому ви не збираєтеся вносити зміни. За допомогою TextWatcherви завжди будете повідомлені про зміни.
zkon

1

Для мене вищезазначені рішення не спрацювали. Для деяких з них слухач не дзвонив, і коли слухач був викликаний у методі onBindViewHolder, здається, навіть при прокручуванні викликаються події слухача. 'Текст' змінюється, тому я спробував ключовий слухач, і він працював нормально. Я думаю, під час прокрутки не натискаються клавіші.

holder.ticketNo.setOnKeyListener(new View.OnKeyListener() {
                @Override
                public boolean onKey(View v, int keyCode, KeyEvent event) {
                    results.get(position).TicketNo = holder.ticketNo.getText().toString();
                    return false;
                }
            });

Код, який працював у мене.


Дякую, допоміг
Мак

0

Я не настільки знайомий з об'єктами RecyclerView, але у мене була та ж проблема з ListView. Для них я зазвичай створюю спеціальний клас, що представляє значення, вставлені в мої подання (це працює з EditTexts, прапорцями, RadioButtons ...), і отримую через них оновлені дані. Потім я створюю власний ArrayAdapter, що складається із зазначених об’єктів-контейнерів, отримуючи значення для вставки в edittexts при кожному зворотному виклику getView () від них та реалізовуючи текстовий спостерігач, щоб тримати ці об’єкти в курсі. Знову ж таки, я точно не пам’ятаю, як працює RecyclerViews, але якщо вони залучають адаптери, це може бути гарним рубанням для вас.


0

Я замінив метод, getItemViewTypeякий вирішив для мене

override fun getItemViewType(position: Int): Int {
    return position
}

1
На жаль, це створило б кількість власників переглядів на елемент, і вони ніколи не будуть використані повторно. Насправді, продуктивність буде точно такою ж, як додавання нових елементів всерединуLinearLayout
ruX


0

реалізує View.OnFocusChangeListener

@Override
public void onBindViewHolder(Viewholder holder, int position) {
        editText.setText(model.getValue());
        editText.setOnFocusChangeListener(this);
        editText.setTag(position);
}

@Override
public void onFocusChange(View v, boolean hasFocus) {
    if (v.getTag() == null)
        return;
    int position = (int) v.getTag();
    if (!hasFocus && v instanceof EditText)
        mValues.get(position).setValue(((EditText) v).getText().toString());
}

0

Проблема полягає в тому, що ви 【додаєте】 слухача, а не 【встановлюєте】 слухача. Отже, коли перегляд перероблений, деякі EditText матимуть слухачів більше 1.

Щоб вирішити цю проблему, слід очистити слухачів при перегляді перегляду.

Крок 1

використовувати користувальницьку заміну EditText.

public class RecyclerViewEditText extends AppCompatEditText {

    private ArrayList<TextWatcher> mListeners = null;

    public RecyclerViewEditText(@NonNull Context context) {
        super(context);
    }

    public RecyclerViewEditText(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public RecyclerViewEditText(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void addTextChangedListener(TextWatcher watcher)
    {
        if (mListeners == null)
        {
            mListeners = new ArrayList<>();
        }
        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;
        }
    }
}

Крок 2

коли перегляд перероблений, очистіть усі прослуховувачі в адаптері.

@Override
public void onViewRecycled(@NonNull ViewHolder holder) {
    holder.et_remark.clearTextChangedListeners();
    super.onViewRecycled(holder);
}

див. https://stackoverflow.com/a/6270674/1227525

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.