Продолжая предыдущую статью, в этой я расскажу про 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
qwert2603
При переопределении метода
public boolean animateChange(...)
уItemAnimator
обязательно вызыватьdispatchAnimationFinished(newHolder);
И еще крайне желательно сохранять созданные
Animator
's, чтобы суметь их остановить в переопределённых методахendAnimation(ViewHolder)
иendAnimations
.