Подтвердить что ты не робот

FindViewById vs Просмотр шаблона владельца в адаптере ListView

Я всегда использую LayoutInflater и findViewById для создания нового элемента в getView методе Adapter.

Но во многих статьях люди пишут, что findViewById очень медленный и настоятельно рекомендую использовать шаблон держателя вида.

Может ли кто-нибудь объяснить, почему findViewById так медленно? И почему шаблон просмотра владельца быстрее?

И что делать, если нужно добавить разные элементы в ListView? Должен ли я создавать классы для каждого типа?

static class ViewHolderItem1 {
    TextView textViewItem;
}

static class ViewHolderItem2 {
    Button btnViewItem;
}
static class ViewHolderItem3 {
    Button btnViewItem;
    ImageView imgViewItem;
}
4b9b3361

Ответ 1

Может ли кто-нибудь объяснить, почему findViewById так медленно? И почему View Holder Узор быстрее?

Если вы не используете Holder, getView() метод getView() будет вызывать findViewById() столько раз, сколько вы не увидите. Так что, если у вас 1000 строк в List и 990 строк будут отсутствовать, тогда 990 раз будет называться findViewById() снова.

Шаблон проектирования держателя используется для просмотра кеширования. Объект Holder (произвольный) содержит дочерние виджеты каждой строки, а когда строка отсутствует в представлении, то findViewById() не будет вызываться, но View будет переработана, а виджеты будут получены из Holder.

if (convertView == null) {
   convertView = inflater.inflate(layout, null, false);
   holder = new Holder(convertView);
   convertView.setTag(holder); // setting Holder as arbitrary object for row
}
else { // view recycling
   // row already contains Holder object
   holder = (Holder) convertView.getTag();
}

// set up row data from holder
titleText.setText(holder.getTitle().getText().toString());

Где класс Holder может выглядеть так:

public class Holder {

   private View row;
   private TextView title;

   public Holder(View row) {
      this.row = row;
   }

   public TextView getTitle() {
      if (title == null) {
         title = (TextView) row.findViewById(R.id.title);
      }
      return title;
   }
}

Поскольку @meredrica указала вам, хотите ли вы повысить производительность, вы можете использовать общедоступные поля (но это уничтожает инкапсуляцию).

Обновление:

Вот второй подход, как использовать шаблон ViewHolder:

ViewHolder holder;
// view is creating
if (convertView == null) {
   convertView = LayoutInflater.from(mContext).inflate(R.layout.row, parent, false);
   holder = new ViewHolder();   
   holder.title = (TextView) convertView.findViewById(R.id.title);
   holder.icon = (ImageView) convertView.findViewById(R.id.icon);
   convertView.setTag(holder);
}
// view is recycling
else {
   holder = (ViewHolder) convertView.getTag();
}

// set-up row
final MyItem item = mItems.get(position);
holder.title.setText(item.getTitle());
...

private static class ViewHolder {

   public TextView title;
   public ImageView icon;
}

Обновление # 2:

Как известно, Google и AppCompat v7 в качестве библиотеки поддержки выпустили новую ViewGroup под названием RecyclerView, которая предназначена для рендеринга любых видов на основе адаптеров, Поскольку @antonioleiva говорит в post: "Он является преемником ListView и GridView".

Чтобы иметь возможность использовать этот элемент, вам нужно в основном одно самое важное, и это особый вид адаптера, который завернут в упомянутую ViewGroup - RecyclerView.Adapter, где ViewHolder - это то, о чем мы говорим здесь:) Просто этот новый элемент ViewGroup имеет свой собственный шаблон ViewHolder. Все, что вам нужно сделать, это создать собственный класс ViewHolder, который должен простираться от RecyclerView.ViewHolder, и вам не нужно заботиться о том, является ли текущая строка в адаптере нулевой или нет.

Адаптер сделает это за вас, и вы можете быть уверены, что строка будет накачана только в том случае, если она должна быть раздута (я бы сказал). Вот простое прочтение:

public static class ViewHolder extends RecyclerView.ViewHolder {

   private TextView title;

   public ViewHolder(View root) {
      super(root);
      title = root.findViewById(R.id.title);
   }
}

Две важные вещи здесь:

  • Вам нужно вызвать конструктор super(), в котором вам нужно передать свой корневой вид строки
  • Вы можете получить конкретную позицию строки непосредственно из ViewHolder с помощью метода getPosition(). Это полезно, когда вы хотите сделать некоторые действие после нажатия 1 на виджет строки.

И использование ViewHolder в адаптере. Адаптер имеет три метода, которые вы должны реализовать:

  • onCreateViewHolder() - при создании ViewHolder
  • onBindViewHolder() - где вы обновляете свою строку. Мы можем сказать, что это фрагмент кода, в котором вы перерабатываете строку
  • getItemCount() - я бы сказал так же, как и обычный метод getCount() в BaseAdapter

Итак, небольшой пример:

@Override 
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
   View root = LayoutInflater.from(mContext).inflate(myLayout, parent, false);
   return new ViewHolder(root);
}

@Override public void onBindViewHolder(ViewHolder holder, int position) {
   Item item = mItems.get(position);
   holder.title.setText(item.getTitle());
}

@Override public int getItemCount() {
   return mItems != null ? mItems.size() : 0;
}

1 Хорошо отметить, что RecyclerView не предоставляет прямой интерфейс для прослушивания событий щелчка элемента. Это может быть интересно для кого-то, но вот хорошее объяснение, почему это не так любопытно, как на самом деле выглядит.

Я решил это, создав свой собственный интерфейс, который используется для обработки событий щелчка по строкам (и любого вида виджета, который вы хотите в строке):

public interface RecyclerViewCallback<T> {

   public void onItemClick(T item, int position); 
}

Я привязываю его к адаптеру через конструктор, а затем вызываю этот обратный вызов в ViewHolder:

root.setOnClickListener(new View.OnClickListener {
   @Override
   public void onClick(View v) {
      int position = getPosition();
      mCallback.onItemClick(mItems.get(position), position);
   }
});

Это базовый пример, поэтому не принимайте его как единственный возможный способ. Возможности бесконечны.

Ответ 2

Шаблон ViewHolder создаст статический экземпляр ViewHolder и прикрепит его к элементу представления при первом загрузке, а затем он будет извлечен из этого тега просмотра для будущих вызовов. поскольку мы знали, что метод getView() вызывается очень часто, особенно когда множество элементов в listview прокручивается, на самом деле он вызывается каждый раз, когда элемент listview становится видимым в прокрутке.

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

очень хорошо сказано @RomainGuy

ViewHolder может и должен также использоваться для хранения временных данных структуры, чтобы избежать выделения памяти в getView(). ViewHolder содержит буфер char, чтобы избежать выделения при получении данных из Курсор.