Как обрабатывать расширяемые ViewHolders в RecyclerView?

1

У меня есть RecyclerView работающий с FirestoreRecyclerAdapter в моем приложении. ViewHolders реализуют расширяемое меню "быстрых действий", которое расширяется при нажатии на элемент, и идеальным поведением является расширение только одного элемента за раз. Развертывание одного элемента должно свернуть все остальные элементы в адаптере.

Когда на экране достаточно элементов, чтобы вместить их все, это прекрасно работает. Но если список расширяется за пределы экрана, когда я пытаюсь перебрать элементы адаптера, я получаю NullPointerException когда отсоединенные или неэкранные ViewHolders пытаются вызвать getParent().

Как я могу перебрать все ViewHolders в адаптере или, наоборот, перебрать только видимые?

Соответствующий код адаптера:

@Override
protected void onBindViewHolder(@NonNull BrewViewHolder holder, 
               int position, @NonNull Brew brew) {

    // Bind Views
    holder.bind(brew);

    // Set expander ClickListener
    holder.card.setOnClickListener(v -> {
        for (int i = 0; i < mAdapter.getItemCount(); i++) {
            if (i != position) {
                // ERROR THROWN HERE
                BrewViewHolder vh = ((BrewViewHolder) recyclerView.getChildViewHolder(recyclerView.getChildAt(i)));
                vh.expanded = false;
            }
        }
        holder.expanded = !holder.expanded;
        notifyItemChanged(position);
   });

}

Соответствующий код ViewHolder:

public class BrewViewHolder extends RecyclerView.ViewHolder {

    // Expanded state
    public boolean expanded = false;
    ...

    public BrewViewHolder(@NonNull final View itemView) {
        super(itemView);
        ...
        // Set visibility of quick actions based on expanded state
        quickActions.setVisibility(expanded ? View.VISIBLE : View.GONE);
    }

}

Заранее спасибо!

РЕДАКТИРОВАТЬ: Как отметил Гилберто, это очень дорого. Пошел с версией решения Андрея:

private int expandedItemIndex = -1;

@Override
protected void onBindViewHolder(@NonNull BrewViewHolder holder, int position,
                                @NonNull Brew brew) {
    // Bind Views
    holder.bind(brew);

    // Set expander ClickListener
    holder.card.setOnClickListener(v -> {
        if (position == expandedItemIndex) {
            holder.expanded = false;
            expandedItemIndex = -1;
        } else {
            holder.expanded = true;
            if (expandedItemIndex != -1) {
                BrewViewHolder otherHolder = ((BrewViewHolder) recyclerView.getChildViewHolder(
                        recyclerView.getChildAt(expandedItemIndex)));
                otherHolder.expanded = false;
            }
            expandedItemIndex = position;
        }
        notifyItemRangeChanged(0, recyclerView.getChildCount());
    });

}
Теги:
android-recyclerview
android-viewholder

3 ответа

0
Лучший ответ

Вы не должны повторять все представления. Вам нужно просто сохранить индекс расширенного представления и вызвать notifyDataSetChanged или notifyItemChanged.

private int expandedItemIndex = -1;

@Override
protected void onBindViewHolder(@NonNull BrewViewHolder holder, 
               final int position, @NonNull Brew brew) {

    // Bind Views
    holder.bind(brew);

    // Set expander ClickListener
    holder.card.setOnClickListener(v -> {
        if (position == expandedItemIndex) {
            notifyItemChanged(position);
            expandedItemIndex = -1;
        } else {
            if (expandedItemIndex != -1) {
                notifyItemChanged(expandedItemIndex);
            }
            expandedItemIndex = position;
            notifyItemChanged(position);
        }
   });

   if (position == expandedItemIndex) {
      // Expand
      holder.quickActions.setVisibility(View.VISIBLE);
   } else {
      // Collapse
      holder.quickActions.setVisibility(View.GONE);
   }
}
  • 0
    Это близко к тому, что я хочу, но я думаю, что это может вызвать NPE, если развернутый элемент прокручивается за пределами экрана и щелкает другой элемент? Что происходит, когда вы notifyItemChanged(positionOfItemOffScreen) ? Кроме того, вы уведомляете, прежде чем элемент действительно изменился ...
  • 0
    @tobiasfried Вы не получаете NPE с моим кодом. Для закадровых элементов метод onBindViewHolder просто не будет вызываться. notifyItemYYY методы обновления представления не сразу, поэтому все правильно.
Показать ещё 1 комментарий
0

If (vh! = Null) {vh.expanded(false);}

Или же

Используй попробуй, лови)))

  • 0
    if (position == extendedItemIndex) {// Expand} else {// Collapse}. Я думаю, что это даст те же результаты
  • 0
    Да, все еще не удается. Моя проблема в том, что mAdapter.getItemCount() возвращает общее количество элементов, раздутых или нет.
Показать ещё 5 комментариев
0

Решено путем итерации по i < recyclerView.getChildCount() и проверки очистки VH в onViewRecycled(ViewHolder holder):

@Override
protected void onBindViewHolder(@NonNull BrewViewHolder holder, int position, @NonNull Brew brew) {
    // Bind Views
    holder.bind(brew);

    // Set expander ClickListener
    holder.card.setOnClickListener(v -> {
        for (int i = 0; i < recyclerView.getChildCount(); i++) {
            if (i != position) {
                BrewViewHolder otherHolder = ((BrewViewHolder) recyclerView.getChildViewHolder(recyclerView.getChildAt(i)));
                otherHolder.expanded = false;
            }
        }
        holder.expanded = !holder.expanded;
        notifyItemRangeChanged(0, recyclerView.getChildCount());
    });
}

@Override
public void onViewRecycled(@NonNull BrewViewHolder holder) {
    super.onViewRecycled(holder);
    holder.expanded = false;
}
  • 1
    это неправильный подход и слишком дорогой.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню