
Продолжая предыдущую статью, в этой я расскажу про 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 — More Animations with Less Code using Support Library ListAdapter
- Using SnapHelper in RecyclerView
- File template for RecyclerView Adapter in Kotlin
← Советы для профессионального использования RecyclerView. Часть 1
