2015-11-14 8 views
24

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

Реализация этого, сначала я попытался назвать пустой вид логики сразу после изменения нижележащих структуры (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(); 
    } 
}); 

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

К моему удивлению, я не смог найти хороший способ прослушать анимационные события в 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 еще не находится в «бегущей» состоянии. Итак, вопросы: как правильно использовать метод ItemAnimator.isRunning() и есть ли лучший способ достичь желаемого результата, то есть показать пустой вид после удаления анимации одного элемента завершен?

ответ

14

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

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

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


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

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

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

+0

Эй, римский. Вы когда-нибудь сталкивались с более централизованным решением этой проблемы? – Ryan

+0

@ Ryan: К сожалению, нет. Но с момента моего ответа я не исследовал эту проблему. –

0

Чтобы расширить ответ на ответ Романа Петренко, у меня тоже нет поистине универсального ответа, но я нашел шаблон 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 вы хотите.

1

То, что сработало для меня следующее:

  • обнаружить, что держатель вид был удален
  • в этом случае зарегистрировать слушателя, чтобы получать уведомления, когда 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); 
}} 
+0

Это сработало для меня – BQuadra

4

У меня есть немного более общий случай, когда я хочу обнаружить, когда просмотр ресайклера закончен, полностью оживляя, когда один или несколько элементов удаляются или добавляются одновременно.

Я пробовал ответ Романа Петренко, но в этом случае он не работает. Проблема в том, что для каждой записи в представлении ресайклера вызывается onAnimationFinished. Большинство записей не изменилось, поэтому onAnimationFinished называется более или менее мгновенным. Но для дополнений и абстракций анимация занимает немного времени, поэтому ее называют позже.

Это приводит к по меньшей мере двум проблемам. Предположим, у вас есть метод под названием doStuff(), который вы хотите запустить, когда анимация выполнена.

  1. Если вы просто звоните doStuff() в onAnimationFinished вы будете называть его один раз для каждого элемента в представлении ресайклера, который не может быть то, что вы хотите сделать.

  2. Если вы только что позвонили doStuff() при первом вызове onAnimationFinished, вы можете позвонить ему задолго до завершения последней анимации.

Если бы вы могли знать, сколько элементов есть анимировать вы могли убедиться, что вы называете doStuff(), когда последняя анимация заканчивается. Но я не нашел способа узнать, сколько оставшихся анимаций в очереди.

Мое решение этой проблемы заключается в том, чтобы позволить ресайклеру сначала начать анимацию с помощью new Handler().post(), а затем настроить слушателя с помощью isRunning(), который вызывается, когда анимация готова. После этого он повторяет процесс до тех пор, пока все представления не будут анимированы.

void changeAdapterData() { 
    // ... 
    // Changes are made to the data held by the adapter 
    recyclerView.getAdapter().notifyDataSetChanged(); 

    // The recycler view have not started animating yet, so post a message to the 
    // message queue that will be run after the recycler view have started animating. 
    new Handler().post(waitForAnimationsToFinishRunnable); 
} 

private Runnable waitForAnimationsToFinishRunnable = new Runnable() { 
    @Override 
    public void run() { 
     waitForAnimationsToFinish(); 
    } 
}; 

// When the data in the recycler view is changed all views are animated. If the 
// recycler view is animating, this method sets up a listener that is called when the 
// current animation finishes. The listener will call this method again once the 
// animation is done. 
private void waitForAnimationsToFinish() { 
    if (recyclerView.isAnimating()) { 
     // The recycler view is still animating, try again when the animation has finished. 
     recyclerView.getItemAnimator().isRunning(animationFinishedListener); 
     return; 
    } 

    // The recycler view have animated all it's views 
    onRecyclerViewAnimationsFinished(); 
} 

// Listener that is called whenever the recycler view have finished animating one view. 
private RecyclerView.ItemAnimator.ItemAnimatorFinishedListener animationFinishedListener = 
     new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() { 
    @Override 
    public void onAnimationsFinished() { 
     // The current animation have finished and there is currently no animation running, 
     // but there might still be more items that will be animated after this method returns. 
     // Post a message to the message queue for checking if there are any more 
     // animations running. 
     new Handler().post(waitForAnimationsToFinishRunnable); 
    } 
}; 

// The recycler view is done animating, it's now time to doStuff(). 
private void onRecyclerViewAnimationsFinished() { 
    doStuff(); 
} 
+0

Он отлично работает, плюс я пробовал, когда метод '' 'notifyItemRemoved (position)' '' и работает с уважением. –

Смежные вопросы