Speichern von EditText-Inhalten in RecyclerView


77

Ich habe einen Listeneintrag EditTextdarin, ich weiß nicht, wie viele Einträge es geben wird. Ich habe ein Problem, wenn ich Text EditTexteingebe und dann nach unten scrolle RecyclerView. Nachdem ich wieder nach oben gescrollt habe, befindet sich in meinem ersten Text kein Text EditText.

Ich frage mich , was und wo soll ich Code schreiben , so dass während der Benutzer die Eingabe oder Eingabe beendet ist (ich dachte , das zu tun, mit ein TextWatcher) in EditTextden Text in einer Datei gespeichert wird (ich es in eine sparen werde. txt-Datei im externen Speicher)

Soll ich das in der onCreateMethode der Aktivität oder in der Adapterklasse oder anderswo tun ?

Hier ist ein Code

Hauptaktivitätscode

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

Mein Adaptercode

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

Ohne diesen "addtextChangedListener" funktioniert mein Code offensichtlich einwandfrei, ohne den Inhalt des Bearbeitungstextes beim Scrollen zu speichern. Wenn ich diesen Code hinzufüge, haben beim Scrollen alle editText-Ansichten, die vom Bildschirm verschwinden und dann wieder eingehen, den Inhalt durcheinander gebracht.


2
Bitte teilen Sie Ihren Code für Adapter, Layouts, Aktivitäten / Fragmente, die recyclerView hosten.
Dkarmazi

Verwenden Sie einen TextWatcher, um den eingegebenen Text in einer speicherinternen Darstellung Ihrer Liste zu speichern, und speichern Sie ihn dann in SharedPreferences in der Activity- oder Fragment onStop () -Methode.
BladeCoder

Sie können diesen editText in Ihrer Aktivität erhalten. Sie müssen nur die Ansicht Ihres editText dieser Position finden. Wenn Sie diese Art von Code möchten, kann ich Ihnen helfen.
Harvi Sirja

poste deinen onBindViewHolder
Apurva

@dkarmazi, ich habe den Code hinzugefügt
Gatteo

Antworten:


168

Das Hauptproblem bei Ihrer Lösung besteht darin, TextWatcher in onBindViewHolder zuzuweisen und zuzuweisen. Dies ist eine teure Operation, die bei schnellen Bildläufen Verzögerungen verursacht und die Bestimmung der zu aktualisierenden Position in mAdapter zu beeinträchtigen scheint.

Das Ausführen aller Vorgänge in onCreateViewHolder ist eine vorzuziehende Option. Hier ist die vollständig getestete Arbeitslösung:

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
Ich habe zu danken! In Bezug auf Ihre Frage ist das Speichern von Daten in einem externen Speicher (ob beim Schreiben auf die Festplatte oder über das Netzwerk) ein sehr teurer Vorgang und muss über den UI-Thread ausgeführt werden. Daher müssen Sie dies asynchron tun und die Anzahl dieser Anrufe auf ein Minimum reduzieren. Da Sie über 20 EditTextViews verfügen, ist es noch teurer, für jede einen eigenen Assync-Aufruf zu erstellen. Was Sie tun können, ist lokal in String [] zu speichern, so wie Sie es jetzt tun. Wenn Sie diesen Bildschirm verlassen, synchronisieren Sie den lokalen String [] mit Ihrem externen Speicher. Wenn Sie zu diesem Bildschirm zurückkehren, ziehen Sie String [] aus dem externen Speicher
dkarmazi

4
Varun, in diesem Fall müssten Sie dem Layout einen zweiten editText hinzufügen, ihn dem viewHolder hinzufügen und einen zweiten MyCustomEditTextListener erstellen, um Änderungen im zweiten editText-Objekt abzuhören. Am wichtigsten ist, dass Sie eine neue Datenstruktur erstellen müssen, die mit mDataset identisch ist, um die Werte für die zweiten editTexts zu speichern.
Dkarmazi

1
@ quangson91 Wir müssen notifyItemChanged nicht aufrufen, da es sich um eine EditText-Ansicht handelt, die derzeit auf dem Bildschirm angezeigt wird. In dieser Ansicht wird angezeigt, was der letzte Benutzer eingegeben hat, während die Ansicht sichtbar ist. Jetzt ist mDataset praktisch, wenn die Ansicht den Bildschirm verlässt und dann wieder angezeigt wird. Wenn wir so etwas wie eine normale Textansicht hatten, wäre notifyItemChanged erforderlich, um den aktuell angezeigten Wert der Ansicht zu aktualisieren.
Dkarmazi

3
UPDATE: sollte Holder.getAdapterPosition () anstelle der Position in verwenden: Holder.myCustomEditTextListener.updatePosition ( Position );
Ninja Coding

2
Diese Lösung hat einen Fehler. Man sollte niemals Positionsinformationen speichern, die an übergeben wurden, onBindViewHolderda sich die Position eines Elements ändern kann, ohne dass ein weiterer onBindViewHolderAnruf getätigt wird. Stattdessen MyCustomEditTextListenersollte ein Verweis auf a ViewHolderund call sein ViewHolder.getAdapterPosition(), um die richtige Position zu erhalten.
Andrey Makarov

13

Ich hatte das gleiche Problem, ich fügte die folgende Zeile hinzu und es scheint das Problem auf meiner Seite behoben zu haben.

mRecyclerview.setItemViewCacheSize(mDataset.size());

