J'ai un ListView
qui affiche les nouvelles. Ils contiennent une image, un titre et du texte. L'image est chargée dans un thread séparé (avec une file d'attente et tout) et lorsque l'image est téléchargée, j'appelle maintenant notifyDataSetChanged()
sur l'adaptateur de liste pour mettre à jour l'image. Cela fonctionne, mais getView()
est appelé trop souvent, car notifyDataSetChanged()
appelle getView()
pour tous les éléments visibles. Je souhaite mettre à jour uniquement le seul élément de la liste. Comment je ferais ça?
Les problèmes que j'ai avec mon approche actuelle sont:
J'ai trouvé la réponse, grâce à vos informations Michelle. Vous pouvez en effet obtenir la bonne vue en utilisant View#getChildAt(int index)
. Le problème, c'est qu'il commence à compter à partir du premier élément visible. En fait, vous ne pouvez obtenir que les éléments visibles. Vous résolvez ceci avec ListView#getFirstVisiblePosition()
.
Exemple:
private void updateView(int index){
View v = yourListView.getChildAt(index -
yourListView.getFirstVisiblePosition());
if(v == null)
return;
TextView someText = (TextView) v.findViewById(R.id.sometextview);
someText.setText("Hi! I updated you manually!");
}
Cette question a été posée sur le Google I/O 2010, vous pouvez la voir ici:
Le monde de ListView, heure 52:
En gros, Romain Guy explique qu'il appelle getChildAt(int)
sur le ListView
pour obtenir la vue et (je pense) appeler getFirstVisiblePosition()
pour connaître la corrélation entre position et index.
Romain pointe également le projet intitulé Shelves à titre d'exemple, je pense qu'il pourrait vouloir dire la méthode ShelvesActivity.updateBookCovers()
, mais je ne trouve pas l'appel de getFirstVisiblePosition()
.
AWESOME UPDATES À VENIR:
Le RecyclerView va résoudre ce problème dans un proche avenir. Comme indiqué sur http://www.grokkingandroid.com/first-glance-androids-recyclerview/ , vous pourrez appeler des méthodes pour spécifier exactement le changement, telles que:
void notifyItemInserted(int position)
void notifyItemRemoved(int position)
void notifyItemChanged(int position)
De plus, tout le monde voudra utiliser les nouvelles vues basées sur RecyclerView, car ils seront récompensés par de jolies animations! L'avenir est génial! :-)
Voici comment je l'ai fait:
Vos éléments (lignes) doivent avoir des identifiants uniques pour que vous puissiez les mettre à jour ultérieurement. Définissez la balise de chaque vue lorsque la liste obtient la vue de l'adaptateur. (Vous pouvez également utiliser une étiquette si l'étiquette par défaut est utilisée ailleurs)
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
View view = super.getView(position, convertView, parent);
view.setTag(getItemId(position));
return view;
}
Pour la mise à jour, vérifiez chaque élément de la liste. Si une vue avec un identifiant donné est présente, elle est visible et nous effectuons la mise à jour.
private void update(long id)
{
int c = list.getChildCount();
for (int i = 0; i < c; i++)
{
View view = list.getChildAt(i);
if ((Long)view.getTag() == id)
{
// update view
}
}
}
C'est en fait plus facile que d'autres méthodes et mieux lorsque vous traitez avec des identifiants, pas des positions! De plus, vous devez appeler update pour les éléments visibles.
obtenir la classe de modèle d'abord en tant que global, comme cet objet de classe de modèle
SampleModel golbalmodel=new SchedulerModel();
et l'initialiser à global
obtenir la ligne actuelle de la vue par le modèle en l'initialisant au modèle global
SampleModel data = (SchedulerModel) sampleList.get(position);
golbalmodel=data;
définir la valeur modifiée sur la méthode d'objet de modèle global à définir et ajouter le notifyDataSetChanged pour moi
golbalmodel.setStartandenddate(changedate);
notifyDataSetChanged();
int wantedPosition = 25; // Whatever position you're looking for
int firstPosition = linearLayoutManager.findFirstVisibleItemPosition(); // This is the same as child #0
int wantedChild = wantedPosition - firstPosition;
if (wantedChild < 0 || wantedChild >= linearLayoutManager.getChildCount()) {
Log.w(TAG, "Unable to get view for desired position, because it's not being displayed on screen.");
return;
}
View wantedView = linearLayoutManager.getChildAt(wantedChild);
mlayoutOver =(LinearLayout)wantedView.findViewById(R.id.layout_over);
mlayoutPopup = (LinearLayout)wantedView.findViewById(R.id.layout_popup);
mlayoutOver.setVisibility(View.INVISIBLE);
mlayoutPopup.setVisibility(View.VISIBLE);
Pour RecycleView, veuillez utiliser ce code.
Les réponses sont claires et correctes, je vais ajouter une idée pour le cas CursorAdapter
ici.
Si vous sous-classez CursorAdapter
(ou ResourceCursorAdapter
ou SimpleCursorAdapter
), vous pouvez alors implémenter ViewBinder
ou remplacer bindView()
et newView()
méthodes, celles-ci ne reçoivent pas l'index des éléments de la liste actuelle sous forme d'arguments. Par conséquent, lorsque vous connaissez certaines données et que vous souhaitez mettre à jour les éléments de liste visibles pertinents, comment connaissez-vous leurs index?
Ma solution de contournement était de:
newView()
notifyDatasetChanged()
et de les rafraîchir toutesEn raison du recyclage de la vue, le nombre de références de vue que j’aurai besoin de stocker et d’itérer sera à peu près égal au nombre d’éléments de la liste visibles à l’écran.
J'ai utilisé le code qui a fourni Erik, fonctionne très bien, mais j'ai un adaptateur personnalisé complexe pour mon listview et j'ai été confronté à deux implémentations du code qui met à jour l'interface utilisateur. J'ai essayé d'obtenir la nouvelle vue à l'aide de la méthode getView de mon adaptateur (la liste de contrôle contenant les données de la liste a déjà été mise à jour/modifiée):
View cell = lvOptim.getChildAt(index - lvOptim.getFirstVisiblePosition());
if(cell!=null){
cell = adapter.getView(index, cell, lvOptim); //public View getView(final int position, View convertView, ViewGroup parent)
cell.startAnimation(animationLeftIn());
}
Cela fonctionne bien, mais je ne sais pas si c'est une bonne pratique. Je n'ai donc pas besoin d'implémenter le code qui met à jour l'élément de liste deux fois.
exactement j'ai utilisé cela
private void updateSetTopState(int index) {
View v = listview.getChildAt(index -
listview.getFirstVisiblePosition()+listview.getHeaderViewsCount());
if(v == null)
return;
TextView aa = (TextView) v.findViewById(R.id.aa);
aa.setVisibility(View.VISIBLE);
}
J'ai créé une autre solution, telle que RecyclyerView méthode void notifyItemChanged(int position)
, create CustomBaseAdapter classe comme ceci:
public abstract class CustomBaseAdapter implements ListAdapter, SpinnerAdapter {
private final CustomDataSetObservable mDataSetObservable = new CustomDataSetObservable();
public boolean hasStableIds() {
return false;
}
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
public void notifyItemChanged(int position) {
mDataSetObservable.notifyItemChanged(position);
}
public void notifyDataSetInvalidated() {
mDataSetObservable.notifyInvalidated();
}
public boolean areAllItemsEnabled() {
return true;
}
public boolean isEnabled(int position) {
return true;
}
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return getView(position, convertView, parent);
}
public int getItemViewType(int position) {
return 0;
}
public int getViewTypeCount() {
return 1;
}
public boolean isEmpty() {
return getCount() == 0;
} {
}
}
N'oubliez pas de créer une variable classe CustomDataSetObservable aussi pour mDataSetObservable
dans CustomAdapterClass, comme ceci:
public class CustomDataSetObservable extends Observable<DataSetObserver> {
public void notifyChanged() {
synchronized(mObservers) {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
public void notifyInvalidated() {
synchronized (mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onInvalidated();
}
}
}
public void notifyItemChanged(int position) {
synchronized(mObservers) {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
mObservers.get(position).onChanged();
}
}
}
on class CustomBaseAdapter Il existe une méthode notifyItemChanged(int position)
, et vous pouvez appeler cette méthode lorsque vous souhaitez mettre à jour une ligne où vous le souhaitez (à partir d'un clic de souris ou de l'endroit où vous souhaitez appeler cette méthode). Et voila !, votre rangée unique se mettra à jour instantanément ..
Ma solution: si elle est correcte *, mettez à jour les données et les éléments visibles sans re-dessiner toute la liste. Sinon notifyDataSetChanged.
Correct - oldData size == nouvelle taille de données et anciens ID de données et leur ordre == nouveaux ID de données et ordre
Comment:
/**
* A View can only be used (visible) once. This class creates a map from int (position) to view, where the mapping
* is one-to-one and on.
*
*/
private static class UniqueValueSparseArray extends SparseArray<View> {
private final HashMap<View,Integer> m_valueToKey = new HashMap<View,Integer>();
@Override
public void put(int key, View value) {
final Integer previousKey = m_valueToKey.put(value,key);
if(null != previousKey) {
remove(previousKey);//re-mapping
}
super.put(key, value);
}
}
@Override
public void setData(final List<? extends DBObject> data) {
// TODO Implement 'smarter' logic, for replacing just part of the data?
if (data == m_data) return;
List<? extends DBObject> oldData = m_data;
m_data = null == data ? Collections.EMPTY_LIST : data;
if (!updateExistingViews(oldData, data)) notifyDataSetChanged();
else if (DEBUG) Log.d(TAG, "Updated without notifyDataSetChanged");
}
/**
* See if we can update the data within existing layout, without re-drawing the list.
* @param oldData
* @param newData
* @return
*/
private boolean updateExistingViews(List<? extends DBObject> oldData, List<? extends DBObject> newData) {
/**
* Iterate over new data, compare to old. If IDs out of sync, stop and return false. Else - update visible
* items.
*/
final int oldDataSize = oldData.size();
if (oldDataSize != newData.size()) return false;
DBObject newObj;
int nVisibleViews = m_visibleViews.size();
if(nVisibleViews == 0) return false;
for (int position = 0; nVisibleViews > 0 && position < oldDataSize; position++) {
newObj = newData.get(position);
if (oldData.get(position).getId() != newObj.getId()) return false;
// iterate over visible objects and see if this ID is there.
final View view = m_visibleViews.get(position);
if (null != view) {
// this position has a visible view, let's update it!
bindView(position, view, false);
nVisibleViews--;
}
}
return true;
}
et bien sur:
@Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
final View result = createViewFromResource(position, convertView, parent);
m_visibleViews.put(position, result);
return result;
}
Ignore le dernier paramètre de bindView (je l'utilise pour déterminer si je dois ou non recycler des images bitmap pour ImageDrawable).
Comme mentionné ci-dessus, le nombre total de vues "visibles" correspond à peu près à la quantité qui convient à l'écran (en ignorant les changements d'orientation, etc.), donc pas de mémoire.