Советы для профессионального использования RecyclerView. Часть 2

Советы для профессионального использования RecyclerView. Часть 2

Продолжая предыдущую статью, в этой я расскажу про ItemDecoration и ItemAnimator и постараюсь объяснить принцип их работы в RecyclerView на примере простого приложения, которое доступно на Github.

1. ItemDecoration

ItemDecoration используется для декорирования элементов списка в RecyclerView.

С помощью ItemDecoration вы сможете добавлять разделители между view-компонентам, выравнивать их или разбивать равными промежутками. Чтобы добавить простой разделитель между view-компонентами, воспользуйтесь классом DividerItemDecoration, который можно найти в библиотеке поддержки версии 25.1.0 и выше. Следующий фрагмент кода демонстрирует его реализацию:

mDividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(),
             mLayoutManager.getOrientation());
recyclerView.addItemDecoration(mDividerItemDecoration);

Лучший способ создания собственного разделителя — расширение класса RecyclerView.ItemDecoration. В примере приложения я использовал GridLayoutManager и применил CharacterItemDecoration к RecyclerView:

recyclerView.addItemDecoration(new CharacterItemDecoration(50));

Здесь CharacterItemDecoration устанавливает смещение (англ. offset) на 50 пикселей в своём конструкторе и переопределяет getItemOffsets(...). Внутри метода getItemOffsets() каждое поле outRects определяет количество пикселей, которые необходимо установить для каждого view-компонента, подобно внутренним и внешним отступам. Поскольку я использовал GridLayoutManager и хотел настроить равные расстояния между элементами сетки, я установил отступ справа в 25 пикселей (т.е. offset/2) для каждого чётного элемента и отступ слева в 25 пикселей для каждого нечётного элемента, сохраняя при этом верхний отступ одинаковым для всех элементов.

Отступы элементов в сетке

2. ItemAnimator

ItemAnimator используется для анимации элементов или view-компонентов внутри RecyclerView.

Анимация элементов в списке

Давайте сделаем наше приложение «Инстаграмоподобным», расширив DefaultItemAnimator и переопределив несколько методов.

public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
        return true;
}

Метод canReuseUpdatedViewHolder(...) определяет, будет ли один и тот же ViewHolder использоваться для анимации, если данные этого элемента изменятся. Если он возвращает false, то оба ViewHolders — старый и обновленный — передаются в метод animateChange(...).

public ItemHolderInfo recordPreLayoutInformation(@NonNull RecyclerView.State state, @NonNull RecyclerView.ViewHolder viewHolder, int changeFlags, @NonNull List<Object> payloads) {
    if (changeFlags == FLAG_CHANGED) {
        for (Object payload : payloads) {
            if (payload instanceof String) {
                return new CharacterItemHolderInfo((String) payload);
            }
        }
    }
    return super.recordPreLayoutInformation(state, viewHolder, changeFlags, payloads);
}

public static class CharacterItemHolderInfo extends ItemHolderInfo {
        public String updateAction;

        public CharacterItemHolderInfo(String updateAction) {
            this.updateAction = updateAction;
        }
}

RecyclerView вызывает метод recordPreLayoutInformation(...) до начала отрисовки layout. ItemAnimator должен записывать необходимую информацию о view-компоненте до того, как он будет перезаписан, перемещен или удалён. Данные, возвращаемые этим методом, будут переданы соответствующему методу анимации (в нашем случае это animateChange(...)).

@Override
public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder,
                                 @NonNull RecyclerView.ViewHolder newHolder,
                                 @NonNull ItemHolderInfo preInfo,
                                 @NonNull ItemHolderInfo postInfo) {

  if (preInfo instanceof CharacterItemHolderInfo) {
    CharacterItemHolderInfo recipesItemHolderInfo = (CharacterItemHolderInfo) preInfo;
    CharacterRVAdapter.CharacterViewHolder holder = (CharacterRVAdapter.CharacterViewHolder) newHolder;
       if (CharacterRVAdapter.ACTION_LIKE_IMAGE_DOUBLE_CLICKED.equals(recipesItemHolderInfo.updateAction)) {
            animatePhotoLike(holder);
          }
        }
       return false;
    }

private void animatePhotoLike(final CharacterRVAdapter.CharacterViewHolder holder) {
     holder.likeIV.setVisibility(View.VISIBLE);
     holder.likeIV.setScaleY(0.0f);
     holder.likeIV.setScaleX(0.0f);
     
     AnimatorSet animatorSet = new AnimatorSet();
     ObjectAnimator scaleLikeIcon = ObjectAnimator.ofPropertyValuesHolder
              (holder.likeIV, PropertyValuesHolder.ofFloat("scaleX", 0.0f, 2.0f), 
              PropertyValuesHolder.ofFloat("scaleY", 0.0f, 2.0f), PropertyValuesHolder.ofFloat("alpha", 0.0f, 1.0f, 0.0f));
     scaleLikeIcon.setInterpolator(DECELERATE_INTERPOLATOR);
     scaleLikeIcon.setDuration(1000);

     ObjectAnimator scaleLikeBackground = ObjectAnimator.ofPropertyValuesHolder
              (holder.characterCV, PropertyValuesHolder.ofFloat("scaleX", 1.0f, 0.95f, 1.0f),
              PropertyValuesHolder.ofFloat("scaleY", 1.0f, 0.95f, 1.0f));
     scaleLikeBackground.setInterpolator(DECELERATE_INTERPOLATOR);
     scaleLikeBackground.setDuration(600);
     animatorSet.playTogether(scaleLikeIcon, scaleLikeBackground);
     animatorSet.start();
}

RecyclerView вызывает метод animateChange(...), когда элемент адаптера присутствует одновременно до и после отрисовки после вызова метода notifyItemChanged(int). Этот метод также можно использовать, при вызове notifyDataSetChanged(), если при этом в адаптере используются стабильные идентификаторы. Это необходимо для того, чтобы RecyclerView мог переиспользовать view-компоненты в тех же ViewHolders. Обратите внимание на то, что этот метод принимает в качестве аргументов: (ViewHolder oldHolder, ViewHolder newHolder, ItemHolderInfo preInfo, ItemHolderInfo postInfo). Поскольку мы повторно используем ViewHolder, оба — oldHolder и newHolder — одинаковы.

Всякий раз, когда пользователь дважды кликает на любой элемент, вызывается следующий метод:

notifyItemChanged(position, ACTION_LIKE_IMAGE_DOUBLE_CLICKED);

Это запускает всю цепочку вызовов: canReuseUpdatedViewHolder(...), recordPreLayoutInformation(...) и, в конечном итоге, animateChange(...) в ItemAnimator, который, в свою очередь, анимирует элемент списка и иконку сердечка в этом элементе (пример на гифке выше).

Это вторая часть серии статей про RecyclerView. Если пропустили первую часть, то читайте её здесь.

Ещё несколько хороших статей на тему RecyclerView:

← Советы для профессионального использования RecyclerView. Часть 1

Перевод статьи «Recyclerview Pro Tips — Part 2»

ПОХОЖИЕ ПУБЛИКАЦИИ

818
18/10/2018

0 комментариев к статье "Советы для профессионального использования RecyclerView. Часть 2"

Добавить комментарий