Сохранение содержимого EditText в RecyclerView
У меня есть элемент списка с EditText
в нем я не знаю, сколько предметов будет. У меня проблема при вводе текста в EditText
, а затем прокрутите вниз RecyclerView
, после того, как я снова прокрутите вверх, в моем первом тексте нет EditText
,
Мне интересно, что и где я должен написать код, чтобы, пока пользователь печатал или закончил печатать (я думал сделать это с 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, которые уходят с экрана и затем возвращаются, испортили содержимое.
18 ответов
Основной проблемой вашего решения является выделение и назначение 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
}
}
}
У меня была та же проблема, я добавил следующую строку, и она, кажется, устранила проблему на моей стороне.
mRecyclerview.setItemViewCacheSize(mDataset.size());
Надеюсь, это решит проблему на вашей стороне.
Я реализовал решение @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
метод, и это сработало хорошо.
Есть только одна проблема, после добавления в EditText пользовательский Editable.Factory список снова тормозит. Я не знаю почему, может быть, я тоже найду решение этой проблемы
Я хотел бы создать интерфейс и передать текущую позицию адаптера для обработки события изменения текста
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);
}
});
}
}
}
Создайте массив 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
}
});
}
По моему мнению, это больше оптимизирует ответ @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);
}
}
}
Переопределите метод onViewRecycled в адаптере RecyclerView следующим образом:
@Override
public void onViewRecycled(@NonNull ViewHolder holder) {
mDataSet[holder.getAdapterPosition()] = holder.mEditText.getText().toString();
}
EditText#setText
копирует строку вEditable
который можно прочитать, вызвавgetText()
Прямо вперед. Таким образом, вы можете создать альтернативный редактируемый набор данных:
fun onBindViewHolder(…) {
holder.editText.setText(editables[position] ?: texts[position])
editables[position] = holder.editText.text
}
Сейчасeditables
всегда будет содержать актуальные данные.
Вот полный образец , который я написал.
Проблема в том, что вы «добавляете» слушателя, а не «устанавливаете» слушателя. Поэтому, когда представление повторно используется, у некоторых 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
замените EditText на приведенное выше в xml.
<com.xxxxxxxx.widget.RecyclerViewEditText
android:id="@+id/et_remark"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:background="@null"
android:gravity="end"
android:hint="input hint"
android:inputType="textPersonName" />
шаг 3
при просмотре переработанного, очистите всех слушателей в вашем адаптере.
@Override
public void onViewRecycled(@NonNull ViewHolder holder) {
holder.et_remark.clearTextChangedListeners();
super.onViewRecycled(holder);
}
Привет @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) {
}
});
}
Я переопределил метод getItemViewType
решить для меня
override fun getItemViewType(position: Int): Int {
return position
}
Для меня вышеуказанные решения не сработали. Для некоторых из них слушатель не вызывал, и когда слушатель был вызван в методе 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;
}
});
Код, который работал у меня.
Я думаю, что это самое простое решение. ТолькоsetOnFocusChangeListener
в ваш EditText и добавьте/удалитеTextWatcher
соответственно:
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
// Initialize the TextWatcher when the ViewHolder is bound.
// You need to make sure you don't add a TextWatcher to an EditText that already has one.
holder.editText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
EditText editText = (EditText) v;
if (hasFocus) {
// Add the TextWatcher when the EditText has focus
TextWatcher textWatcher = 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) {
// ...
}
};
editText.addTextChangedListener(textWatcher);
editText.setTag(textWatcher); // Store the TextWatcher as a tag so you can remove it later.
} else {
// Remove the TextWatcher when the EditText loses focus
TextWatcher textWatcher = (TextWatcher) editText.getTag();
if (textWatcher != null) {
editText.removeTextChangedListener(textWatcher);
}
}
}
});
}
Я использовал свойство тега, чтобы сохранить прошлый наблюдатель и иметь возможность удалить его:
editText.tag?.let {
editText.removeTextChangedListener(it as TextWatcher)
}
object:TextWatcherAdapter() {
override fun afterTextChanged(s: Editable) {
// do something with text
}
}.also {
editText.addTextChangedListener(it)
editText.tag = it
}
Реализует 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());
}
Я не очень знаком с объектами RecyclerView, но у меня была та же проблема с ListView. Для них я обычно создаю специальный класс, представляющий значения, вставленные в мои представления (он работает с EditTexts, Checkboxes, RadioButtons...), и через них получаю обновленные данные. Затем я создаю пользовательский ArrayAdapter, состоящий из указанных объектов-контейнеров, извлекая из них значения, которые они помещают в edittexts при каждом обратном вызове getView(), и реализую textwatcher, чтобы поддерживать эти объекты в актуальном состоянии. Опять же, я точно не помню, как работают RecyclerViews, но если они используют адаптеры, это может быть хорошим взломом для вас.
Используйте двустороннюю привязку при работе с привязкой данных
Просто переопределите метод ниже, чтобы решить проблему:
@Override
public int getItemViewType(final int position) {
return position;
}