自定义RecyclerView的LayoutManager。动画在项目删除

时间:2016-10-25 13:57:40

标签: android android-layout animation android-recyclerview

我为RecyclerView创建了custom layout manager。我已通过setAutoMeasureEnabled(true)

在我的经理的构造函数中启用了自动测量功能

RecyclerView的{​​{1}}设置为layout_height时,此属性wrap_content可以根据其中的项目来衡量LayoutManager的高度。它工作得很好,但当我删除底部的最后一项时,删除动画正在播放,它会导致RecyclerView在动画结束前测量它的高度到结果高度。

看看这个gif。 enter image description here 绿色背景是RecyclerView的地方

您可能已经猜到这种行为对于添加项目是正确的,因为在这种情况下容器应该在动画之前进行测量

如何处理这种情况,以便在动画完成后进行处理RecyclerView自动测量? 我在RecyclerView中有所有的儿童定位逻辑,但我认为发布它不是这个问题所必需的,可能是如此广泛和不清楚。

可能我应该手动处理layoutManager的onLayoutChildren拦截器(当项目被删除时调用4次(在onItemsRemoved调用之前调用一次))。所以我可以根据高度设置测量高度,这是我在删除开始时收到的:

onMeasure

正如你所看到的,我提供了一个大约延迟@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 } 值的处理程序,它在动画完成后执行自动测量,因此这可能会导致所需的结果,但动画的持续时间不是静态的,我不会没有看到任何可能听动画完成的事件。

所以,期望的行为:

enter image description here

顺便说一下,LinearLayoutManager的自动测量工作方式相同

如果你用正确的算法文字描述指向我正确的方向就足够了。

3 个答案:

答案 0 :(得分:2)

我终于明白了。 所以,我的解决方案:

onAdapterChanged方法中订阅dataChanges事件并禁用自动测量。

 @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();
}

onMeasure中,如果手动禁用自动测量,请使用当前大小:

 @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());
    }
}

动画结束后执行自动测量:

 @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();
                }
            });
        }
    });

这个回调链是安全的,因为如果布局管理器与postOnAnimation分离,RecyclerView runnable将无法执行

答案 1 :(得分:2)

@Beloo的答案对我不起作用,我怀疑是因为setAutoMeasureEnabled已过时。我只能覆盖onItemsRemovedonMeasure以及isAutoMeasureEnabled来使其正常工作。

首先创建一个变量,该变量将跟踪isAutoMeasureEnabled属性:

private boolean autoMeasureEnabled = true;

然后覆盖isAutoMeasureEnabled以使用变量:

@Override
public boolean isAutoMeasureEnabled (){
    return autoMeasureEnabled;
}

现在,您可以随意设置变量autoMeasureEnabled。覆盖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();
                }
            });
        }
    });
}

也像@Beloo的答案一样覆盖onMeasure

    @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());
    }
}

这似乎有效。动画速度很快,但定时正确。

答案 2 :(得分:0)

正确的方法是在RecyclerView本身上使用过渡,以便它也为自己的绑定更改设置动画。

来自:https://android-developers.googleblog.com/2016/02/android-support-library-232.html

请注意,尽管RecyclerView为其子动画设置动画,但不会为自己的边界变化设置动画。如果您想对RecyclerView边界进行动画处理,可以使用Transition API。

因此,here TransitionManager.beginDelayedTransition(recyclerView)应该可以正常工作。