2012-10-31 1 views
9

Una piccola domanda da principianti. Perché dovremmo inizializzare lo ViewHolder in getView()? Perché non possiamo inizializzarlo nel costruttore?ViewHolder - buona pratica

+1

http://www.youtube.com/watch?v=wDBM6wVEO70. Dai un'occhiata a questo video. Dovrebbe rispondere alla tua domanda. – Raghunandan

risposta

29

Avrete più ViewHolder oggetti esistenti.

A ListView A sua natura non crea nuove istanze View per ciascuna delle sue righe. Questo è così che se hai un ListView di un milione di cose, non è necessario memorizzare le informazioni di layout per un milione di cose. Allora, cosa hai bisogno di memorizzare? Solo le cose che sono sullo schermo. È quindi possibile riutilizzare queste visualizzazioni più e più volte. In questo modo, il tuo ListView di un milione di oggetti può avere solo 10 visualizzazioni figlio.

Nella scheda di serie personalizzato, si avrà una funzione chiamata getView() che sembra qualcosa di simile:

public View getView(int position, View convertView, ViewGroup parent) { 
    //Here, position is the index in the list, the convertView is the view to be 
    //recycled (or created), and parent is the ListView itself. 

    //Grab the convertView as our row of the ListView 
    View row = convertView; 

    //If the row is null, it means that we aren't recycling anything - so we have 
    //to inflate the layout ourselves. 
    if(row == null) { 
      LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
      row = inflater.inflate(R.layout.list_item, parent, false); 
    } 

    //Now either row is the recycled view, or one that we've inflated. All that's left 
    //to do is set the data of the row. In this case, assume that the row is just a 
    //simple TextView 
    TextView textView = (TextView) row.findViewById(R.id.listItemTextView); 

    //Grab the item to be rendered. In this case, I'm just using a string, but 
    //you will use your underlying object type. 
    final String item = getItem(position); 

    textView.setText(item); 

    //and return the row 
    return row; 
} 

Questo funzionerà, ma prendere un attimo e vedere se è possibile individuare l'inefficienza qui. Pensa a quale dei suddetti codici sarà chiamato ridondante.

Il problema è che stiamo chiamando row.findViewById più e più volte, anche se dopo la prima volta che lo cerchiamo, non cambierà mai. Se nel tuo elenco hai solo un semplice TextView, probabilmente non è così male, se hai un layout complesso o hai più visualizzazioni per cui desideri impostare i dati, potresti perdere un po 'di tempo per trovare la tua vista e di nuovo

Quindi, come possiamo risolvere questo? Bene, sarebbe logico immagazzinare quel TextView da qualche parte dopo averlo cercato. Quindi introduciamo una classe chiamata ViewHolder, che "trattiene" le viste. Così all'interno della scheda, di introdurre una classe interna in questo modo:

private static class ViewHolder { 
    TextView textView; 
} 

Questa classe è privata, dal momento che è solo un meccanismo di caching per l'adattatore, ed è statico in modo che non abbiamo bisogno di un riferimento alla adattatore per usarlo.

Questo memorizzerà la nostra vista in modo che non dobbiamo chiamare più volte row.findViewById. Dove dovremmo impostarlo? Quando gonfiamo la vista per la prima volta. Dove lo immagazziniamo? Le viste hanno un campo "tag" personalizzato, che può essere utilizzato per memorizzare le meta-informazioni sulla vista - esattamente ciò che vogliamo! Quindi, se abbiamo già visto questo punto di vista, non ci resta che cercare il tag, invece di guardare in alto ciascuna delle opinioni in sede di fila ..

Quindi, l'istruzione if all'interno di getView() diventa:

//If the row is null, it means that we aren't recycling anything - so we have 
//to inflate the layout ourselves. 
ViewHolder holder = null; 
if(row == null) { 
    LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
    row = inflater.inflate(R.layout.list_item, parent, false); 
    //Now create the ViewHolder 
    holder = new ViewHolder(); 
    //and set its textView field to the proper value 
    holder.textView = (TextView) row.findViewById(R.id.listItemTextView); 
    //and store it as the 'tag' of our view 
    row.setTag(holder); 
} else { 
    //We've already seen this one before! 
    holder = (ViewHolder) row.getTag(); 
} 

Ora, dobbiamo solo aggiornare il valore del testo holder.textView, poiché è già un riferimento alla vista riciclata! Quindi il codice del nostro adattatore finale diventa:

public View getView(int position, View convertView, ViewGroup parent) { 
    //Here, position is the index in the list, the convertView is the view to be 
    //recycled (or created), and parent is the ListView itself. 

    //Grab the convertView as our row of the ListView 
    View row = convertView; 

    //If the row is null, it means that we aren't recycling anything - so we have 
    //to inflate the layout ourselves. 
    ViewHolder holder = null; 
    if(row == null) { 
     LayoutInflater inflater = (LayoutInflater)getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
     row = inflater.inflate(R.layout.list_item, parent, false); 
     //Now create the ViewHolder 
     holder = new ViewHolder(); 
     //and set its textView field to the proper value 
     holder.textView = (TextView) row.findViewById(R.id.listItemTextView); 
     //and store it as the 'tag' of our view 
     row.setTag(holder); 
    } else { 
     //We've already seen this one before! 
     holder = (ViewHolder) row.getTag(); 
    } 

    //Grab the item to be rendered. In this case, I'm just using a string, but 
    //you will use your underlying object type. 
    final String item = getItem(position); 

    //And update the ViewHolder for this View's text to the correct text. 
    holder.textView.setText(item); 

    //and return the row 
    return row; 
} 

E abbiamo finito!

Alcune cose a cui pensare:

  1. Come funziona questo cambiamento, se si dispone di più viste di fila che si desidera modificare? Come una sfida, fare un controllo ListView in cui ogni riga ha due TextView oggetti e un ImageView
  2. Quando il debug ListView, controllare alcune cose in modo che si può veramente vedere quello che sta succedendo:
    1. Quante volte il costruttore di ViewHolder si chiama .
    2. Quale il valore di holder.textView.getText() è prima di aggiornare alla fine della getView()
+0

thnx, splendida risposta – Gorets

+0

Nessun problema - fammi sapere se tutto ha un senso, la cosa di ViewHolder mi ha definitivamente confuso per un po 'quando ho saputo per la prima volta. – mindvirus

+0

Wow. Ottima risposta Come una sfida per te; puoi migliorarlo con alcuni riferimenti? =) – Qw4z1

2

Mentre si scorre l'elenco ogni volta che la riga viene popolata e la nuova vista di riga viene creata per ogni riga, è necessario inizializzare il titolare della vista. Proprio come ho due TextView in fila poi,

static class ViewHolder { 
     protected TextView title; 
     protected TextView type; 

    } 


    public View getView(int position, View convertView, ViewGroup parent) { 
      View view = null; 
      if (convertView == null) { 
       LayoutInflater inflator = context.getLayoutInflater(); 
       view = inflator.inflate(R.layout.feeds_rowview, null); 
       final ViewHolder viewHolder = new ViewHolder(); 
       view.setTag(viewHolder); 
       viewHolder.title = (TextView) view.findViewById(R.id.Title); 
       viewHolder.type = (TextView) view.findViewById(R.id.Type); 

      } else { 
       view = convertView; 
      } 

      ViewHolder holder = (ViewHolder) view.getTag(); 
      holder.title.setText(list.get(position).getTitle()); 
      holder.type.setText(list.get(position).getType()); 

      return view; 
    } 
+0

No, Ogni viewholder ha valori diversi. Come abbiamo i diversi dati in fila. –