在回收视图中保存 EditText 内容

我有列表项目与 EditText在它,我不知道有多少项目将有。 当我在 EditText中输入一些文本,然后向下滚动一个 RecyclerView,在我再次向上滚动之后,在我的第一个 EditText中没有文本时,我遇到了一个问题。

我想知道应该在哪里写代码,当用户在 EditText中输入或完成输入时(我想用 TextWatcher) ,文本将被保存到一个文件中(我将把它保存在。外部存储中的 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”,我的代码工作得很好,当滚动时显然不会保存编辑文本的内容。如果我添加这个代码,当我滚动所有的编辑文本视图,离开屏幕,然后返回有混乱的内容。

87302 次浏览

I'm not that familiar with RecyclerView objects, but I had the same issue with ListView. For those ones, I usually create an ad-hoc class representing values inserted into my views (it works with EditTexts, Checkboxes, RadioButtons...) and get updated data through them. I then create a custom ArrayAdapter consisting of said container objects, retrieving values to put into the edittexts at every getView() callback from them and implementing a textwatcher to keep these objects up to date. Again, I don't exactly remember how RecyclerViews work, but if they involve Adapters, this could be a good hack for you to try.

Create a String array with the size of your adapter data.

Eg: String[] texts = new String[dataSize];

on the onBindViewHolder method inside your adapter , add a TextChangedListener to the Textview.

Eg : -

@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
}
});




}

The major problem with your solution is allocating and assigning TextWatcher in onBindViewHolder which is an expensive operation that will introduce lags during fast scrolls and it also seems to interfere with determining what position to update in mAdapter.

Making all operations in onCreateViewHolder is a more preferable option. Here is the complete tested working solution:

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
}
}
}

Hi @mikwee make sure you are adding text changed listener in below method rather than adding it to 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) {


}
});




}

I would create an interface and pass the current adapter position to handle the text change event

    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);
}
});
}
}


}

I had the same problem, I added the following line and it seems to have fixed the problem on my side.

mRecyclerview.setItemViewCacheSize(mDataset.size());

Hopefully this sorts out the issue on your side.

According to me this is more optimize of @dkarmazi's answer

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);
}
}


}

I implemented @dkarmazi solution, but it didn't help me. So, I've come further and there's truly working solution.

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
}
}
}

The main problem was that applied TextWatcher continued to work during item recycling.

I've tried to disable it before recycling, but there's no any "beforeRecycle" event methods. So I used onViewDetachedFromWindow method, and it has worked well.

Override onViewRecycled method in RecyclerView adapter like this:

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

For me the above solutions didnt work. For some of them, the listener was not calling and when the listener was called in the onBindViewHolder method, it seems even when scrolling the listener events are called. 'Text' is changing, So i tried key listener and it worked fine. No keys are pressed during scrolling i guess.

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;
}
});

The code that worked for me.

I overrided the method getItemViewType to resolved for me

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

implements 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());
}

The problem is that you 【add】 a listener , not 【set】 a listener. So when view is recycled, some EditText will have listeners more than 1.

To fix this problem, should clear listeners when view recycled.

Step 1

use custom EditText replace.

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;
}
}
}

step 2

replace EditText with above in 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" />

step 3

when view recycled, clear all listeners in your adapter.

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

see https://stackoverflow.com/a/6270674/1227525

I used the tag property to save past watcher and be able to remove it:

 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
}