2012-10-31 17 views
9

Małe pytanie dla początkujących. Dlaczego powinniśmy zainicjować ViewHolder w getView()? Dlaczego nie możemy zainicjować tego w konstruktorze?ViewHolder - dobra praktyka

+1

http://www.youtube.com/watch?v=wDBM6wVEO70. Obejrzyj ten film. Powinien odpowiedzieć na twoje pytanie. – Raghunandan

Odpowiedz

29

Będziesz mieć wiele ViewHolder obiektów w istnieniu.

Ze względu na swój charakter nie tworzy nowych instancji View dla każdego z jego wierszy. Jest tak, że jeśli masz milion rzeczy, nie musisz przechowywać informacji o układzie na milion rzeczy. Co więc chcesz przechowywać? Tylko rzeczy, które są na ekranie. Możesz następnie ponownie wykorzystać te widoki. W ten sposób twój ListView z miliona obiektów może mieć po prostu 10 widoków potomnych.

W niestandardowego adaptera tablicy, trzeba będzie funkcję o nazwie getView(), który wygląda mniej więcej tak:

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

to będzie działać, ale poświęć chwilę i sprawdzić, czy można dostrzec nieefektywność tutaj. Zastanów się, który z powyższych kodów zostanie nazwany redundantnie.

Problem polega na tym, że wzywamy row.findViewById w kółko, mimo że po raz pierwszy sprawdzimy to, nigdy się nie zmieni. Jeśli masz tylko prostą TextView na liście, to prawdopodobnie nie jest tak źle, jeśli masz złożony układ lub masz wiele widoków, dla których chcesz ustawić dane, możesz stracić trochę czasu na znajdowaniu widoku i jeszcze raz.

Jak to naprawić? Cóż, sensownym byłoby przechowywanie tego TextView gdzieś po sprawdzeniu. Wprowadzamy więc klasę o nazwie ViewHolder, która "zatrzymuje" widoki. Więc wewnątrz zasilacza, wprowadzić wewnętrzną klasę tak:

private static class ViewHolder { 
    TextView textView; 
} 

Ta klasa jest prywatny, ponieważ jest to po prostu mechanizm buforowania dla adaptera, a to jest statyczne, dzięki czemu nie musimy odniesienie do adapter, aby z niego korzystać.

Spowoduje to zapisanie naszego widoku, abyśmy nie musieli wielokrotnie dzwonić pod numer row.findViewById. Gdzie powinniśmy go ustawić? Kiedy podnosimy widok po raz pierwszy. Gdzie go przechowujemy? Widoki mają niestandardowe pole "znacznik", które może służyć do przechowywania meta-informacji o widoku - dokładnie tego, co chcemy! Następnie, jeśli już widzieliśmy ten pogląd, musimy tylko patrzeć tag zamiast patrząc na każdego z widoków w obrębie rzędu ..

So if wewnątrz getView() staje:

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

Teraz musimy po prostu zaktualizować wartość tekstową holder.textView, ponieważ jest to już odniesienie do widoku z recyklingu! Więc nasz kod ostatniego adaptera staje się następujący:

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

Skończyliśmy!

Niektóre rzeczy o tym myśleć:

  1. W jaki sposób ta zmiana, jeśli masz wiele widoków w wierszu, który chcesz zmienić? Jako wyzwanie, uczynić ListView, gdzie każdy wiersz ma dwa TextView obiektów i ImageView
  2. Podczas debugowania ListView, sprawdzić kilka rzeczy, dzięki czemu można naprawdę zobaczyć, co się dzieje:
    1. Ile razy konstruktor ViewHolder nazywa .
    2. Co wartość holder.textView.getText() to zanim go zaktualizować na koniec getView()
+0

thnx, wspaniała odpowiedź – Gorets

+0

Nie ma problemu - daj mi znać, jeśli wszystko ma sens, funkcja ViewHolder na chwilę mnie zmyliła, gdy po raz pierwszy się o tym dowiedziałem. – mindvirus

+0

Wow. Doskonała odpowiedź. Jako wyzwanie dla ciebie; czy możesz to poprawić dzięki niektórym referencjom? =) – Qw4z1

2

Gdy przewijamy listę za każdym razem, gdy wypełniamy wiersz, a dla każdego wiersza tworzony jest nowy widok wiersza, musimy zainicjować uchwyt widoku. Podobnie jak mam dwa TextView w rzędzie wtedy

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

Nie, każdy wzorzec ma różne wartości. Ponieważ mamy różne dane w rzędzie. –

Powiązane problemy