2012-10-31 13 views
9

Eine kleine Neuling Frage. Warum sollten wir die ViewHolder in getView() initialisieren? Warum können wir es nicht im Konstruktor initialisieren?ViewHolder - gute Praxis

+1

http://www.youtube.com/watch?v=wDBM6wVEO70 aktualisieren. Schau dir dieses Video an. Sollte deine Frage beantworten. – Raghunandan

Antwort

29

Sie werden mehrere ViewHolder Objekte in Existenz.

Ein ListView erstellt von Natur aus keine neuen View Instanzen für jede Zeile. Dies ist so, dass, wenn Sie eine ListView von einer Million Dinge haben, Sie Layout-Informationen für eine Million Dinge nicht speichern müssen. Was müssen Sie also speichern? Nur die Dinge, die auf dem Bildschirm sind. Sie können diese Ansichten dann immer wieder verwenden. Auf diese Weise kann Ihre ListView einer Million Objekte nur vielleicht 10 Kindansichten haben.

In Ihrem benutzerdefinierten Array-Adapter, werden Sie eine Funktion getView(), die etwa wie folgt aussieht genannt haben:

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

einen Moment Zeit und sehen Dies funktioniert, aber nehmen, wenn Sie die Ineffizienz hier erkennen. Denken Sie darüber nach, welcher der obigen Codes redundant genannt wird.

Das Problem ist, dass wir row.findViewById immer und immer wieder aufrufen, obwohl nach dem ersten Mal wir es nachschlagen, wird es nie ändern. Wenn Sie nur eine einfache TextView in Ihrer Liste haben, ist es wahrscheinlich nicht so schlimm, wenn Sie ein komplexes Layout haben oder mehrere Ansichten haben, für die Sie Daten festlegen möchten, könnten Sie ein wenig Zeit verlieren, Ihre Ansicht über und zu finden erneut.

Also, wie reparieren wir das? Nun, es würde Sinn machen, das TextView irgendwo zu speichern, nachdem wir es nachgeschlagen haben. Also stellen wir eine Klasse namens ViewHolder vor, die die Ansichten "hält". Also innerhalb des Adapters einführte eine innere Klasse wie folgt:

private static class ViewHolder { 
    TextView textView; 
} 

Diese Klasse ist privat, da es nur ein Caching-Mechanismus für den Adapter ist, und es ist statisch, so dass wir keinen Hinweis auf die Notwendigkeit Adapter, um es zu benutzen.

Dies speichert unsere Ansicht, so dass wir row.findViewById nicht mehrmals aufrufen müssen. Wo sollten wir es einstellen? Wenn wir die Ansicht zum ersten Mal aufblasen. Wo speichern wir es? Ansichten haben ein benutzerdefiniertes "Tag" -Feld, mit dem Meta-Informationen über die Ansicht gespeichert werden können - genau das, was wir wollen! Wenn wir dann schon diese Ansicht gesehen haben, haben wir nur den Tag zu sehen, anstatt jede der Ansichten innerhalb der Reihe aufzublicken ..

also die if-Anweisung innerhalb von getView() wird:

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

Nun müssen wir nur noch den Textwert von holder.textView aktualisieren, da es sich bereits um einen Verweis auf die recycelte Ansicht handelt! So wird unser endgültiger Adaptercode:

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

Und wir sind fertig!

Einige Dinge denken:

  1. Wie funktioniert diese Änderung, wenn Sie mehrere Ansichten in einer Zeile, die Sie ändern möchten haben? Als Herausforderung, ein Listview machen, wobei jede Reihe zwei TextView Objekte und ein ImageView
  2. Wenn Ihre Listview-Debugging, überprüfen ein paar Dinge, so dass Sie wirklich sehen, was los ist:
    1. Wie oft ViewHolder Konstruktor aufgerufen wird .
    2. Was ist der Wert von holder.textView.getText() ist, bevor Sie es am Ende des getView()
+0

thnx, ehrfürchtige Antwort – Gorets

+0

Kein Problem - lassen Sie mich wissen, wenn alles Sinn ergibt, verwirrte das ViewHolder-Ding mich definitiv eine Zeitlang, als ich zuerst darüber erfuhr. – mindvirus

+0

Wow. Ausgezeichnete Antwort. Als eine Herausforderung für dich; Kannst du es mit einigen Referenzen verbessern? =) – Qw4z1

2

Beim Scrollen der Liste jedes Mal, wenn Zeilenauffüllung und neue Zeilenansicht für jede Zeile erstellt werden, müssen wir den Ansichtshalter initialisieren. Genau wie ich zwei Textview in Reihe haben, dann

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

Nein, Jeder Viewholder hat unterschiedliche Werte. Da wir die verschiedenen Daten in der Reihe haben. –

Verwandte Themen