Ответ 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);
}
});
Это базовый пример, поэтому не принимайте его как единственный возможный способ. Возможности бесконечны.