ItemDecoration переопределяет getItemOffsets() и анимацию

Я применяю равные поля для моего RecyclerView с помощью GridLayoutManager переопределив getItemOffsets() метод (см. мой код ниже).

Однако при удалении объекта из адаптера анимация удаления вызывается без смещений. Таким образом, anmiation начинается в другом положении, чем объект, который будет удален.

Я пытался получить позицию через getSpanIndex(position) но позиция (parent.getChildAdapterPosition(view)) возвращает NO_POSITION поскольку объект уже был удален из адаптера, когда getItemOffsets() называется.

Есть ли способ получить смещения в моем случае?

@Override
public void getItemOffsets(Rect outRect, View view, 
                     RecyclerView parent, RecyclerView.State state) {

    GridLayoutManager mgr = parent.getLayoutManager();
    int position = parent.getChildAdapterPosition(view);

    if (position == RecyclerView.NO_POSITION) {
       // here I need to access the position of the current element
       // and call outRect.set(left, top , right, bottom);
       // which is not possible because it is no longer in the adapter
        return;
    } 

    int spanCount = mgr.getSpanCount();
    int spanSize = mgr.getSpanSizeLookup().getSpanSize(position);
    int spanIndex = mgr.getSpanSizeLookup().getSpanIndex(position, spanCount);

    if (spanIndex == spanCount-1) {
        // last element
        left = space / 2;
        right = space;
    } else if (spanIndex == 0) {
        // first element
        left = space;
        right = space / 2;
    } else {
        // middle element
        left = space / 2;
        right = space / 2;
    }
    outRect.set(left, top, right, bottom);
}

4 ответа

Решение

Попробуйте использовать виды LayoutParams, Если вы не используете некоторые пользовательские LayoutManager, он должен содержать информацию, которая вам нужна.

int position = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewAdapterPosition();

На самом деле, это не очень хорошее решение получить дочернюю позицию от родителя. Фактическое состояние просмотра может отличаться от реального состояния, потому что в то же время работает либо ItemDecorator, либо LayoutManager, либо RecyclerView для управления ими, либо Adapter для изменения положения некоторых элементов.

Лучше, когда попросить Recycler дать вам ViewHolder для определенного вида. Затем вы можете получить всю необходимую информацию (getLayoutPosition(), getAdapterPosition()).

parent.getChildViewHolder(view).getLayoutPosition();
parent.getChildViewHolder(view).getAdapterPosition;

И еще один совет, не из вашего случая. Если вам нужно получить ItemCount, не используйте adapter.getItemCount(), лучше получить состояние из состояния, потому что количество элементов может быть разным в Adapter и Recycler.

state.getItemCount()

Я наткнулся на эту же проблему здесь, и я прочитал ответы и обсуждения. Это может быть очень поздний ответ, но я отвечу ради тех, кто столкнулся с этой проблемой.

Ответ Джулиана Ос должен был быть помечен как правильный ответ, но на самом деле мало что объяснил.

Вот подробное объяснение:

на ItemDecoration"s getItemOffsets() сначала получить позицию предмета parent.getChildViewHolder(view).getAdapterPosition(); Как отметил Ильягорбунов, это более безопасный способ получить точку зрения. Этот метод возврата RecyclerView.NO_POSITION когда запрашиваемое представление больше не существует. Так что, если это так, иди проверить parent.getChildViewHolder(view).getOldPosition() которая возвращает старую позицию анимации. Этот метод также возвращает RecyclerView.NO_POSITION когда ChildViewHolder больше не присутствует в представлении или завершил анимацию.

Если в вашем адаптере есть несколько типов представлений, просто напрямую получите тип представления из parent.getChildViewHolder(view).getItemViewType()

Для полноты вот небольшой фрагмент кода:

override fun getItemOffsets(outRect: Rect?, view: View?, parent: RecyclerView?, state: RecyclerView.State?) {
    super.getItemOffsets(outRect, view, parent, state)
    var position = parent?.getChildViewHolder(view)?.adapterPosition
    if (position == RecyclerView.NO_POSITION) {
        val oldPosition = parent?.getChildViewHolder(view)?.oldPosition
        if (oldPosition == RecyclerView.NO_POSITION) return
        position = oldPosition
    }

    val viewType = parent?.getChildViewHolder(view)?.itemViewType

    when (viewType) {
        //do your outRect here
    }
}

Итак, после попытки выяснить это в течение некоторого времени, я в конце концов наткнулся на ViewHolder#getOldPosition,

Javadoc говорит следующее:

Когда LayoutManager поддерживает анимацию, RecyclerView отслеживает 3 позиции для ViewHolders для выполнения анимации.

Если ViewHolder был размечен в предыдущем вызове onLayout, старая позиция сохранит индекс адаптера в предыдущем макете.

Другими словами, getOldPosition() возвращает позицию адаптера ViewHolder, в которой он находился до удаления.

Таким образом, чтобы получить это значение из вашего метода ItemDecoration # getItemOffsets, просто укажите:

parent.getChildViewHolder(view).getOldPosition()

Я проверял это сам, и это действительно работает:).

Вы должны получить spanIndex от (view.layoutParams as?GridLayoutManager.LayoutParams)?.spanIndex

и то же самое для spanSize

(view.layoutParams as? GridLayoutManager.LayoutParams)?.spanSize

благодаря этому вы в безопасности во время анимации, когда адаптер может иметь новый набор данных и SpanSizeLookup будет работать с другим набором элементов, чем те, для которых вы рассчитываете смещения

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

Другие вопросы по тегам