Обнаружение анимации в Android RecyclerView

RecyclerView , в отличие от ListView , не имеет простого способа установить для него пустое представление, поэтому нужно вручную управлять им, делая пустой вид видимым, если количество элементов адаптера равно 0.

Реализовав это, сначала я попытался вызвать пустую логику представления сразу после изменения структуры ArrayList ( ArrayList в моем случае), например:

 btnRemoveFirst.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { devices.remove(0); // remove item from ArrayList adapter.notifyItemRemoved(0); // notify RecyclerView's adapter updateEmptyView(); } }); 

Он делает это, но имеет недостаток: когда последний элемент удаляется, появляется пустой вид перед завершением анимации удаления, сразу после удаления. Поэтому я решил подождать окончания анимации и обновить интерфейс.

К моему удивлению, я не смог найти хороший способ прослушать анимационные события в RecyclerView. Первое, что приходит на ум, – использовать метод isRunning следующим образом:

 btnRemoveFirst.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { devices.remove(0); // remove item from ArrayList adapter.notifyItemRemoved(0); // notify RecyclerView's adapter recyclerView.getItemAnimator().isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() { @Override public void onAnimationsFinished() { updateEmptyView(); } }); } }); 

К сожалению, обратный вызов в этом случае выполняется немедленно, потому что в этот момент внутренний ItemAnimator все еще не находится в состоянии «running». Итак, возникают вопросы: как правильно использовать метод ItemAnimator.isRunning () и есть ли лучший способ добиться желаемого результата, т. Е. Показать пустой вид после удаления анимации одного элемента ?

Solutions Collecting From Web of "Обнаружение анимации в Android RecyclerView"

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

 recyclerView.setItemAnimator(new DefaultItemAnimator() { @Override public void onAnimationFinished(RecyclerView.ViewHolder viewHolder) { updateEmptyView(); } }); 

Но этот метод не универсален, потому что я должен перейти от конкретной реализации ItemAnimator, используемой RecyclerView. В случае частного внутреннего CoolItemAnimator внутри CoolRecyclerView мой метод не будет работать вообще.


PS: Мой коллега предложил обернуть ItemAnimator внутри декоратора следующим образом:

 recyclerView.setItemAnimator(new ListenableItemAnimator(recyclerView.getItemAnimator())); 

Было бы неплохо, несмотря на то, что для такой тривиальной задачи кажется излишним, но создание декоратора в этом случае невозможно, потому что ItemAnimator имеет метод setListener (), который защищен пакетом, поэтому я, очевидно, не могу его обернуть Как несколько окончательных методов.

Чтобы расширить ответ на ответ Романа Петренко, у меня тоже нет поистине универсального ответа, но я нашел шаблон Factory полезным для того, чтобы хотя бы очистить часть этой проблемы.

 public class ItemAnimatorFactory { public interface OnAnimationEndedCallback{ void onAnimationEnded(); } public static RecyclerView.ItemAnimator getAnimationCallbackItemAnimator(OnAnimationEndedCallback callback){ return new FadeInAnimator() { @Override public void onAnimationFinished(RecyclerView.ViewHolder viewHolder) { callback.onAnimationEnded(); super.onAnimationEnded(viewHolder); } }; } } 

В моем случае я использую библиотеку, которая предоставляет FadeInAnimator, который я уже использовал. Я использую решение Roman в фабричном методе, чтобы подключиться к событию onAnimationEnded, а затем передать событие в цепочку.

Затем, когда я настраиваю свой recyclerview, я указываю, что обратный вызов является моим методом для обновления представления на основе подсчета элементов recyclerview:

 mRecyclerView.setItemAnimator(ItemAnimatorFactory.getAnimationCallbackItemAnimator(this::checkSize)); 

Опять же, он не является полностью универсальным для всех и всех ItemAnimators, но он, по крайней мере, «консолидирует крип», поэтому, если у вас есть несколько разных аниматоров элементов, вы можете просто внедрить фабричный метод здесь, следуя одному шаблону, а затем настроить свою recyclerview Просто указывает, какой именно ItemAnimator вы хотите.

Что для меня работало:

  • Обнаружить, что держатель вида удален
  • В этом случае зарегистрируйте прослушиватель, который будет уведомлен при вызове dispatchAnimationsFinished()
  • Когда все анимации закончены, вызовите слушателя для выполнения задачи ( updateEmptyView() )

 public class CompareItemAnimator extends DefaultItemAnimator implements RecyclerView.ItemAnimator.ItemAnimatorFinishedListener { private OnItemAnimatorListener mOnItemAnimatorListener; public interface OnItemAnimatorListener { void onAnimationsFinishedOnItemRemoved(); } @Override public void onAnimationsFinished() { if (mOnItemAnimatorListener != null) { mOnItemAnimatorListener.onAnimationsFinishedOnItemRemoved(); } } public void setOnItemAnimatorListener(OnItemAnimatorListener onItemAnimatorListener) { mOnItemAnimatorListener = onItemAnimatorListener; } @Override public void onRemoveFinished(RecyclerView.ViewHolder viewHolder) { isRunning(this); }}