У меня есть RecycleView, в котором отображается список элементов. Я указываю аниматор по умолчанию для RecyclerView следующим образом:
recyclerView.setItemAnimator( new DefaultItemAnimator() );
Все работает отлично, но я хочу использовать свои собственные анимации для добавления / удаления / обновления элементов в списке.
Я определил собственный класс аниматора следующим образом:
public class MyAnimator extends RecyclerView.ItemAnimator {
@Override
public boolean animateDisappearance(@NonNull RecyclerView.ViewHolder viewHolder, @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
return false;
}
@Override
public boolean animateAppearance(@NonNull RecyclerView.ViewHolder viewHolder, @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
return false;
}
@Override
public boolean animatePersistence(@NonNull RecyclerView.ViewHolder viewHolder, @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
return false;
}
@Override
public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder, @NonNull RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
return false;
}
@Override
public void runPendingAnimations() {
}
@Override
public void endAnimation(RecyclerView.ViewHolder item) {
}
@Override
public void endAnimations() {
}
@Override
public boolean isRunning() {
return false;
}
}
И установите его так же, как я сделал для DefaultItemAnimator. Анимации больше не воспроизводятся, поэтому я думаю, это сработало, но проблема в том, что элементы иногда складываются друг на друга, и когда я удаляю все элементы, некоторые из них все еще остаются, поэтому я думаю, я что-то упускаю.
Насколько я понял, animateDisappearance - это метод, который вызывается, когда элемент удаляется из списка. Если я верну false, он должен просто пропустить анимацию, насколько я понял, правильно?
Я вообще на правильном пути? когда я ищу примеры этого на github, результатов очень мало, и в целом я не могу найти ни одного базового примера кода, как это сделать, а те, которые я нашел, представляют собой все тысячи строк кода.
Как я могу просто перезаписать анимацию добавления / удаления по умолчанию моей собственной без использования каких-либо внешних библиотек? Спасибо!
Обновлено:
Мне удалось переопределить анимацию по умолчанию следующим образом:
recyclerView.setItemAnimator(new DefaultItemAnimator() {
@Override
public boolean animateRemove(RecyclerView.ViewHolder holder) {
holder.itemView.clearAnimation();
final RecyclerView.ViewHolder h = holder;
holder.itemView.animate()
.alpha(0)
.setInterpolator(new AccelerateInterpolator(2.f))
.setDuration(1350)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
dispatchRemoveFinished(h);
}
})
.start();
//
return false;
}
} );
Анимация работает отлично, но почему-то кажется, что «dispatchRemoveFinished» запускается мгновенно, поэтому вместо настройки остальных элементов ПОСЛЕ анимации, они делают это мгновенно, как только представление удаляется. Есть ли способ исправить это?
Чтобы использовать любой из них, мне нужно использовать класс BaseItemAnimator, который представляет собой почти 1000 строк кода: github.com/wasabeef/recyclerview-animators/blob/… Это единственный способ переопределить анимацию по умолчанию? Кажется, должен быть более короткий путь, не так ли?
это DefaultItemAnimator: github.com/aosp-mirror/platform_frameworks_base/blob/master/… - где вы можете пропустить реализацию некоторых методов интерфейса и просто вызвать super.methodName() внутри них ... если только вызов класса super, просто возвращающий false, не сработает.
Извините, я не уверен, что полностью понял. Вы имеете в виду переопределение методов DefaultItemAnimator (как я сделал в отредактированном вопросе) и вызов super в конце?
Вам необходимо реализовать animatePersistence для анимации движения при удалении элемента.
@ 0x29a я имел в виду, что вам нужно будет вызвать, например. super.animateRemove(holder);, на всякий случай вы бы метод не реализовали. большинство методов там возвращают void или boolean (а не только какое-то статическое логическое значение).
При реализации вашего RecyclerView.ItemAnimator вы должны следовать нескольким правилам, иначе состояние RecyclerView испортится:
Все эти пустые методы, возвращающие false, должны как минимум вызывать dispatchAnimationFinished(viewHolder), чтобы очистить состояние анимации.
Если эти методы должны запускать анимацию, вы должны dispatchAnimationStarted(viewHolder), сохранить запрос анимации и вернуть true, чтобы получить вызов runPendingAnimations(), где анимация должна начинаться.
Вам необходимо отслеживать текущие анимации, чтобы иметь возможность правильно их отменить. Вы также будете получать запросы на элементы, которые уже анимируются.
Вот образец ItemAnimator, который анимирует только удаление и перемещение. Обратите внимание на внутренний класс, который действует как хранитель данных анимации и слушатель состояний анимации:
public class RecAnimator extends RecyclerView.ItemAnimator {
private final static String TAG = "RecAnimator";
private final static int ANIMATION_TYPE_DISAPPEAR = 1;
private final static int ANIMATION_TYPE_MOVE = 2;
// must keep track of all pending/ongoing animations.
private final ArrayList<AnimInfo> pending = new ArrayList<>();
private final HashMap<RecyclerView.ViewHolder, AnimInfo> disappearances = new HashMap<>();
private final HashMap<RecyclerView.ViewHolder, AnimInfo> persistences = new HashMap<>();
@Override
public boolean animateDisappearance(@NonNull RecyclerView.ViewHolder viewHolder, @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
pending.add(new AnimInfo(viewHolder, ANIMATION_TYPE_DISAPPEAR, 0));
dispatchAnimationStarted(viewHolder);
// new pending animation added, return true to indicate we want a call to runPendingAnimations()
return true;
}
@Override
public boolean animateAppearance(@NonNull RecyclerView.ViewHolder viewHolder, @Nullable ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
dispatchAnimationFinished(viewHolder);
return false;
}
@Override
public boolean animatePersistence(@NonNull RecyclerView.ViewHolder viewHolder, @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
if (preLayoutInfo.top != postLayoutInfo.top) {
// required movement
int topDiff = preLayoutInfo.top - postLayoutInfo.top;
AnimInfo per = persistences.get(viewHolder);
if (per != null && per.isRunning) {
// there is already an ongoing animation - update it instead
per.top = per.holder.itemView.getTranslationY() + topDiff;
per.start();
// discard this animatePersistence call
dispatchAnimationFinished(viewHolder);
return false;
}
pending.add(new AnimInfo(viewHolder, ANIMATION_TYPE_MOVE, topDiff));
dispatchAnimationStarted(viewHolder);
// new pending animation added, return true to indicate we want a call to runPendingAnimations()
return true;
}
dispatchAnimationFinished(viewHolder);
return false;
}
@Override
public boolean animateChange(@NonNull RecyclerView.ViewHolder oldHolder, @NonNull RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preLayoutInfo, @NonNull ItemHolderInfo postLayoutInfo) {
dispatchAnimationFinished(oldHolder);
dispatchAnimationFinished(newHolder);
return false;
}
@Override
public void runPendingAnimations() {
for (AnimInfo ai: pending) {
ai.start();
}
pending.clear();
}
@Override
public void endAnimation(RecyclerView.ViewHolder item) {
AnimInfo ai = disappearances.get(item);
if (ai != null && ai.isRunning) {
ai.holder.itemView.animate().cancel();
}
ai = persistences.get(item);
if (ai != null && ai.isRunning) {
ai.holder.itemView.animate().cancel();
}
}
@Override
public void endAnimations() {
for (AnimInfo ai: disappearances.values())
if (ai.isRunning)
ai.holder.itemView.animate().cancel();
for (AnimInfo ai: persistences.values())
if (ai.isRunning)
ai.holder.itemView.animate().cancel();
}
@Override
public boolean isRunning() {
return !pending.isEmpty() &&
!disappearances.isEmpty() &&
!persistences.isEmpty();
}
/**
* This is container for each animation. It's also cancel/end listener for them.
* */
private final class AnimInfo implements Animator.AnimatorListener {
private final RecyclerView.ViewHolder holder;
private final int animationType;
private float top;
private boolean isRunning = false;
private AnimInfo(RecyclerView.ViewHolder holder, int animationType, float top) {
this.holder = holder;
this.animationType = animationType;
this.top = top;
}
void start(){
View itemView = holder.itemView;
itemView.animate().setListener(this);
switch (animationType) {
case ANIMATION_TYPE_DISAPPEAR:
itemView.setPivotY(0f);
itemView.animate().scaleX(0f).scaleY(0f).setDuration(getRemoveDuration());
disappearances.put(holder, this); // must keep track of all animations
break;
case ANIMATION_TYPE_MOVE:
itemView.setTranslationY(top);
itemView.animate().translationY(0f).setDuration(getMoveDuration());
persistences.put(holder, this); // must keep track of all animations
break;
}
isRunning = true;
}
private void resetViewHolderState(){
// reset state as if no animation was ran
switch (animationType) {
case ANIMATION_TYPE_DISAPPEAR:
holder.itemView.setScaleX(1f);
holder.itemView.setScaleY(1f);
break;
case ANIMATION_TYPE_MOVE:
holder.itemView.setTranslationY(0f);
break;
}
}
@Override
public void onAnimationEnd(Animator animation) {
switch (animationType) {
case ANIMATION_TYPE_DISAPPEAR:
disappearances.remove(holder);
break;
case ANIMATION_TYPE_MOVE:
persistences.remove(holder);
break;
}
resetViewHolderState();
holder.itemView.animate().setListener(null); // clear listener
dispatchAnimationFinished(holder);
if (!isRunning())
dispatchAnimationsFinished();
isRunning = false;
}
@Override
public void onAnimationCancel(Animator animation) {
// jump to end state
switch (animationType) {
case ANIMATION_TYPE_DISAPPEAR:
holder.itemView.setScaleX(0f);
holder.itemView.setScaleY(0f);
break;
case ANIMATION_TYPE_MOVE:
holder.itemView.setTranslationY(0f);
break;
}
}
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
}
}
Вы также можете переопределить класс SimpleItemAnimator, который анализирует методы animate... в animateMove, animateRemove и т. д. Для вас.