Custom RecyclerView's LayoutManager. Automeasuring after animation finished on item delete

android, android-layout, android-recyclerview, animation

Solution

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`

Problem

I've created a custom layout manager for `RecyclerView`. I've enabled auto-measuring in constructor of my manager via `setAutoMeasureEnabled(true)` When `RecyclerView`'s `layout_height` is set to `wrap_content` with this property `LayoutManager` is able to measure height of `RecyclerView` according to items inside it. It works perfectly, but when i delete the last item at bottom, the deleting animation is playing at that moment, it causes `RecyclerView` to measure it's height to result height before the animation have finished. Look at this gif. Where green background is a RecyclerView As you may guessed this behaviour is correct for adding item, because in that case container should be measured before an animation How can i handle this situation to make processing `RecyclerView` auto-measure after animation have finished? I have all child positioning logic in `onLayoutChildren` but i think posting it doesn't required for this question and could be so broad and unclear. Probably i should handle `onMeasure` interceptor of layoutManager manually (It called 4 times when item have deleted (one time before onItemsRemoved invocation)). So i could set measured height there, based on height, which i receive when deletion have started: ``` @Override public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) { super.onMeasure(recycler, state, widthSpec, heightSpec); requestSimpleAnimationsInNextLayout(); if (!isAutoMeasureEnabled()) { setMeasuredDimension(getWidth(), preHeight); } } @Override public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) { super.onItemsRemoved(recyclerView, positionStart, itemCount); setAutoMeasureEnabled(false); new Handler(Looper.myLooper()).postDelayed(new Runnable() { @Override public void run() { setAutoMeasureEnabled(true); requestSimpleAnimationsInNextLayout(); requestLayout(); } }, 400); preHeight = //calculate height before deletion } ``` As you can see, i provided a handler with approximately delayed `400ms` value, which execute auto measure after animations finished, so this could lead to needed results, but the duration of animation isn't static and i don't see any possibility to listen animation finished event. So, desired behaviour: By the way auto-measuring of LinearLayoutManager works with same way If you pointed me to right direction with just text description of algorithm it would be enough.

Original source