web-dev-qa-db-fra.com

Android ListView Refresh Single Row

Après avoir obtenu les données d'une seule ligne d'un ListView, je veux mettre à jour cette seule ligne.

Actuellement j'utilise notifyDataSetChanged(); mais cela fait réagir View très lentement. Y a-t-il d'autres solutions?

76
Thys

Une option consiste à manipuler directement le ListView. Vérifiez d'abord si l'index de la ligne mise à jour est compris entre getFirstVisiblePosition() et getLastVisiblePosition(), ces deux vous donnent les première et dernière positions de l'adaptateur qui sont visibles à l'écran. Ensuite, vous pouvez obtenir la ligne View avec getChildAt(int index) et la modifier.

30
Daniel Velkov

Comme Romain Guy expliqué il y a quelque temps pendant la session Google I/O , le moyen le plus efficace de ne mettre à jour qu'une seule vue dans une vue de liste est quelque chose comme ceci (celui-ci mettre à jour toutes les données de la vue):

ListView list = getListView();
int start = list.getFirstVisiblePosition();
for(int i=start, j=list.getLastVisiblePosition();i<=j;i++)
    if(target==list.getItemAtPosition(i)){
        View view = list.getChildAt(i-start);
        list.getAdapter().getView(i, view, list);
        break;
    }

En supposant que target est un élément de l'adaptateur.

Ce code récupère le ListView, puis parcourez les vues actuellement affichées, comparez l'élément target que vous recherchez avec chaque élément de vue affiché, et si votre cible est parmi celles-ci, obtenez la vue englobante et exécutez l'adaptateur getView sur cette vue pour actualiser l'affichage.

Comme note secondaire invalidate ne fonctionne pas comme certaines personnes s'y attendent et ne rafraîchira pas la vue comme getView, notifyDataSetChanged reconstruira toute la liste et finira par appeler getview pour tous les éléments affichés et invalidateViews affecteront également un groupe.

Une dernière chose, on peut également obtenir des performances supplémentaires s'il n'a besoin que de changer un enfant d'une vue de ligne et non la ligne entière comme le fait getView. Dans ce cas, le code suivant peut remplacer list.getAdapter().getView(i, view, list); (exemple pour modifier un texte TextView):

((TextView)view.findViewById(R.id.myid)).setText("some new text");

En code, nous avons confiance.

88
GriffonRL

Cette méthode plus simple fonctionne bien pour moi, et il vous suffit de connaître l'indice de position pour obtenir la vue:

// mListView is an instance variable

private void updateItemAtPosition(int position) {
    int visiblePosition = mListView.getFirstVisiblePosition();
    View view = mListView.getChildAt(position - visiblePosition);
    mListView.getAdapter().getView(position, view, mListView);
}
27

Le code suivant a fonctionné pour moi. Notez que lorsque vous appelez GetChild (), vous devez compenser par le premier élément de la liste car il est relatif à cela.

int iFirst = getFirstVisiblePosition();
int iLast = getLastVisiblePosition();

if ( indexToChange >= numberOfRowsInSection() ) {
    Log.i( "MyApp", "Invalid index. Row Count = " + numberOfRowsInSection() );
}
else {      
    if ( ( index >= iFirst ) && ( index <= iLast ) ) {
        // get the view at the position being updated - need to adjust index based on first in the list
        View vw = getChildAt( sysvar_index - iFirst );
        if ( null != vw ) {
            // get the text view for the view
            TextView tv = (TextView) vw.findViewById(com.Android.myapp.R.id.scrollingListRowTextView );
            if ( tv != null ) {
                // update the text, invalidation seems to be automatic
                tv.setText( "Item = " + myAppGetItem( index ) + ". Index = " + index + ". First = " + iFirst + ". Last = " + iLast );
            }
        }
    }
}  
6
SkolVikingsGuy

Il y a une autre chose beaucoup plus efficace que vous pouvez faire, si cela correspond à votre cas d'utilisation.

Si vous changez d'état et pouvez en quelque sorte appeler le bon (en connaissant la position) mListView.getAdapter().getView() ce sera le plus efficace de tous.

Je peux démontrer un moyen très simple de le faire, en créant une classe interne anonyme dans ma classe ListAdapter.getView(). Dans cet exemple, j'ai un TextView montrant un texte "nouveau" et cette vue est définie sur GONE lorsque l'élément de liste est cliqué:

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    // assign the view we are converting to a local variable
    View view = convertView;

    Object quotation = getItem(position);
    // first check to see if the view is null. if so, we have to inflate it.
    if (view == null)
        view = mInflater.inflate(R.layout.list_item_quotation, parent, false);

    final TextView newTextView = (TextView) view.findViewById(R.id.newTextView);

    view.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (mCallbacks != null)
                mCallbacks.onItemSelected(quotation.id);
            if (!quotation.isRead()) {
                servicesSingleton.setQuotationStatusReadRequest(quotation.id);
                quotation.setStatusRead();
                newTextView.setVisibility(View.GONE);
            }
        }
    });

    if(quotation.isRead())
        newTextView.setVisibility(View.GONE);
    else
        newTextView.setVisibility(View.VISIBLE);

    return view;
}

Le framework utilise automatiquement le position correct et vous devez vous soucier de le récupérer à nouveau avant d'appeler getView.

2
vedant