简体   繁体   English

在 RecyclerView 中保存 EditText 内容

[英]Saving EditText content in RecyclerView

I have list item with EditText in it, I don't know how many items there will be.我有一个带有EditText的列表项,我不知道会有多少项。 I have a problem when I enter some text in EditText , and then scroll down a RecyclerView , after I've scroll up again there is no text in my first EditText .当我在EditText中输入一些文本,然后向下滚动RecyclerView时出现问题,在我再次向上滚动后,我的第一个EditText中没有文本。

I am wondering what, and where should I write code so that while the user is typing or finished typing (I was thinking to do that with a TextWatcher ) in the EditText the text gets saved into into a file (I'll save it in a.txt file in the external storage)我想知道我应该写什么,在哪里写代码,以便当用户在EditText中输入或完成输入(我想用TextWatcher做到这一点)时,文本被保存到一个文件中(我将它保存在a.txt文件在外部存储中)

Am I supposed to do so in the onCreate method of the activity or in the adapter class or elsewhere?我应该在活动的onCreate方法或适配器 class 或其他地方这样做吗?

Here is some code这是一些代码

Main Activity code主要活动代码

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

My adapter code我的适配器代码

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

without this "addtextChangedListener" my code works fine obviusly not saving the content of the edit Text when scrolled.没有这个“addtextChangedListener”,我的代码工作正常,显然在滚动时不保存编辑文本的内容。 If i add this code, when i scroll all editText views that go off screen and than come back in have messed up content.如果我添加此代码,当我将 go 的所有 editText 视图滚动到屏幕外,然后再返回时,内容就会变得一团糟。

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.您的解决方案的主要问题是在 onBindViewHolder 中分配和分配 TextWatcher,这是一项昂贵的操作,会在快速滚动期间引入延迟,并且似乎还会干扰确定在 mAdapter 中更新的位置。

Making all operations in onCreateViewHolder is a more preferable option.在 onCreateViewHolder 中进行所有操作是更可取的选择。 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
        }
    }
}

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.希望这可以解决您这边的问题。

I implemented @dkarmazi solution, but it didn't help me.我实施了@dkarmazi 解决方案,但对我没有帮助。 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.主要问题是应用的 TextWatcher 在项目回收期间继续工作。

I've tried to disable it before recycling, but there's no any "beforeRecycle" event methods.我试图在回收之前禁用它,但没有任何“beforeRecycle”事件方法。 So I used onViewDetachedFromWindow method, and it has worked well.所以我使用了onViewDetachedFromWindow方法,效果很好。

Create a String array with the size of your adapter data.创建一个具有适配器数据大小的字符串数组。

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

on the onBindViewHolder method inside your adapter , add a TextChangedListener to the Textview.在适配器内的 onBindViewHolder 方法上,将 TextChangedListener 添加到 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
            }
        });


}

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

}

According to me this is more optimize of @dkarmazi's answer据我说,这是对@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);
            }
        }

    }

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

        }
    });


}

Override onViewRecycled method in RecyclerView adapter like this:像这样覆盖 RecyclerView 适配器中的 onViewRecycled 方法:

@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.对于其中一些,侦听器没有调用,当在 onBindViewHolder 方法中调用侦听器时,似乎甚至在滚动侦听器事件时也被调用。 '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我覆盖了getItemViewType方法来为我解决

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

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.所以当 view 被回收时,一些 EditText 会有超过 1 的侦听器。

To fix this problem, should clear listeners when view recycled.要解决此问题,应在视图回收时清除侦听器。

Step 1第1步

use custom EditText replace.使用自定义 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;
        }
    }
}

step 2第2步

replace EditText with above in xml.用上面的 xml 替换 EditText。

    <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第 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/1227525https://stackoverflow.com/a/6270674/1227525

I'm not that familiar with RecyclerView objects, but I had the same issue with ListView.我对 RecyclerView 对象不太熟悉,但我对 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.对于那些,我通常会创建一个临时类来表示插入到我的视图中的值(它与 EditTexts、Checkboxes、RadioButtons 一起使用...)并通过它们获取更新的数据。 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.然后,我创建了一个由所述容器对象组成的自定义 ArrayAdapter,在每个 getView() 回调中检索要放入 edittexts 的值,并实现一个 textwatcher 来使这些对象保持最新状态。 Again, I don't exactly remember how RecyclerViews work, but if they involve Adapters, this could be a good hack for you to try.同样,我不太记得 RecyclerViews 是如何工作的,但是如果它们涉及适配器,这可能是您尝试的好方法。

implements View.OnFocusChangeListener实现 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());
}

I used the tag property to save past watcher and be able to remove it:我使用 tag 属性来保存过去的观察者并能够将其删除:

 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
}
            

Just override below method to solve the issue: 只需覆盖以下方法即可解决问题:

@Override
public int getItemViewType(final int position) {
    return position;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM