Прежде всего:
- Это решение предполагает, что элементы, которые все еще видны после изменения набора данных, также скользят вправо, а затем снова скользят снизу (это, по крайней мере, то, о чем вы просите)
- Из-за этого требования я не смог найти простого и красивого решения этой проблемы (по крайней мере, на первой итерации). Единственный способ, который я нашел, это обмануть адаптер — и заставить фреймворк сделать что-то, для чего он не предназначен. Вот почему первая часть (Как это обычно работает) описывает, как добиться красивой анимации с помощью
RecyclerView
способа по умолчанию. Во второй части описывается решение, как обеспечить анимацию выскальзывания/вставки для всех элементов после изменения набора данных.
- Позже я нашел лучшее решение, которое не требует обмана адаптера случайными идентификаторами (перейдите к нижней части для обновленной версии).
Как это обычно работает
Чтобы включить анимацию, вам нужно сообщить RecyclerView
, как изменился набор данных (чтобы он знал, какие анимации следует запускать). Это можно сделать двумя способами:
1) Простая версия: нам нужно установить adapter.setHasStableIds(true);
и предоставить идентификаторы ваших товаров через public long getItemId(int position)
в вашем Adapter
в RecyclerView
. RecyclerView
использует эти идентификаторы, чтобы выяснить, какие элементы были удалены/добавлены/перемещены во время вызова adapter.notifyDataSetChanged();
.
2) Расширенная версия: вместо вызова adapter.notifyDataSetChanged();
вы также можете явно указать, как изменился набор данных. Adapter
предоставляет несколько методов, таких как adapter.notifyItemChanged(int position)
,adapter.notifyItemInserted(int position)
,... для описания изменений в наборе данных.
Анимации, которые запускаются для отражения изменений в наборе данных, управляются ItemAnimator
. RecyclerView
уже оснащен хорошим DefaultItemAnimator
по умолчанию. Кроме того, можно определить пользовательское поведение анимации с помощью пользовательского ItemAnimator
.
Стратегия реализации слайда наружу (справа), слайда внутрь (внизу)
Слайд справа — это анимация, которая должна воспроизводиться, если элементы удаляются из набора данных. Анимация слайда снизу должна воспроизводиться для элементов, которые были добавлены в набор данных. Как упоминалось в начале, я предполагаю, что желательно, чтобы все элементы выдвигались вправо и вдвигались снизу. Даже если они видны до и после изменения набора данных. Обычно RecyclerView
воспроизводит анимацию изменения/перемещения таких элементов, которые остаются видимыми. Однако, поскольку мы хотим использовать анимацию удаления/добавления для всех элементов, нам нужно обмануть адаптер, заставив его думать, что после изменения есть только новые элементы, а все ранее доступные элементы были удалены. Этого можно добиться, указав случайный идентификатор для каждого элемента в адаптере:
@Override
public long getItemId(int position) {
return Math.round(Math.random() * Long.MAX_VALUE);
}
Теперь нам нужно предоставить пользовательский ItemAnimator
, который управляет анимацией для добавленных/удаленных элементов. Структура представленного SlidingAnimator
очень похожа на структуру android.support.v7.widget.DefaultItemAnimator
, поставляемую с RecyclerView
. Также обратите внимание, что это доказательство концепции и должно быть скорректировано перед использованием в любом приложении:
public class SlidingAnimator extends SimpleItemAnimator {
List<RecyclerView.ViewHolder> pendingAdditions = new ArrayList<>();
List<RecyclerView.ViewHolder> pendingRemovals = new ArrayList<>();
@Override
public void runPendingAnimations() {
final List<RecyclerView.ViewHolder> additionsTmp = pendingAdditions;
List<RecyclerView.ViewHolder> removalsTmp = pendingRemovals;
pendingAdditions = new ArrayList<>();
pendingRemovals = new ArrayList<>();
for (RecyclerView.ViewHolder removal : removalsTmp) {
// run the pending remove animation
animateRemoveImpl(removal);
}
removalsTmp.clear();
if (!additionsTmp.isEmpty()) {
Runnable adder = new Runnable() {
public void run() {
for (RecyclerView.ViewHolder addition : additionsTmp) {
// run the pending add animation
animateAddImpl(addition);
}
additionsTmp.clear();
}
};
// play the add animation after the remove animation finished
ViewCompat.postOnAnimationDelayed(additionsTmp.get(0).itemView, adder, getRemoveDuration());
}
}
@Override
public boolean animateAdd(RecyclerView.ViewHolder holder) {
pendingAdditions.add(holder);
// translate the new items vertically so that they later slide in from the bottom
holder.itemView.setTranslationY(300);
// also make them invisible
holder.itemView.setAlpha(0);
// this requests the execution of runPendingAnimations()
return true;
}
@Override
public boolean animateRemove(final RecyclerView.ViewHolder holder) {
pendingRemovals.add(holder);
// this requests the execution of runPendingAnimations()
return true;
}
private void animateAddImpl(final RecyclerView.ViewHolder holder) {
View view = holder.itemView;
final ViewPropertyAnimatorCompat anim = ViewCompat.animate(view);
anim
// undo the translation we applied in animateAdd
.translationY(0)
// undo the alpha we applied in animateAdd
.alpha(1)
.setDuration(getAddDuration())
.setInterpolator(new DecelerateInterpolator())
.setListener(new ViewPropertyAnimatorListener() {
@Override
public void onAnimationStart(View view) {
dispatchAddStarting(holder);
}
@Override
public void onAnimationEnd(View view) {
anim.setListener(null);
dispatchAddFinished(holder);
// cleanup
view.setTranslationY(0);
view.setAlpha(1);
}
@Override
public void onAnimationCancel(View view) {
}
}).start();
}
private void animateRemoveImpl(final RecyclerView.ViewHolder holder) {
View view = holder.itemView;
final ViewPropertyAnimatorCompat anim = ViewCompat.animate(view);
anim
// translate horizontally to provide slide out to right
.translationX(view.getWidth())
// fade out
.alpha(0)
.setDuration(getRemoveDuration())
.setInterpolator(new AccelerateInterpolator())
.setListener(new ViewPropertyAnimatorListener() {
@Override
public void onAnimationStart(View view) {
dispatchRemoveStarting(holder);
}
@Override
public void onAnimationEnd(View view) {
anim.setListener(null);
dispatchRemoveFinished(holder);
// cleanup
view.setTranslationX(0);
view.setAlpha(1);
}
@Override
public void onAnimationCancel(View view) {
}
}).start();
}
@Override
public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) {
// don't handle animateMove because there should only be add/remove animations
dispatchMoveFinished(holder);
return false;
}
@Override
public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) {
// don't handle animateChange because there should only be add/remove animations
if (newHolder != null) {
dispatchChangeFinished(newHolder, false);
}
dispatchChangeFinished(oldHolder, true);
return false;
}
@Override
public void endAnimation(RecyclerView.ViewHolder item) { }
@Override
public void endAnimations() { }
@Override
public boolean isRunning() { return false; }
}
Это окончательный результат:
а>
Обновление: при повторном чтении сообщения я нашел лучшее решение.
Это обновленное решение не требует, чтобы адаптер с помощью случайных идентификаторов думал, что все элементы были удалены, а добавлены только новые элементы. Если мы применим 2) расширенную версию — как уведомить адаптер об изменениях набора данных, мы можем просто сообщить adapter
, что все предыдущие элементы были удалены, а все новые элементы добавлены:
int oldSize = oldItems.size();
oldItems.clear();
// Notify the adapter all previous items were removed
notifyItemRangeRemoved(0, oldSize);
oldItems.addAll(items);
// Notify the adapter all the new items were added
notifyItemRangeInserted(0, items.size());
// don't call notifyDataSetChanged
//notifyDataSetChanged();
Представленный ранее SlidingAnimator
по-прежнему необходим для анимации изменений.
person
Andreas Wenger
schedule
21.03.2016
LayoutManager
и переопределитьonLayoutChildren
и вернуть true изsupportsPredictiveItemAnimations()
. Поэтому при вызовеnotifyDataSetChanged
вы должны правильно разместить дочерние элементы recyclerView, чтобы сделать анимацию. - person   schedule 27.03.2016