В чем преимущество ViewHolder?

когда вы разрабатываете программу для Android; и вы хотите иметь ArrayAdapter вы можете просто иметь класс (В большинстве случаев с ViewHolder суффикс) или непосредственно надуть convertView и найдите свой вид по id.
Итак, в чем преимущество использования ViewHolder?
Пример как здесь :

        if(convertView==null)
        {
            convertView = ((Activity)_context).getLayoutInflater().inflate(R.layout.row_phrase, null);
        }
((TextView)convertView.findViewById(R.id.txtPhrase)).setText("Phrase 01");  

или :

static class ViewHolder {   
ImageView leftIcon;   
TextView upperLabel;  
TextView lowerLabel;  
}

и, наконец, в getView адаптера :

ViewHolder holder = null;
  if (view == null) {
   view = LayoutInflater.from(context).inflate(R.layout.row_layout,
   null, false);
   holder = new ViewHolder();
   holder.leftIcon = (ImageView) view.findViewById(R.id.leftIcon);

4 ответов


поймите, как работает ListView recycling

как работает механизм рециркуляции ListView

вы не можете повторно использовать строку, которая в настоящее время используется. Приведенная выше ссылка объясняет, как работает механизм рециркуляции listview

Итак, в чем преимущество использования ViewHolder?

цитируя документы

ваш код может вызвать findViewById() часто во время прокрутки ListView, что может замедлить производительность. Даже когда адаптер возвращает завышенный вид для переработки, вам все равно нужно найти элементы и обновить их. Способ обойти повторное использование findViewById() использовать шаблон дизайна "держатель вида".

    public View getView(int position, View convertView, ViewGroup parent) { 
             ViewHolder holder; 

             if (convertView == null) { // if convertView is null
                 convertView = mInflater.inflate(R.layout.mylayout, 
                         parent, false);
                 holder = new ViewHolder(); 
                     // initialize views  
                convertView.setTag(holder);  // set tag on view
            } else { 
                holder = (ViewHolder) convertView.getTag();
                        // if not null get tag 
                        // no need to initialize
            } 

            //update views here  
            return convertView; 
    }

вы пропустили важную часть convertView.setTag(holder) и holder = (ViewHolder) ConvertView.getTag()

http://developer.android.com/training/improving-layouts/smooth-scrolling.html


ViewHolder шаблон дизайна используется для ускорения рендеринга вашего ListView - на самом деле, чтобы он работал плавно, findViewById довольно дорого (он делает DOM-синтаксический анализ) при использовании каждый раз, когда элемент списка отображается, он должен пересекать иерархию макета , а также создавать объекты. Поскольку списки могут перерисовывать свои элементы довольно часто во время прокрутки, такие накладные расходы могут быть существенными.

вы можете найти хорошее объяснение, как это работает в :

http://www.youtube.com/watch?v=wDBM6wVEO70&feature=youtu.be&t=7m

начиная с минуты 10, вы объяснили шаблон дизайна ViewHolder экспертами google.

[edit]

findViewById не создает новые объекты, он только пересекает иерархию - вот ссылка http://androidxref.com/5.1.1_r6/xref/frameworks/base/core/java/android/view/ViewGroup.java#3610


когда вы просматриваете свой ListView, в любой момент времени отображается только несколько представлений. Это означает, что вам не нужно создавать экземпляр представления для каждого элемента адаптера; когда представление прокручивается за пределами экрана, его можно использовать повторно или рециркулированный.

просмотр рециркуляции и шаблон ViewHolder не совпадают. Шаблон ViewHolder предназначен исключительно для уменьшения количества view.findViewById(int) звонки, которые вы делаете. Шаблон ViewHolder работает только тогда, когда вы используете просмотр рециркуляции.

на getView(int position, View convertView, ViewGroup parent), the элемент, независимо от того, существует ли сама строка. As findViewById(int) рекурсивно перебирает ViewGroup, пока не найдет потомка с заданным идентификатором, это немного бессмысленно для нашего переработанные представления-мы повторно находим представления, на которые у нас уже есть ссылки.

избегайте этого, используя объект ViewHolder для хранения ссылок на вложенные представления после их "поиска":

private static class ViewHolder {
  final TextView text;
  final TextView timestamp;
  final ImageView icon;
  final ProgressBar progress;

  ViewHolder(TextView text, TextView timestamp, ImageView icon, ProgressBar progress) {
    this.text = text;
    this.timestamp = timestamp;
    this.icon = icon;
    this.progress = progress;
  }
}

View.setTag(Object) позволяет сообщить представление для хранения произвольного объекта. Если мы используем его для хранения экземпляра нашего ViewHolder после того, как мы сделаем наш findViewById(int) звонки, то мы можем использовать View.getTag() на переработанных представлениях, чтобы избежать повторных вызовов и снова.

public View getView(int position, View convertView, ViewGroup parent) {
  View view = convertView;
  if (view == null) {
    view = // inflate new view
    ViewHolder holder = createViewHolderFrom(view);
    view.setTag(holder);  
  }
  ViewHolder holder = view.getTag();
  // TODO: set correct data for this list item
  // holder.icon.setImageDrawable(...)
  // holder.text.setText(...)
  // holder.timestamp.setText(...)
  // holder.progress.setProgress(...)
  return view;
}

private ViewHolder createViewHolderFrom(View view) {
    ImageView icon = (ImageView) view.findViewById(R.id.listitem_image);
    TextView text = (TextView) view.findViewById(R.id.listitem_text);
    TextView timestamp = (TextView) view.findViewById(R.id.listitem_timestamp);
    ProgressBar progress = (ProgressBar) view.findViewById(R.id.progress_spinner);

    return new ViewHolder(text, timestamp, icon, progress);
}

преимущества производительности этой оптимизации сомнительны, но это преимущество ViewHolder.


во-первых :

на ListView при прокрутке ListView вам нужно создать новый элемент и привязать свои данные на нем, так что если у вас есть много пунктов в ListView Это может привести к утечке памяти, потому что больше объектов, созданных для элементов, но Android, используя концепцию утилизации большей части своего API, и это означает, что вы создаете один объект и используете его вместо того, чтобы уничтожить его и объявить новый , поэтому при прокрутке ListView API, используя невидимые элементы, которые вы прокрутили и передать его в getView способ это convertView Итак, здесь вы имеете дело с большим количеством предметов ListView

во-вторых :

если у вас есть пользовательский элемент ListView вам нужно прикрепить свой пользовательский макет к каждому элементу ListView Так вы будете каждый раз ListView привязать новый элемент с помощью findViewById получить ссылку на элементы макета. Этот метод будет идти на поиск вашего элемента рекурсивным способом, так что вы ViewHolder поможет вам сделать рекурсивный только один раз, а затем он будет содержать ссылку на макет товар для вас, пока вы не можете прикрепить его к ListView

надеюсь, это поможет вам и накормит меня в любой не очевидной вещи