Custom RecyclerView's LayoutManager. Automeasuring after animation finished on item delete
I've at last figured it out. So, my solution:
Subscribe to dataChanges event in onAdapterChanged
method and disable automeasuring.
@Override
public void onAdapterChanged(RecyclerView.Adapter oldAdapter,
RecyclerView.Adapter newAdapter) {
newAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
// on api 16 this is invoked before onItemsRemoved
super.onItemRangeRemoved(positionStart, itemCount);
/** we detected removing event, so should process measuring manually
*/
setAutoMeasureEnabled(false);
}
});
//Completely scrap the existing layout
removeAllViews();
}
In onMeasure
in case auto-measuring disabled measure manually, use current size:
@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
super.onMeasure(recycler, state, widthSpec, heightSpec);
if (!isAutoMeasureEnabled()) {
// we should perform measuring manually
// so request animations
requestSimpleAnimationsInNextLayout();
//keep size until remove animation will be completed
setMeasuredDimension(getWidth(), getHeight());
}
}
Perform auto-measuring after animation finished:
@Override
public void onItemsRemoved(final RecyclerView recyclerView, int positionStart, int itemCount) {
super.onItemsRemoved(recyclerView, positionStart, itemCount);
//subscribe to next animations tick
postOnAnimation(new Runnable() {
@Override
public void run() {
//listen removing animation
recyclerView.getItemAnimator().isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
@Override
public void onAnimationsFinished() {
//when removing animation finished return auto-measuring back
setAutoMeasureEnabled(true);
// and process onMeasure again
requestLayout();
}
});
}
});
This callbacks chain is safe, because postOnAnimation
runnable won't be executed in case layout manager is detached from RecyclerView
@Beloo's answer was not working for me, I suspect because setAutoMeasureEnabled
has been deprecated. I was able to get it working by only overriding onItemsRemoved
, onMeasure
and also isAutoMeasureEnabled
.
First create a variable that will keep track of the isAutoMeasureEnabled
property:
private boolean autoMeasureEnabled = true;
Then override isAutoMeasureEnabled
to use the variable:
@Override
public boolean isAutoMeasureEnabled (){
return autoMeasureEnabled;
}
Now you can set the variable autoMeasureEnabled
at will. Override onItemsRemoved
:
@Override
public void onItemsRemoved(@NonNull final RecyclerView recyclerView, int positionStart, int itemCount) {
super.onItemsRemoved(recyclerView, positionStart, itemCount);
autoMeasureEnabled = false;
postOnAnimation(new Runnable() {
@Override
public void run() {
recyclerView.getItemAnimator().isRunning(new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
@Override
public void onAnimationsFinished() {
autoMeasureEnabled = true;
requestLayout();
}
});
}
});
}
Also override onMeasure
just like in @Beloo's answer:
@Override
public void onMeasure(@NotNull RecyclerView.Recycler recycler, @NotNull RecyclerView.State state, int widthSpec, int heightSpec) {
super.onMeasure(recycler, state, widthSpec, heightSpec);
if (!isAutoMeasureEnabled()) {
// we should perform measuring manually
// so request animations
requestSimpleAnimationsInNextLayout();
//keep size until remove animation will be completed
setMeasuredDimension(getWidth(), getHeight());
}
}
This seems to work. The animation is fast, but timed correctly.