Hoffentlich löst dies das Problem auf Ihrer Seite.


26
Dies funktioniert, macht jedoch den gesamten Zweck des "Recyclings von Ansichten" in einer RecyclerView zunichte.
SMBiggs

8
Perfekt für kleine Datenmengen
Mohsin

Wirklich hilfreich!! Nach so viel Kampf endlich mit dieser Antwort fertig. (y)
Stuti Kasliwal

4

Erstellen Sie ein String-Array mit der Größe Ihrer Adapterdaten.

Z.B: String[] texts = new String[dataSize];

Fügen Sie in der onBindViewHolder-Methode in Ihrem Adapter der Textansicht einen TextChangedListener hinzu.

Z.B : -

@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
Hallo @JITHINRAJ ohne diesen "addtextChangedListener", mein Code funktioniert einwandfrei, ohne den Inhalt des Bearbeitungstextes beim Scrollen zu speichern. Wenn ich diesen Code hinzufüge, haben beim Scrollen alle editText-Ansichten, die vom Bildschirm verschwinden und dann wieder eingehen, den Inhalt durcheinander gebracht.
Gatteo

4

Ich habe die @ dkarmazi-Lösung implementiert, aber sie hat mir nicht geholfen. Also bin ich weiter gekommen und es gibt wirklich funktionierende Lösungen.

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

Das Hauptproblem bestand darin, dass der angewendete TextWatcher während des Artikelrecyclings weiter funktionierte.

Ich habe versucht, es vor dem Recycling zu deaktivieren, aber es gibt keine "beforeRecycle" -Ereignismethoden. Also habe ich die onViewDetachedFromWindowMethode verwendet und sie hat gut funktioniert.

Es gibt nur ein Problem, nachdem die Liste zu EditText Custom Editable.Factory hinzugefügt wurde. Ich weiß nicht warum, vielleicht finde ich auch die Lösung für dieses Problem


Beste Antwort bisher, dies sollte die akzeptierte Antwort sein. Die meisten Antworten haben Leistungsmängel, außer diesen
Aklesh Singh

3

Ich würde eine Schnittstelle erstellen und die aktuelle Adapterposition übergeben, um das Textänderungsereignis zu behandeln

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

}

schönste und sauberste aller Antworten. Das ist sicherlich ein
langer

2

Hallo @mikwee, stellen Sie sicher, dass Sie den Listener für Textänderungen in der folgenden Methode hinzufügen, anstatt ihn zu onBindViewHolder () hinzuzufügen.

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

Überschreiben Sie die onViewRecycled-Methode im RecyclerView-Adapter wie folgt:

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

Die Verwendung holder.getAdapterPosition()im TextWatcher ist die beste Lösung - dann müssen Sie die Position nicht im Auge behalten, da der Recycler dies für Sie erledigt. Stellen Sie einfach sicher, dass nur ein TextWatcher im View Holder-Konstruktor hinzugefügt wird.
Zkon

@zkon Meine Lösung benötigt keinen TextWatcher. Es wird lediglich die onViewRecycled-Methode der RecyclerView.Adapter-Klasse überschrieben.
Pourqavam

@Pourqavam Das Problem dabei ist , dass Sie den Text bearbeiten und dann eine Taste speichern Taste (auf der AppBar) und onViewRecycled()wird nicht aufgerufen werden, so können Sie Pickup die Änderungen gewohnt. Mit dem werden TextWatcherSie immer über Änderungen informiert.
Zkon

1

Meiner Meinung nach ist dies eine Optimierung der Antwort von @ 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);
            }
        }

    }

1

Für mich haben die oben genannten Lösungen nicht funktioniert. Für einige von ihnen hat der Listener nicht aufgerufen, und als der Listener in der onBindViewHolder-Methode aufgerufen wurde, scheint es, dass selbst beim Scrollen die Listener-Ereignisse aufgerufen werden. 'Text' ändert sich, also habe ich Key Listener ausprobiert und es hat gut funktioniert. Während des Bildlaufs werden vermutlich keine Tasten gedrückt.

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

Der Code, der für mich funktioniert hat.


Danke, hat mir geholfen
Maq

0

Ich bin mit RecyclerView-Objekten nicht so vertraut, aber ich hatte das gleiche Problem mit ListView. Für diese erstelle ich normalerweise eine Ad-hoc-Klasse, die Werte darstellt, die in meine Ansichten eingefügt wurden (dies funktioniert mit EditTexts, Checkboxes, RadioButtons ...), und erhalte über diese aktualisierte Daten. Anschließend erstelle ich einen benutzerdefinierten ArrayAdapter, der aus den Containerobjekten besteht, rufe Werte ab, die bei jedem Rückruf von getView () in die edittexts eingefügt werden sollen, und implementiere einen Textwatcher, um diese Objekte auf dem neuesten Stand zu halten. Auch hier erinnere ich mich nicht genau, wie RecyclerViews funktionieren, aber wenn es sich um Adapter handelt, könnte dies ein guter Hack für Sie sein, um es zu versuchen.


0

Ich habe die getItemViewTypefür mich gelöste Methode überschrieben

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

1
Leider würde dies die Anzahl der Ansichtsinhaber pro Element erhöhen und sie werden niemals wiederverwendet. Tatsächlich entspricht die Leistung genau dem Hinzufügen neuer Elemente im InnerenLinearLayout
ruX


0

implementiert 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());
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.